访问者模式和策略模式有什么区别?

13

我学习了这两种模式,但并不理解它们之间的区别。

我不知道在何时何地使用这些模式。

有人能解释一下它们的差异和使用案例吗?


3
这是一种与不同模式相关的好方法:http://www.cours.polymtl.ca/inf3700/divers/nonSoftwareExample/patexamples.html - jco.owens
3
链接已经失效了,你能否更新一下 @jco.owens? - Joe
6个回答

25

主要区别在于策略模式封装了一组单一相关行为,而访问者模式则封装了多个这样的组。

  • 当您需要封装一个行为时,应该使用策略模式 - 如果您有一组算法并且需要在运行时选择其中之一,则应使用策略模式。这非常常见:每次您面向接口编程时都会发生。
  • 应该使用访问者模式来实现双重分派 - 如果您有一组需要与多个对象关联的虚拟算法。这种情况很少见,部分原因是它难以实现。

2
+1,您可以更多地提供示例和详细信息,但这是一个很好的极客简洁回答。 - Scorpion
@dasblinkenlight,您能详细说明一下“如果您有一组算法需要在与多个对象相关时进行虚拟化”的含义吗? - Geek
@Geek 经典例子是表达式树。一方面,你有一组遵循组合模式的类(基础表达式、常量表达式、二进制表达式、函数调用等)。另一方面,你有一组实现算法的类(转换为XML、打印到文本文件、评估等)。因此,你要调用的方法取决于表达式和算法的子类型,因此行为在多个类方面是虚拟的。官方名称为双重分派 - Sergey Kalinichenko
一旦你知道如何实现,它并不难。 它只涉及到各种方式相互关联的类。 一旦你编写了它们,除了使用它们或添加到它们之外,就没有更多要做的了。 - Dennis
我同意你的想法,但在我看来有点过于限制了。许多开发人员也经常使用访问者模式将单个类中的关注点分离。因此,“双重分派”不是应用访问者模式的唯一/必要条件。 - stdout

5

访问者模式的意图:

表示要对对象结构的元素执行的操作。访问者允许您定义一个新的操作,而不必更改它所操作的元素的类。

如果需要,使用访问者模式:

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

尽管访问者模式提供了在Object中添加新操作而不更改现有代码的灵活性,但这种灵活性也带来了缺点。

如果添加了新的可访问对象,则需要更改Visitor和ConcreteVisitor类中的代码。有一种解决此问题的方法:使用反射,这会影响性能。

有关详细信息,请参阅oodesign文章sourcemaking文章

策略模式的意图:

定义一组算法,将每个算法封装起来,并使它们可以互换。策略让算法独立于使用它的客户端而变化。

策略允许您更改对象的内部实现。

有关详细信息,请参阅下面的SE问题:

策略模式的实际应用举例


3
访问者模式用于遍历对象层次结构并提供一些功能,如打印或报告等。我使用它来编写多个访问者(每个格式一个),以便为对象层次结构提供不同的格式(文本/HTML)打印。层次结构中的对象是可访问的。
策略模式用于根据输入选择特定的逻辑路径。一个经典的例子是身份验证过滤器,在其中基于HTTP头Authorization中的值,选择不同的身份验证策略,如NTLM/Negotiate/Basic,并运行它们。该过滤器将持有对AuthenticationStrategy接口的引用,根据传入的请求,选择特定的身份验证策略并将其分配给此引用,随后的代码不需要知道正在使用的确切策略。

1
策略模式的一个简单例子 - 使用不同的Comparator实现来以不同的方式对列表进行排序? - Scorpion

3
访问者模式是当你有一组类,需要为每个类添加新功能,但不想修改这些类本身(或希望将所有新功能定义在一个地方,即访问者)时使用的模式。

策略模式是当你有一组类需要执行一些操作来正常工作(例如对它们包含的对象进行排序),但你希望客户端或依赖注入告诉它们以哪种方式进行操作时使用的模式。

0
除了上述的行为差异之外,我在项目中还遇到了依赖关系和用例方面的差异,例如:
例如,访问者模式知道具体类。因此,随着将新的具体类添加到层次结构中,您将更加灵活,但代价是需要更改访问者代码。而在策略模式中则没有这样的情况。在这种情况下,如果您只有方法来返回给定输入的某些输出,则策略模式更加合适,无论上下文如何。
此外,访问者模式还用于实现SOLID原则中的SRP,以分离关注点。

0
假设你正在构建一个现金收银应用程序,用于生成收据。并且假设您想要:
1. 确保不同种类的物品(书籍、水果、肉类、洗漱用品等)被不同地处理
2. 将实际价格计算逻辑与物品定义分开
3. 确保如果商店开始销售完全新的东西(比如按长度计费的布料),您不必更改太多代码 Visitor 可以是一个类,根据正在处理的物品种类定义不同种类的计算。它是一个服务,将价格计算的差异从物品层次结构中移开。
enter image description here 其中 getPrice 的主体可能如下所示:
getPrice(Calculation c) {
    return c.calculate(this);  // <-- visitor.visit( specific implementation )
}

ShoppingCart 中,你将会这样做:

calc = getPriceCalculator()
foreach item in items:
    totalPrice += item.getPrice(calc)

想象这样的情境:在黑色星期五,你想做一些疯狂的折扣,所有图书 7 折,所有水果 5 折。你可以轻松地实现类似以下的操作:

BlackFridayCalculator extends PriceCalculator {
    calculate(Book b) { return 0.3 * parent.calculate(b) }
    calculate(Fruit f) { return 0.5 * parent.calculate(f) }
}

// and in getPriceCalculator:
return (black friday time) ? new BlackFridayCalculator() : new PriceCalculator();

策略 可以有不同种类的计算(策略)层次结构,每个项目都可以定义应该使用哪种计算(策略)。

enter image description here

现在有许多方法来定义哪个项目应该使用哪个计算方法。最直接的方法是给Item方法getCalculator,让每个项目选择它需要的计算方法。
这可能会稍微不太灵活 - 因为每个项目都需要预定义的计算器来使用。但是想象一下这种情况:商店的所有者决定菠萝和西瓜应该按件出售 - 我们可以轻松地让Fruit默认使用WeightCalculator并创建一个将按数量出售的水果子集。

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