装饰设计模式与继承
Decorator design pattern vs. inheritance?
我已经阅读了维基百科上的装饰器设计模式,以及这个网站上的代码示例。
我认为传统继承遵循的是is-a模式,而decorator遵循的是has-a模式。装饰师的称呼惯例看起来像是"皮肤"之上的"皮肤"。。超过"核心"。例如
I* anXYZ = new Z( new Y( new X( new A ) ) );
如上面的代码示例链接所示。
然而,仍然有几个问题我不明白:
wiki所说的"装饰器模式可用于在运行时扩展(装饰)某个对象的功能"是什么意思?新。。。"(new…(new…))"是一个运行时调用,很好,但却是一个"AwithXYZ anXYZ;"是编译时的继承吗?
从代码示例链接中,我可以看到类定义的数量在两种实现中几乎相同。我记得在其他一些设计模式书中,比如《头先设计模式》。他们以星巴克咖啡为例,并表示传统继承将导致"类爆炸",因为对于每种咖啡组合,你都会为其找到一个类。
但在这种情况下,装饰师不是也一样吗?如果一个decorator类可以接受ANY抽象类并对其进行装饰,那么我想它确实可以防止爆炸,但从代码示例来看,您有确切的类定义#,不亚于。。。
有人能解释一下吗?
让我们以一些抽象流为例,假设您希望通过它们提供加密和压缩服务。
使用decorator(伪代码):
Stream plain = Stream();
Stream encrypted = EncryptedStream(Stream());
Stream zipped = ZippedStream(Stream());
Stream zippedEncrypted = ZippedStream(EncryptedStream(Stream());
Stream encryptedZipped = EncryptedStream(ZippedStream(Stream());
有了继承,你就有了:
class Stream() {...}
class EncryptedStream() : Stream {...}
class ZippedStream() : Stream {...}
class ZippedEncryptedStream() : EncryptedStream {...}
class EncryptedZippedStream() : ZippedStream {...}
1) 使用decorator,您可以根据需要在运行时组合功能。每个类只处理功能的一个方面(压缩、加密…)
2) 在这个简单的例子中,我们有3个类带有decorator,5个类带有继承。现在让我们添加更多的服务,例如过滤和剪辑。使用decorator,您只需要再增加2个类就可以支持所有可能的场景,例如过滤->剪切->压缩->加密。对于继承,您需要为每个组合提供一个类,这样您最终会得到几十个类。
按相反顺序:
2) 比如说,有了10个不同的独立扩展,运行时可能需要它们的任何组合,10个decorator类就可以完成这项工作。要通过继承覆盖所有可能性,您需要1024子类。而且没有办法绕过大量的代码冗余。
1) 假设您在运行时有1024个子类可供选择。试着勾画出所需的代码。请记住,您可能无法指定选择或拒绝选项的顺序。还要记住,在扩展实例之前,您可能需要使用一段时间。继续,尝试。相比之下,使用decorator进行操作是微不足道的。
你说得对,它们有时可能非常相似。这两种解决方案的适用性和好处将取决于您的情况。
对于你的第二个问题,其他人击败了我,使我得到了充分的答案。简而言之,您可以组合装饰器来实现继承无法实现的更多组合。
因此,我专注于第一个:
不能严格地说编译时是坏的,运行时是好的,这只是不同的灵活性。在运行时更改内容的能力对某些项目来说可能很重要,因为它允许在不重新编译的情况下进行更改,这可能很慢,并且需要您处于可以编译的环境中。
一个不能使用继承的例子是,当您想向实例化的对象添加功能时。假设为您提供了一个实现日志接口的对象实例:
public interface ILog{
//Writes string to log
public void Write( string message );
}
现在假设您开始一个复杂的任务,该任务涉及许多对象,并且每个对象都进行日志记录,因此您可以传递日志记录对象。但是,您希望任务中的每条消息都以任务名称和任务Id为前缀。您可以传递一个函数,或者传递名称和Id,并相信每个调用方都遵循预先挂起该信息的规则,或者您可以在传递之前装饰日志对象,而不必担心其他对象会正确地
public class PrependLogDecorator : ILog{
ILog decorated;
public PrependLogDecorator( ILog toDecorate, string messagePrefix ){
this.decorated = toDecorate;
this.prefix = messagePrefix;
}
public void Write( string message ){
decorated.Write( prefix + message );
}
}
对C#代码感到抱歉,但我认为它仍然会将想法传达给了解C++的人
要解决问题的第二部分(这可能会反过来解决第一部分),使用decorator方法,您可以访问相同数量的组合,但不必编写它们。如果您有3层装饰器,每层有5个选项,那么就有5*5*5
个可能的类可以使用继承来定义。使用decorator方法,您需要15。
首先,我是一个C#用户,已经有一段时间没有处理过C++了,但希望你能理解我的想法。
脑海中浮现的一个很好的例子是DbRepository
和CachingDbRepository
:
public interface IRepository {
object GetStuff();
}
public class DbRepository : IRepository {
public object GetStuff() {
//do something against the database
}
}
public class CachingDbRepository : IRepository {
public CachingDbRepository(IRepository repo){ }
public object GetStuff() {
//check the cache first
if(its_not_there) {
repo.GetStuff();
}
}
所以,如果我只是使用继承,我会有一个DbRepository
和一个CachingDbRepository
。DbRepository
将从数据库进行查询;CachingDbRepository
将检查其高速缓存,如果数据不在那里,它将查询数据库。因此,这里可能存在重复的实现。
通过使用decorator模式,我仍然有相同数量的类,但我的CachingDbRepository
接收一个IRepository
,并调用它的GetStuff()
,以便在数据不在缓存中的情况下从底层repo获取数据。
所以类的数量是相同的,但是类的使用是相关的。CachingDbRepo
调用传递给它的Repo…所以它更像是组合而非继承。
我发现什么时候决定只使用继承而不是装饰是主观的。
我希望这能有所帮助。祝你好运
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 资源管理设计模式
- 用于在回调中调用解析器的设计模式
- 设计帮助 - 为不同类型的消息处理通用接口的设计模式
- 在这种情况下我应该使用哪种设计模式
- C++中物体改变识别的设计模式?
- 确保所有构造函数调用相同的函数 c++ 设计模式
- 需要实例化不同类/对象并在启动时确定的硬件插槽的设计模式
- 设计模式,以避免不必要地添加抽象函数以适应新功能
- 工厂设计模式优化
- 具有多个继承共享一个资源的对象 - 寻找良好的设计模式
- 在我的情况下,多重继承是一种好的设计模式吗?
- 设计模式:我应该在这里使用继承吗?
- 装饰设计模式与继承
- 依赖注入/继承设计模式的构造函数参数太多
- 设计模式:继承和封装继承
- 具有继承的桥接设计模式,其中抽象基类具有成员数据
- 多重继承和单例设计模式
- 哪种设计模式可以使继承链中的类更薄
- C++设计模式:可以将继承的方法私有为派生类