【问题标题】:Decorator design pattern vs. inheritance?装饰器设计模式与继承?
【发布时间】:2012-09-04 23:33:53
【问题描述】:

我已经阅读了来自 Wikipedia 的 decorator design pattern,以及来自 this site 的代码示例。

我看到传统继承遵循“is-a”模式,而装饰器遵循“has-a”模式。装饰器的调用约定看起来像是在“皮肤”之上的“皮肤”……在“核心”之上。例如

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

如上述代码示例链接所示。

但是还有几个问题我不明白:

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

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

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

谁能解释一下?

【问题讨论】:

    标签: c++ design-patterns


    【解决方案1】:

    让我们以一些抽象流为例,假设您想为它们提供加密和压缩服务。

    你有装饰器(伪代码):

    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 个带有继承的类。现在让我们添加更多服务,例如过滤和裁剪。使用装饰器,您只需要另外 2 个类来支持所有可能的场景,例如过滤->剪辑->压缩->加密。 使用继承,您需要为每个组合提供一个类,因此您最终会得到数十个类。

    【讨论】:

    • 谢谢兹德斯拉夫。关于如何使用装饰器并且不会引起爆炸的一个很好的解释!
    • 非常好的答案!!真不错
    • 我们可以说压缩加密流不是一个好主意,但这不是这篇文章的重点,不是吗。很好的答案。
    【解决方案2】:

    倒序:

    2) 假设有 10 个不同的独立扩展,在运行时可能需要它们的任意组合,10 个装饰器类就可以完成这项工作。要通过继承涵盖所有可能性,您需要 1024 个子类。而且没有办法绕过大量的代码冗余。

    1) 假设您在运行时有 1024 个子类可供选择。尝试勾勒出需要的代码。请记住,您可能无法指定选择或拒绝选项的顺序。还要记住,在扩展实例之前,您可能需要使用一段时间。来吧,试试。相比之下,用装饰器做这件事是微不足道的。

    【讨论】:

    • *1023,以防顺序无关紧要。
    【解决方案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++ 的人

    【讨论】:

    • 感谢 vossad01。您关于“向实例化对象添加功能”的评论非常好!
    • +1 表示“一个不能使用继承的例子是当你想向实例化对象添加功能时。”
    【解决方案4】:

    要解决您问题的第二部分(这可能反过来解决您的第一部分),使用装饰器方法您可以访问相同数量的组合,但 不必编写它们。如果你有 3 层装饰器,每层有 5 个选项,你有5*5*5 可能的类来定义使用继承。使用你需要的装饰器方法 15.

    【讨论】:

      【解决方案5】:

      首先,我是一名 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...所以它更像是组合而不是继承。

      我发现何时决定何时使用继承而不是装饰是主观的。

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

      【讨论】:

        猜你喜欢
        • 2011-09-05
        • 1970-01-01
        • 2012-07-24
        • 2021-07-18
        • 2011-09-18
        • 1970-01-01
        • 2010-10-05
        • 1970-01-01
        • 2017-08-01
        相关资源
        最近更新 更多