装饰设计模式与继承?

时间:2012-09-12 00:47:44

标签: c++ design-patterns

我已阅读维基百科的decorator design patternthis site的代码示例。

我认为传统继承遵循'is-a'模式,而decorator遵循'has-a'模式。装饰者的调用约定看起来像'皮肤'上的'皮肤'......超过'核心'。 e.g。

I* anXYZ = new Z( new Y( new X( new A ) ) );

如上面的代码示例链接所示。

然而,仍然有一些我不明白的问题:

  1. wiki是什么意思'装饰模式可以用来扩展(装饰)某个对象在运行时'的功能? 'new ...(new ...(new ...))'是一个运行时调用,但很好但是'AwithXYZ anXYZ;'是编译时的继承而且不好?

  2. 从代码示例链接中我可以看到类定义的数量在两个实现中几乎相同。我记得在其他一些设计模式书中,比如“Head first design patterns”。他们使用starbuzz咖啡作为例子,并说传统的继承将导致'类爆炸',因为对于每种咖啡组合,你会想出一个类。

    但在这种情况下,装饰者是不是一样的?如果装饰器类可以使用任何抽象类并对其进行装饰,那么我猜它确实可以防止爆炸,但是从代码示例中,您可以获得确切的类定义,不能少......

  3. 有人会解释吗?

5 个答案:

答案 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 ++,但希望你能得到我来自的地方。

我想到的一个好例子是DbRepositoryCachingDbRepository

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();
    }
}

所以,如果我只使用了继承,我会有DbRepositoryCachingDbRepositoryDbRepository将从数据库中查询; CachingDbRepository将检查其缓存,如果数据不存在,它将查询数据库。所以这里可能有重复的实现。

通过使用装饰器模式,我仍然拥有相同数量的类,但我的CachingDbRepository接受IRepository并调用其GetStuff()来获取来自底层回购的数据它不在缓存中。

所以类的数量是相同的,但是类的使用是相关的。 CachingDbRepo调用传递给它的Repo ...所以它更像是继承的组合。

我觉得何时决定何时只使用继承而不是装饰是主观的。

我希望这会有所帮助。祝你好运!