我认为理解装饰器和访问者设计模式的意图是很重要的。
虽然我可以列出以下区别:
- 装饰器适用于对象,访问者适用于组合结构。
- 装饰器是结构设计模式,访问者是行为设计模式。
但当我深入思考时,我无法说服自己这两者之间的真正区别。
我认为理解装饰器和访问者设计模式的意图是很重要的。
虽然我可以列出以下区别:
但当我深入思考时,我无法说服自己这两者之间的真正区别。
实际上,它们是完全不同的!
当您想使用一些新的、更或多或少透明的功能(如验证或缓存)增强现有对象时,可以使用Decorator。请参见此处的示例:Should I extend ArrayList to add attributes that isn't null?
另一方面,当您拥有一个类层次结构并希望根据具体类型运行不同的方法但避免使用instanceof
或typeof
操作时,可以使用Visitor。请参见现实生活中的例子:Is This Use of the "instanceof" Operator Considered Bad Design?
装饰器作用于对象,访问者作用于复合结构。
Visitor作用于继承层次结构,Composite是另一种GoF设计模式。
装饰器是结构型设计模式,访问者是行为型设计模式。
没错,但这并不能真正帮助理解它们是如何工作的?
设计模式的分类不应该基于实现差异,而是根据何时使用它们。
它们用于绝对不同的目的:
if (a is ConcreteClass1) {...} else if (a is ConcreterClass2) {...} 这样的代码,而不编写虚拟方法。
由于这种差异,使用装饰器的客户端代码调用与基本组件类接口上定义的相同方法,它现在只是被“装饰”了额外的功能,而使用访问者的客户端调用一些通用的“接受”方法并将访问者发送到它。
我认为装饰器可以避免继承并扩展类,这是面向对象编程的一般原则,更喜欢聚合而不是继承,尽管你确实以某种方式继承。这是一个过于简单化的例子:
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
}
}
装饰者模式可以静态地或在某些情况下在运行时独立于同一类的其他实例扩展(装饰)某个对象的功能,前提是在设计时完成了一些基础工作。
何时使用装饰者模式?
相关文章:
访问者模式是一种将算法与其操作的对象结构分离的方法。这种分离的一个实际结果是能够向现有的对象结构中添加新的操作而不修改那些结构。这是遵循开闭原则的一种方式。
何时使用访问者模式?
相关文章:
有用的链接:
sourcemaking 装饰器文章
oodesign 访问者文章
sourcemaking 访问者文章
是的,它们都在运行时向现有系统添加一些功能,并试图对动态变化做出更少的反应(在良好的意义上),但存在一些差异。
访问者主要是为了尊重OCP(有时也是SRP),使系统更加灵活。随着程序的发展,您可以添加任何访问者,而无需更改现有系统。但是,在此之前,您需要以这样的方式设计系统。您不能将新的访问者类(或模式)添加到已经运行的系统中,并期望它能够在不重新编译、重新测试或其他方面工作。
另一方面,您可以使用装饰器来丰富现有系统的功能,方法是将抽象基类(您已经拥有的)包装在装饰器中,并提供您的增强功能作为单独的对象,以便您按需创建。此外,从语义上讲,装饰器更多地指的是某物的外观。
哪个更好?在我看来,回答这个问题可能更有帮助。对我来说,我不喜欢装饰器使用基类的方式。它同时使用了继承和聚合。如果你需要更改这个(被包装)类,你最终会重新编译整个层次结构/模块。但是它很方便,因为你可以在设计时之后更改行为。另一方面,在访问者模式中,我不喜欢在访问者实现中知道每个具体类型的想法。当你添加一个新的基类类型时,你还需要去改变访问者类来添加它。但是当你需要向现有系统注入代码而不改变结构或者需要在一个类中分离关注点(单用户Resp)时,它是有用的。
最后,什么使访问者比常规继承更好?这取决于情况。使用继承会使你更加依赖接口签名。使用访问者会使你的访问者类依赖于具体类。更不用说使用访问者添加更多的行为而不改变现有模块签名,而不是在现有类中实现新接口。
我认为装饰器模式的应用场景是在运行时给对象添加功能。通过将对象包装在一个装饰器类中,可以扩展其方法并在程序运行时向对象添加行为。
对于访问者模式,我喜欢在需要对“一组”相同类型的对象执行操作并收集信息时使用它。比如说,我有10个具体的蔬菜类,我想知道所有10个蔬菜的总价,我可以使用访问者模式来“访问”每个蔬菜对象,在迭代结束时得到总价。当然,您也可以将该模式用作将某些操作与对象分离的一种方式。