我已阅读维基百科的decorator design pattern和this site的代码示例。
我认为传统继承遵循'is-a'模式,而decorator遵循'has-a'模式。装饰者的调用约定看起来像'皮肤'上的'皮肤'......超过'核心'。 e.g。
I* anXYZ = new Z( new Y( new X( new A ) ) );
如上面的代码示例链接所示。
然而,仍然有一些我不明白的问题:
wiki是什么意思'装饰模式可以用来扩展(装饰)某个对象在运行时'的功能? 'new ...(new ...(new ...))'是一个运行时调用,但很好但是'AwithXYZ anXYZ;'是编译时的继承而且不好?
从代码示例链接中我可以看到类定义的数量在两个实现中几乎相同。我记得在其他一些设计模式书中,比如“Head first design patterns”。他们使用starbuzz咖啡作为例子,并说传统的继承将导致'类爆炸',因为对于每种咖啡组合,你会想出一个类。
但在这种情况下,装饰者是不是一样的?如果装饰器类可以使用任何抽象类并对其进行装饰,那么我猜它确实可以防止爆炸,但是从代码示例中,您可以获得确切的类定义,不能少......
有人会解释吗?
答案 0 :(得分:70)
让我们以一些抽象流为例,想象一下你想为它们提供加密和压缩服务。
使用装饰器(伪代码):
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)使用装饰器,您可以根据需要在运行时组合功能。每个类只关注功能的一个方面(压缩,加密......)
2)在这个简单的例子中,我们有3个带有装饰器的类,5个带有继承的类。现在让我们添加更多服务,例如过滤和剪裁。使用装饰器,您只需要另外两个类来支持所有可能的场景,例如:过滤 - >剪辑 - >压缩 - > encription。 通过继承,您需要为每个组合提供一个类,以便最终获得数十个类。
答案 1 :(得分:7)
按相反的顺序:
2)例如,有10个不同的独立扩展,在运行时可能需要任何组合,10个装饰器类将完成这项工作。要通过继承涵盖所有可能性,您需要 1024 子类。并且没有办法解决大规模的代码冗余问题。
1)想象一下,你有1024个子类可以在运行时选择。尝试草拟出所需的代码。请记住,您可能无法决定选择或拒绝选项的顺序。还要记住,在扩展它之前,您可能需要使用一段时间。来吧,试试吧。相比之下,使用装饰器进行操作是微不足道的。
答案 2 :(得分:3)
你是对的,他们有时会非常相似。任何一种解决方案的适用性和好处都取决于您的情况。
其他人已经打败了我对第二个问题的充分答案。简而言之,您可以组合装饰器来实现更多组合,而这些组合对于继承是无法做到的。
因此我专注于第一个:
你不能严格地说编译时间差,运行时间好,它只是灵活性不同。在某些项目中,在运行时更改内容的能力可能很重要,因为它允许更改而无需重新编译,这可能很慢并且需要您处于可以编译的环境中。
您不能使用继承的示例是您希望向实例化对象添加功能。假设您提供了一个实现日志记录接口的对象实例:
public interface ILog{
//Writes string to log
public void Write( string message );
}
现在假设您开始涉及许多对象的复杂任务,并且每个对象都执行日志记录,因此您可以传递日志记录对象。但是,您希望任务中的每条消息都以任务名称和任务ID为前缀。您可以传递一个函数,或传递Name和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 ++的人
答案 3 :(得分:1)
要解决问题的第二部分(可能会反过来解决您的第一部分),使用装饰器方法可以访问相同数量的组合,但不必编写它们。如果您有3层装饰器,每个级别有5个选项,则可以使用5*5*5
个类来定义继承。使用装饰器方法需要15。
答案 4 :(得分:1)
首先,我是一个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
将检查其缓存,如果数据不存在,它将查询数据库。所以这里可能有重复的实现。
通过使用装饰器模式,我仍然拥有相同数量的类,但我的CachingDbRepository
接受IRepository
并调用其GetStuff()
来获取来自底层回购的数据它不在缓存中。
所以类的数量是相同的,但是类的使用是相关的。 CachingDbRepo
调用传递给它的Repo ...所以它更像是继承的组合。
我觉得何时决定何时只使用继承而不是装饰是主观的。
我希望这会有所帮助。祝你好运!