我不太理解这两种设计模式。
你可以给我一些上下文信息或示例,这样我就能清楚地了解它们之间的区别并进行比较。
谢谢。
我不太理解这两种设计模式。
你可以给我一些上下文信息或示例,这样我就能清楚地了解它们之间的区别并进行比较。
谢谢。
策略模式就像是一个一对多的关系。当我有一种类型的对象,并且想要对其应用多个操作时,我使用策略模式。例如,如果我有一个Video类封装了一个视频剪辑,我可能想以不同的方式压缩它。所以我创建了一堆策略类:
MpegCompression
AviCompression
QuickTimeCompression
等等等等。
我认为访问者模式就像一个多对多的关系。假设我的应用程序不仅包括视频,还包括音频剪辑,如果我坚持使用策略模式,我必须复制我的压缩类--一个用于视频和一个用于音频:
MpegVideoCompression
MpegAudioCompression
如果我转换到访问者模式,就不必重复策略类。我通过添加方法实现我的目标:
MpegCompressionVisitor::compressVideo(Video object)
MpegCompressionVisitor::compressAudio(Audio object)
[更新:使用Java] 我在一个Java应用程序中使用了访问者模式。它与上面描述的有些不同。这里是该示例的Java版本。
// Visitor interface
interface Compressor {
// Visitor methods
void compress(Video object);
void compress(Audio object);
}
// Visitor implementation
class MpegCompressor implements Compressor {
public void compress(Video object) {
// ...
}
public void compress(Audio object) {
// ...
}
}
现在需要访问的接口和类:
interface Compressible {
void accept(Compressor compressor);
}
class Video implements Compressible {
// If the Compressor is an instance of MpegCompressionVisitor,
// the system prints "Mpeg video compression"
void accept(Compressor compressor) {
compressor.compress(this);
}
compressVideo
和compressAudio
是我的两个visit
函数。Video类将访问compressVideo
,而Audio类将访问compressAudio
。我理解得对吗? - EmptyDatacompress
方法将是您的访问者方法。我会将 MpegCompression
类描述为访问者。Video
对象将被访问。 - ahoffer访问者是一个具有多个方法的策略,并允许双重分派。访问者还允许在运行时两个具体对象之间进行安全绑定。
注意:这是一个Java示例。例如,C#引入了关键字dynamic
,因此双重分派的示例在C#中无用。
考虑以下示例和输出:
package DesignPatterns;
public class CarGarageStrategyDemo
{
public static interface RepairStrategy
{
public void repair(Car car);
}
public static interface Car
{
public String getName();
public void repair(RepairStrategy repairStrategy);
}
public static class PorscheRepairStrategy implements RepairStrategy
{
@Override
public void repair(Car car) {
System.out.println("Repairing " + car.getName() + " with the Porsche repair strategy");
}
}
public static class FerrariRepairStrategy implements RepairStrategy
{
@Override
public void repair(Car car) {
System.out.println("Repairing " + car.getName() + " with the Ferrari repair strategy");
}
}
public static class Porsche implements Car
{
public String getName()
{
return "Porsche";
}
@Override
public void repair(RepairStrategy repairStrategy) {
repairStrategy.repair(this);
}
}
public static void main(String[] args)
{
Car porsche = new Porsche();
porsche.repair(new PorscheRepairStrategy()); //Repairing Porsche with the porsche repair strategy
}
}
策略模式
在策略和主题之间没有直接关系时能够正常工作。例如,我们不希望发生以下情况:
...
public static void main(String[] args)
{
Car porsche = new Porsche();
porsche.repair(new FerrariRepairStrategy()); //We cannot repair a Porsche as a Ferrari!
}
...
因此,在这种情况下,我们可以使用访问者模式。
考虑以下代码:
public class CarGarageVisitorProblem
{
public static interface Car
{
public String getName();
}
public static class Porsche implements Car
{
public String getName()
{
return "Porsche";
}
}
public static class Ferrari implements Car
{
public String getName()
{
return "Ferrari";
}
}
public void repair(Car car)
{
System.out.println("Applying a very generic and abstract repair");
}
public void repair(Porsche car)
{
System.out.println("Applying a very specific Porsche repair");
}
public void repair(Ferrari car)
{
System.out.println("Applying a very specific Ferrari repair");
}
public static void main(String[] args)
{
CarGarageVisitorProblem garage = new CarGarageVisitorProblem();
Porsche porsche = new Porsche();
garage.repair(porsche); //Applying a very specific Porsche repair
}
}
输出结果为 应用非常具体的保时捷修复方法
。问题在于这条线不是抽象的,而是具体的:
Porsche porsche = new Porsche();
我们想要将其编写成(或在构造函数中注入Car的实例,我们想要应用“依赖倒转原则”):
Car porsche = new Porsche();
但是当我们改变这一行时,输出将会是:
Applying a very generic and abstract repair
不是我们想要的!
package DesignPatterns;
public class CarGarageVisitorExample
{
public static interface Car
{
public String getName();
public void repair(RepairVisitorInterface repairVisitor);
}
public static class Porsche implements Car
{
public String getName()
{
return "Porsche";
}
public void repair(RepairVisitorInterface repairVisitor)
{
repairVisitor.repair(this);
}
}
public static class Ferrari implements Car
{
public String getName()
{
return "Ferrari";
}
public void repair(RepairVisitorInterface repairVisitor)
{
repairVisitor.repair(this);
}
}
public static interface RepairVisitorInterface
{
public void repair(Car car);
public void repair(Porsche car);
public void repair(Ferrari car);
}
public static class RepairVisitor implements RepairVisitorInterface
{
public void repair(Car car)
{
System.out.println("Applying a very generic and abstract repair");
}
public void repair(Porsche car)
{
System.out.println("Applying a very specific Porsche repair");
}
public void repair(Ferrari car)
{
System.out.println("Applying a very specific Ferrari repair");
}
}
public static void main(String[] args)
{
CarGarageVisitor garage = new CarGarageVisitor();
Car porsche = new Porsche();
porsche.repair(new RepairVisitor()); //Applying a very specific Porsche repair
}
}
由于方法重载,访问者和主题(Car)之间存在具体的绑定关系。 由于使用了方法重载,没有办法像修复法拉利那样修复保时捷。此外,我们通过实现此方法解决了先前解释的问题(即无法使用依赖反转
)。public void repair(RepairVisitorInterface repairVisitor)
{
repairVisitor.repair(this);
}
this
引用将返回对象的具体类型,而不是抽象类型(Car)。
Car porsche = new Porsche(new CarGarageVisitor());
porsche.repair();
- Douma区别在于 Visitor 通过运算符重载为元素的子类提供了不同的行为。它知道正在处理或访问的对象类型。
与此相反,策略模式在所有实现中都将保持一致的接口。
访问者用于允许一个对象的子部分使用一种一致的方法来做某事。策略用于允许依赖注入如何做某事。
因此,这将是一个访问者:
class LightToucher : IToucher{
string Touch(Head head){return "touched my head";}
string Touch(Stomach stomach){return "hehehe!";}
}
使用另一种相同类型的方法
class HeavyToucher : IToucher{
string Touch(Head head){return "I'm knocked out!";}
string Touch(Stomach stomach){return "oooof you bastard!";}
}
class Person{
IToucher visitor;
Head head;
Stomach stomach;
public Person(IToucher toucher)
{
visitor = toucher;
//assume we have head and stomach
}
public string Touch(bool aboveWaist)
{
if(aboveWaist)
{
visitor.Touch(head);
}
else
{
visitor.Touch(stomach);
}
}
}
如果我们这样做:
var person1 = new Person(new LightToucher());
var person2 = new Person(new HeavyToucher());
其中,person1使用了轻触摸器,person2使用了重触摸器。
person1.Touch(true); //touched my head
person2.Touch(true); //knocked me out!
begin(obj); // <-- visitor
obj.begin(); // <-- strategy
我认为策略模式是一种将方法/策略注入对象的方式,但通常该方法的签名需要一些值参数并返回结果,因此它与策略的使用者没有耦合关系:
来自维基百科:
class Minus : ICalculateStrategy {
public int Calculate(int value1, int value2) {
return value1 - value2;
}
}
双重分派将访问者与用户耦合在一起,并通常保留状态。
这里有一个很好的例子(点击这里),我会从那里复制内容:
public class BlisterPack
{
// Pairs so x2
public int TabletPairs { get; set; }
}
public class Bottle
{
// Unsigned
public uint Items { get; set; }
}
public class Jar
{
// Signed
public int Pieces { get; set; }
}
public class PillCountVisitor : IVisitor
{
public int Count { get; private set; }
#region IVisitor Members
public void Visit(BlisterPack blisterPack)
{
Count += blisterPack.TabletPairs * 2;
}
public void Visit(Bottle bottle)
{
Count += (int) bottle.Items;
}
public void Visit(Jar jar)
{
Count += jar.Pieces;
}
#endregion
}
public class BlisterPack : IAcceptor
{
public int TabletPairs { get; set; }
#region IAcceptor Members
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
#endregion
}
正如您所看到的,访问者有状态(public int Count),并且它对BlisterPack、Bottle和Jar等已知类型的列表进行操作。因此,如果您想要支持新类型,您需要通过添加该类型来更改所有访问者。
此外,由于"visitor.Visit(this)",它与它所操作的类型耦合在一起。如果我从瓶子中删除或更改"Items"属性会发生什么呢?...所有访问者都会失败。
对我来说,第二张图看起来像是访问者模式...因为对于策略模式,类包含的数据结构往往只有一个,没有子类(或者子类保持相同的行为)。策略是针对相同结构上的不同操作。
如果您只有一个单一的上下文或元素,并且需要对该上下文执行不同的操作,则可以选择策略模式
。这是上面答案中提到的1:M
关系
。 java.util.Comparator
是策略设计模式在实际应用中的一个很好的例子。我们可以为相同的集合(上下文或元素)设置不同的排序策略。
另一方面,假设您有多个元素都符合共同的契约,并且需要对每个元素执行不同的操作。例如,考虑一个洗车用例,其中有车身、发动机和轮等部件,每个部件都可以使用蒸汽或水进行清洗。这是访问者模式
的一个很好的应用场景。但请确保您的上下文元素保持完整且永远不会更改。如果元素将要更改,例如添加一个Door
元素到Car
中,则需要更改所有访问者,在每个访问者中添加一个新方法并违反模式的OCP
性质。因此,这是上面答案中所述的M:N
关系。
设计模式
间微妙的差异更深入地感兴趣,我建议你阅读this article。