原型法
此原型法非原型模式,而是类似JavaScript中的原型扩展,在JS中,能够很轻松地为String类型“原地”扩展方法,如:
String.prototype.isDigit = function() {
return this.length && !(/\D/.test(this));
};
这个能力其实很好用,但是C++无法这样,一直觉得std::string
的功能不足,想为其添加更丰富的如trim
/split
之类的语义,只能采用继承或者组合代理方式:
继承:用一个新类继承
std::string
,并为新类实现trim
/split
组合代理:用一个新类组合
std::string
,并为新类代理所有std::string
的方法,包括各类构造方法和析构方法,再为新类实现trim
/split
然后,使用std::string
的地方替换成新类。这时候那种都比较复杂,组合的方式更复杂一些,所以也别无脑相信面向对象里“组合一定优于继承”。幸运的是,Rust能轻易完成原型法,比如有个bytes
库提供了可廉价共享的内存缓冲区,避免不必要的内存搬运拷贝,bytes::BytesMut
实现了可变缓冲区bytes::BufMut
,有一系列为其写入u8、i8、u16、i16、slice等基础类型的接口,对于基础的通用的在bytes库中已经足够了,现在有个网络模块,想往bytes::BytesMut
中写入std::net::SocketAddr
结构,Rust可轻易为BytesMut
扩展实现put_socket_addr
:
pub trait WriteSocketAddr {
fn put_socket_addr(&mut self, sock_addr: &std::net::SocketAddr);
}
impl WriteSocketAddr for bytes::BytesMut {
fn put_socket_addr(&mut self, sock_addr: &std::net::SocketAddr) {
match sock_addr {
SocketAddr::V4(v4) => {
self.put_u8(4); // 代表v4地址族
self.put_slice(v4.ip().octets().as_ref());
self.put_u16(v4.port());
}
SocketAddr::V6(v6) => {
self.put_u8(6); // 代表v6地址族
self.put_slice(v6.ip().octets().as_ref());
self.put_u16(v6.port());
}
}
}
}
然后就可以使用BytesMut::put_socket_addr
了,只需use WriteSocketAddr
引入这个trait就可以,是不是很轻松!为何会这么容易?先看JS的原型法,其背后是原型链在支撑,调用String的方法,不仅在Str