- 对继承的不良应用会导致爆炸的子类数量
- 如何通过组合/聚合的方式扩展一个类的功能
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码, 这时候的关键是划清责任
- 典型模式
- Decorator
- Bridge
缘起 Motivation
- 在某些情况下我们可能会“过度地使用继承来扩展对象的功能”
- 由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性
-
静态特质(继承实现)
NetworkStream::Read(number); FileStram::Read(number);
-
动态特质(组合/聚合实现)
stream->Read(number);
-
- 并且随着子类的增多(扩展功能的增多)
- 各种子类的组合(扩展功能的组合)会导致更多子类的膨胀
- 由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性
- 如何使“对象功能的扩展”能够根据需要来动态地实现?
- 同时避免“扩展功能的增多”带来的子类膨胀问题?
- 从而使得任何“功能 扩展变化”所导致的影响将为最低?
初级设计
- 一个IO流操作的功能
- 流的需求
- 文件流
- 网络流
- 内存流
- 操作的需求
- 加密
- 缓存
- 流的需求
设计类图
这种继承方式,规模非常大
一个文件流的基类
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
流的子类
// 文件流
class FileStream: public Stream{
public:
virtual char Read(int number) { //读文件流 }
virtual void Seek(int position) { //定位文件流 }
virtual void Write(char data) { //写文件流 }
};
// 网络流
class NetworkStream :public Stream{
public:
virtual char Read(int number) { //读网络流 }
virtual void Seek(int position) { //定位网络流 }
virtual void Write(char data) { //写网络流 }
};
// 内存流
class MemoryStream :public Stream{
public:
virtual char Read(int number) { //读内存流 }
virtual void Seek(int position) { //定位内存流 }
virtual void Write(char data) { //写内存流 }
};
流的操作功能
// 对文件流的加密操作
class CryptoFileStream :public FileStream{
public:
virtual char Read(int number){
//额外的加密操作...
FileStream::Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
FileStream::Seek(position);//定位文件流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
FileStream::Write(data);//写文件流
//额外的加密操作...
}
};
-
加密操作以子类的方式继承对应的流父类
// 加密文件流 class CryptoFileStream :public FileStream{ ...} class CryptoNetworkStream :public NetworkStream {...} class CryptoMemoryStream :public MemoryStream{...} // 缓存文件流 class BufferedFileStream : public FileStream{ ... }; class BufferedNetworkStream : public NetworkStream{ //... }; class BufferedMemoryStream : public MemoryStream{ //... } // 加密缓存文件流 class CryptoBufferedFileStream :public FileStream{...}
-
但是内部每个加密操作代码都是一样的
virtual void Seek(int position){ //额外的加密操作... Stream::Seek(position);//定位流 //额外的加密操作... } // 加密又缓存 virtual void Seek(int position){ //额外的加密操作... //额外的缓冲操作... Stream::Seek(position);//定位流 //额外的加密操作... //额外的缓冲操作... }
重构设计
继承变成组合
-
继承
class CryptoFileStream :public FileStream{ public: virtual char Read(int number){ //额外的加密操作... FileStream::Read(number);//读文件流 } };
-
组合
class CryptoFileStream { FileStream *stream public: virtual char Read(int number){ //额外的加密操作... stream->Read(number);//读文件流 } };
组合再通过抽象重构
class CryptoFileStream {
Stream *stream; // = new FileStream(); [不同]
public:
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
};
class NetworkStream {
Stream *stream; // = new NetworkStream(); [不同]
public:
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
};
- 通过
stream = new FileStream()
得到具体的stream对象 - 把编译期的对象编程运行时的对象
- 设计模式的真谛
- 编译时复用
- 变化(需求的变化)都放到运行时
- 通过多态的方式支持变化
- 上面编译期都是
Stream *stream
- 在运行时用
new xxxStream()
来产生变化的对象
那么最后
class CryptoFileStream :public FileStream{ ...}
class CryptoNetworkStream :public NetworkStream {...}
class CryptoMemoryStream :public MemoryStream{...}
就可以重构成
class CryptoFileStream -> class CryptoStream
class CryptoStream: public Stream {
Stream* stream; //... 运行时的对象多态
public:
CryptoStream(Stream* stm):stream(stm){ }
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
stream::Seek(position);//定位文件流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
stream::Write(data);//写文件流
//额外的加密操作...
}
};
- 这里继承
public Stream
是为了保证虚函数的接口标准Stream
定义了接口规范
重构优化
class CryptoStream: public Stream {
Stream* stream;//...
...
}
class BufferedStream : public Stream{
Stream* stream;//...
...
}
- 如果子类都有同一个字段,可以把这个字段上提
-
可以把 Stream 的子类的
Stream *stream
上提到基类Stream中class Stream { Stream *stream; } class CrytoSteam: public Stream {}
-
-
但FileStream这个子类不需要
Stream
这个字段class FileStream: public Stream{ public: virtual char Read(int number){ //读文件流 } }
所以
Stream *stream
字段应该提升到一个中间类 -
中间类 Decorator
class DecoratorStream: public Stream{ protected: Stream* stream;//... DecoratorStream(Stream * stm):stream(stm){ } };
-
有
Stream* stream
字段的子类继承中间类Decorator
class CryptoStream: public DecoratorStream { public: CryptoStream(Stream* stm):DecoratorStream(stm){ } virtual char Read(int number){ //额外的加密操作... stream->Read(number);//读文件流 } virtual void Seek(int position){ //额外的加密操作... stream::Seek(position);//定位文件流 //额外的加密操作... } virtual void Write(byte data){ //额外的加密操作... stream::Write(data);//写文件流 //额外的加密操作... } }; class BufferedStream : public DecoratorStream{ Stream* stream;//... public: BufferedStream(Stream* stm):DecoratorStream(stm){ } ... }
装饰:在其他类的基础上再去做新的功能
重构后的类图
对比之前对继承不良使用造成的子类爆炸
修改使用方式
-
重构前编译时装配
void Process(){ CryptoFileStream *fs1 = new CryptoFileStream(); BufferedFileStream *fs2 = new BufferedFileStream(); CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); }
-
重构后运行时装配
void Process(){ FileStream* s1=new FileStream(); // crypto + filestream CryptoStream* s2=new CryptoStream(s1); // buffer + filestream BufferedStream* s3=new BufferedStream(s1); // buffer + crypto + filestream BufferedStream* s4=new BufferedStream(s2); }
模式定义
- 动态(组合)地给一个对象增加一些额外的职责
- 就增加功能而言,Decorator模式比生成子类(继承)更为灵活
- 消除重复代码
- 减少子类个数
装饰器结构
要点总结
- 通过采用组合而非继承的手法
- Decorator模式实现了在运行时动态扩展对象功能的能力
- 而且可以根据需要扩展多个功能
- 避免了使用继承带来的“灵活性差”和“多子类衍生问题”
- Decorator类在接口上表现为is-a Component的继承关系
- 即Decorator类继承了Component类所具有的接口
- 但在实现上又表现为has-a Component的组合关系
- 即Decorator类又使用了另外一个Component类
-
说明
class DecoratorStream: public Stream{ protected: Stream* stream;//... DecoratorStream(Stream * stm):stream(stm){ } };
- Decorator继承了Stream
- 为了完善接口的规范
- 但内部又聚合了父类Stream
- 为了动态的运行时装配对象
- 为了将来支持动态实例化
- Decorator继承了Stream
- Decorator模式的目的并非解决“多子类衍生的多继承”问题
- Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义
- FileStream、NetworkStream是一个变化的方向
- 主体操作
- CryptoStream和FileStream不是一个变化的方向
- 是扩展操作
- FileStream、NetworkStream是一个变化的方向
- Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义
附录
初始设计
//业务操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类
class FileStream: public Stream{
public:
virtual char Read(int number) { //读文件流 }
virtual void Seek(int position) { //定位文件流 }
virtual void Write(char data) { //写文件流 }
};
class NetworkStream :public Stream{
public:
virtual char Read(int number){ //读网络流 }
virtual void Seek(int position){ //定位网络流 }
virtual void Write(char data){ //写网络流 }
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){ //读内存流 }
virtual void Seek(int position){ //定位内存流 }
virtual void Write(char data){ //写内存流 }
};
//扩展操作
class CryptoFileStream :public FileStream{
public:
virtual char Read(int number){
//额外的加密操作...
FileStream::Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
FileStream::Seek(position);//定位文件流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
FileStream::Write(data);//写文件流
//额外的加密操作...
}
};
class CryptoNetworkStream : :public NetworkStream{
public:
virtual char Read(int number){
//额外的加密操作...
NetworkStream::Read(number);//读网络流
}
virtual void Seek(int position){
//额外的加密操作...
NetworkStream::Seek(position);//定位网络流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
NetworkStream::Write(data);//写网络流
//额外的加密操作...
}
};
class CryptoMemoryStream : public MemoryStream{
public:
virtual char Read(int number){
//额外的加密操作...
MemoryStream::Read(number);//读内存流
}
virtual void Seek(int position){
//额外的加密操作...
MemoryStream::Seek(position);//定位内存流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
MemoryStream::Write(data);//写内存流
//额外的加密操作...
}
};
class BufferedFileStream : public FileStream{
//...
};
class BufferedNetworkStream : public NetworkStream{
//...
};
class BufferedMemoryStream : public MemoryStream{
//...
}
class CryptoBufferedFileStream :public FileStream{
public:
virtual char Read(int number){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Seek(position);//定位文件流
//额外的加密操作...
//额外的缓冲操作...
}
virtual void Write(byte data){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Write(data);//写文件流
//额外的加密操作...
//额外的缓冲操作...
}
};
void Process(){
//编译时装配
CryptoFileStream *fs1 = new CryptoFileStream();
BufferedFileStream *fs2 = new BufferedFileStream();
CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}
重构
//业务操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类
class FileStream: public Stream{
public:
virtual char Read(int number) { //读文件流 }
virtual void Seek(int position) { //定位文件流 }
virtual void Write(char data) { //写文件流 }
};
class NetworkStream :public Stream{
public:
virtual char Read(int number) { //读网络流 }
virtual void Seek(int position) { //定位网络流 }
virtual void Write(char data) { //写网络流 }
};
class MemoryStream :public Stream{
public:
virtual char Read(int number) { //读内存流 }
virtual void Seek(int position) { //定位内存流 }
virtual void Write(char data) { //写内存流 }
};
//扩展操作
class CryptoStream: public Stream {
Stream* stream;//...
public:
CryptoStream(Stream* stm):stream(stm){ }
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
stream::Seek(position);//定位文件流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
stream::Write(data);//写文件流
//额外的加密操作...
}
};
class BufferedStream : public Stream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):stream(stm){ }
//...
};
void Process(){
//运行时装配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}
Rust 实现
实现功能
- 实现一个穿衣功能,给工程师穿上各种衣服
- 装饰器模式的理解
- 装饰器继承了被装饰的类,主要是为了规范它要装饰的类的接口,以便像使用原来类的方式来使用装饰后的实例
- 装饰器又组合了被装饰的类,是为了利用多态特性,它可以调用所有继承此类的各种实例
- 然后,装饰器在它的调用接口中,先调用被装饰类的函数接口,然后再调用想加上的装饰功能
结构
具体实现
trait IPerson {
fn 穿着(&self);
}
struct 工程师 {
技能: String,
名字: String,
}
impl 工程师 {
fn new(技能: String, 名字: String) -> Self {
Self { 技能, 名字 }
}
}
impl IPerson for 工程师 {
fn 穿着(&self) {
println!("[工程师] {}, 技能: {},着装:", self.名字, self.技能);
}
}
struct 老师 {
头衔: String,
名字: String,
}
impl 老师 {
fn new(头衔: String, 名字: String) -> Self {
Self { 头衔, 名字 }
}
}
impl IPerson for 老师 {
fn 穿着(&self) {
println!("[老师] {}, 头衔: {},着装:", self.名字, self.头衔);
}
}
trait 服装装饰器 {
fn 穿好服饰(&self);
}
struct 休闲裤装饰器 {
人: Box<dyn IPerson>,
}
impl 休闲裤装饰器 {
fn new(人: Box<dyn IPerson>) -> Self {
Self { 人 }
}
}
impl IPerson for 休闲裤装饰器 {
fn 穿着(&self) {
self.人.穿着();
self.穿好服饰();
}
}
impl 服装装饰器 for 休闲裤装饰器 {
fn 穿好服饰(&self) {
println!("[着装] 一条卡其色休闲裤");
}
}
struct 腰带装饰器 {
人: Box<dyn IPerson>,
}
impl 腰带装饰器 {
fn new(人: Box<dyn IPerson>) -> Self {
Self { 人 }
}
}
impl IPerson for 腰带装饰器 {
fn 穿着(&self) {
self.人.穿着();
self.穿好服饰();
}
}
impl 服装装饰器 for 腰带装饰器 {
fn 穿好服饰(&self) {
println!("[着装] 一条银色针扣头的黑色腰带");
}
}
struct 皮鞋装饰器 {
人: Box<dyn IPerson>,
}
impl 皮鞋装饰器 {
fn new(人: Box<dyn IPerson>) -> Self {
Self { 人 }
}
}
impl IPerson for 皮鞋装饰器 {
fn 穿着(&self) {
self.人.穿着();
self.穿好服饰();
}
}
impl 服装装饰器 for 皮鞋装饰器 {
fn 穿好服饰(&self) {
println!("[着装] 一双深色休闲皮鞋");
}
}
struct 针织毛衣装饰器 {
人: Box<dyn IPerson>,
}
impl 针织毛衣装饰器 {
fn new(人: Box<dyn IPerson>) -> Self {
Self { 人 }
}
}
impl IPerson for 针织毛衣装饰器 {
fn 穿着(&self) {
self.人.穿着();
self.穿好服饰();
}
}
impl 服装装饰器 for 针织毛衣装饰器 {
fn 穿好服饰(&self) {
println!("[着装] 一件紫红色针织毛衣");
}
}
struct 眼镜装饰器 {
人: Box<dyn IPerson>,
}
impl 眼镜装饰器 {
fn new(人: Box<dyn IPerson>) -> Self {
Self { 人 }
}
}
impl IPerson for 眼镜装饰器 {
fn 穿着(&self) {
self.人.穿着();
self.穿好服饰();
}
}
impl 服装装饰器 for 眼镜装饰器 {
fn 穿好服饰(&self) {
println!("[着装] 一幅金属圆形眼镜");
}
}
fn main() {
let 刘璟 = 工程师::new("前端工程师".to_string(), "刘璟".to_string());
let 刘璟 = 休闲裤装饰器::new(Box::new(刘璟));
let 刘璟 = 皮鞋装饰器::new(Box::new(刘璟));
let 刘璟 = 腰带装饰器::new(Box::new(刘璟));
let 刘璟 = 针织毛衣装饰器::new(Box::new(刘璟));
let 刘璟 = 眼镜装饰器::new(Box::new(刘璟));
刘璟.穿着();
println!();
let 唐维 = 老师::new("教授".to_string(), "唐维".to_string());
let 唐维 = 针织毛衣装饰器::new(Box::new(唐维));
let 唐维 = 眼镜装饰器::new(Box::new(唐维));
唐维.穿着();
}
执行结果:
[工程师] 刘璟, 技能: 前端工程师,着装:
[着装] 一条卡其色休闲裤
[着装] 一双深色休闲皮鞋
[着装] 一条银色针扣头的黑色腰带
[着装] 一件紫红色针织毛衣
[着装] 一幅金属圆形眼镜
[老师] 唐维, 头衔: 教授,着装:
[着装] 一件紫红色针织毛衣
[着装] 一幅金属圆形眼镜