装饰器设计模式和访问者设计模式的区别

30

我认为理解装饰器和访问者设计模式的意图是很重要的。

虽然我可以列出以下区别:

  1. 装饰器适用于对象,访问者适用于组合结构。
  2. 装饰器是结构设计模式,访问者是行为设计模式。

但当我深入思考时,我无法说服自己这两者之间的真正区别。

8个回答

35

实际上,它们是完全不同的!

当您想使用一些新的、更或多或少透明的功能(如验证或缓存)增强现有对象时,可以使用Decorator。请参见此处的示例:Should I extend ArrayList to add attributes that isn't null?

另一方面,当您拥有一个类层次结构并希望根据具体类型运行不同的方法但避免使用instanceoftypeof操作时,可以使用Visitor。请参见现实生活中的例子:Is This Use of the "instanceof" Operator Considered Bad Design?

装饰器作用于对象,访问者作用于复合结构。

Visitor作用于继承层次结构,Composite是另一种GoF设计模式。

装饰器是结构型设计模式,访问者是行为型设计模式。

没错,但这并不能真正帮助理解它们是如何工作的?

另请参阅


14

设计模式的分类不应该基于实现差异,而是根据何时使用它们。

它们用于绝对不同的目的:

  • 当您想要通过提供装饰其他对象的单个元素来动态丰富对象功能以使其实际添加一些行为时,将使用装饰器(实际上这是一种结构型模式,因为它改变了您正在使用的对象的结构)。
  • 当您想要将算法与其所使用的对象分离时,将使用访问者。您拥有此访问者,将其传递给许多不同的对象(通常是层次结构),这些对象被称为接受访问者。此访问者会根据其正在访问的对象类型执行特定的操作。通过这种方式,您可以让访问者在不需要在对象本身中指定这些操作的情况下对特定对象进行任意操作(这就是为什么它是行为型)。 这类似于在对象本身中未定义的抽象方法。

11
他们都在不修改原始类的情况下为现有对象“添加功能”。区别是: 使用装饰器,您可以添加包装此对象具有的基本功能的功能(例如,除执行某些基本操作外,还将其写入日志,在将文件写入磁盘后还加密它)。 这还允许我们创建不同的装饰器组合,而不必针对每种可能的情况进行子类化。 使用访问者,您可以添加完全新的行为,而无需将其定义为基本组件类本身的一部分(甚至不是基本功能的包装),例如单一责任原则、开闭原则等。 当该行为在同一类型的不同子类之间不同时,它特别有用(如果没有复杂的子类结构,只有一个类,您可以创建一个新类并通过组合包含原始类,并仍然实现不影响或修改原始类的目标)。这样,您就可以避免像if (a is ConcreteClass1) {...} else if (a is ConcreterClass2) {...} 这样的代码,而不编写虚拟方法。
由于这种差异,使用装饰器的客户端代码调用与基本组件类接口上定义的相同方法,它现在只是被“装饰”了额外的功能,而使用访问者的客户端调用一些通用的“接受”方法并将访问者发送到它。

优秀的回答。从这个意义上说,我们可以说使用访问者模式时不会向装饰对象添加新方法,对吗? - flyer88

5

我认为装饰器可以避免继承并扩展类,这是面向对象编程的一般原则,更喜欢聚合而不是继承,尽管你确实以某种方式继承。这是一个过于简单化的例子:

abstract class Chef{
  public abstract void Prepare();
}

class CookieMaker:Chef{         //Concrete class
   public override void Prepare()
    {
        //Bake in Oven
     }
 }

  // Decorator class
 // This chef adds chocolate topping to everything
 class ChocoChef:Chef{  

     public ChocoChef(Chef mychef)
     {
        this.chef = mychef; 
     }

     public override void Prepare()
     {
           // Add chocolate topping first
           chef.Prepare()
     } 
 }

出于篇幅考虑,我删减了一些细节。例如,您可以抽象出一个厨师,添加任何种类的配料,然后ChocoChef就成为其具体类。现在,无论你要准备什么,ChocoChef都会添加巧克力配料。因此,现在您可以通过将相应的厨师传递给其构造函数来制作巧克力饼干或巧克力蛋糕。另一方面,访问者是针对对象进行操作,并根据正在访问的对象做出决策。

class Student{
     // Different visitors visit each student object using this method
     // like prize distributor or uniform inspector
     public Accept(IVisitor v)
     {
         v.Visit(this)
     }
}

 // Visitor visits all student OBJECTS
class PrizeDistributor:IVisitor{
     public override void Visit(Student s)
     {
           //  if(s has scored 100)
           // Award prize to s
     }
}

5
我理解的方式是,访问者代表我们可能希望对某个对象采取的行动,但这些行动并不一定是对象固有的,并且它们在关系上更具水平性。例如,我可以为汽车“做营销推广”,但我不会为汽车对象编写一个“createMarketingPitch”函数,因为这将是在我的汽车对象上创建许多许多函数的一个滑坡。
另一方面,装饰器是一种在现有对象之上添加功能的模式,这是一种垂直关系,修改了当其正常功能被调用时对象的行为。此外,访问者被编码为与对象类一起工作,而装饰器可以分配给特定实例的对象,以便相同类型的不同实例之间的行为不同。

+1 对于描述“水平关系”的访问者模式的解释,这帮助我形象化地理解了何时以及为什么要使用它。 - Ryan Pelletier

2

装饰者模式

装饰者模式可以静态地或在某些情况下在运行时独立于同一类的其他实例扩展(装饰)某个对象的功能,前提是在设计时完成了一些基础工作。

何时使用装饰者模式?

  1. 对象责任和行为应该动态添加/删除
  2. 具体实现应该与责任和行为分离
  3. 当子类过多时,动态添加/删除责任成本太高

相关文章:

何时使用装饰者模式?

访问者模式:

访问者模式是一种将算法与其操作的对象结构分离的方法。这种分离的一个实际结果是能够向现有的对象结构中添加新的操作而不修改那些结构。这是遵循开闭原则的一种方式。

何时使用访问者模式?

  1. 需要对结构中不同类型的对象执行类似操作。
  2. 需要执行许多不同且无关的操作。它将操作与对象结构分离。
  3. 需要添加新的操作而无需改变对象结构。
  4. 将相关操作聚集到单个类中,而不是强制更改或派生类。
  5. 为类库添加函数,而您既没有源代码也不能更改源代码。

相关文章:

何时使用访问者设计模式?

有用的链接:

sourcemaking 装饰器文章

oodesign 访问者文章

sourcemaking 访问者文章


0

是的,它们都在运行时向现有系统添加一些功能,并试图对动态变化做出更少的反应(在良好的意义上),但存在一些差异。

访问者主要是为了尊重OCP(有时也是SRP),使系统更加灵活。随着程序的发展,您可以添加任何访问者,而无需更改现有系统。但是,在此之前,您需要以这样的方式设计系统。您不能将新的访问者类(或模式)添加到已经运行的系统中,并期望它能够在不重新编译、重新测试或其他方面工作。

另一方面,您可以使用装饰器来丰富现有系统的功能,方法是将抽象基类(您已经拥有的)包装在装饰器中,并提供您的增强功能作为单独的对象,以便您按需创建。此外,从语义上讲,装饰器更多地指的是某物的外观。

哪个更好?在我看来,回答这个问题可能更有帮助。对我来说,我不喜欢装饰器使用基类的方式。它同时使用了继承和聚合。如果你需要更改这个(被包装)类,你最终会重新编译整个层次结构/模块。但是它很方便,因为你可以在设计时之后更改行为。另一方面,在访问者模式中,我不喜欢在访问者实现中知道每个具体类型的想法。当你添加一个新的基类类型时,你还需要去改变访问者类来添加它。但是当你需要向现有系统注入代码而不改变结构或者需要在一个类中分离关注点(单用户Resp)时,它是有用的。

最后,什么使访问者比常规继承更好?这取决于情况。使用继承会使你更加依赖接口签名。使用访问者会使你的访问者类依赖于具体类。更不用说使用访问者添加更多的行为而不改变现有模块签名,而不是在现有类中实现新接口。


0

我认为装饰器模式的应用场景是在运行时给对象添加功能。通过将对象包装在一个装饰器类中,可以扩展其方法并在程序运行时向对象添加行为。

对于访问者模式,我喜欢在需要对“一组”相同类型的对象执行操作并收集信息时使用它。比如说,我有10个具体的蔬菜类,我想知道所有10个蔬菜的总价,我可以使用访问者模式来“访问”每个蔬菜对象,在迭代结束时得到总价。当然,您也可以将该模式用作将某些操作与对象分离的一种方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接