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

时间:2011-12-29 07:59:31

标签: oop design-patterns

我无法理解这两种设计模式。

你能否给我一些背景信息或一个例子,这样我就可以得到一个清晰的想法,并能够绘制两者之间的差异。

感谢。

enter image description here

enter image description here

11 个答案:

答案 0 :(得分:77)

以下是我的看法。 策略模式就像 1:很多关系。当有一种类型的对象并且我想对它应用多个操作时,我使用策略模式。例如,如果我有一个封装视频剪辑的Video类,我可能想以不同的方式压缩它。所以我创建了一堆策略类:

MpegCompression
AviCompression
QuickTimeCompression

等等。

我认为访客模式很多:很多关系。假设我的应用程序不仅包括视频,还包括音频剪辑。如果我坚持使用策略模式,我必须复制我的压缩类 - 一个用于视频,一个用于音频:

MpegVideoCompression
MpegAudioCompression

依旧......

如果我切换到访客模式,我不必复制策略类。我通过添加方法来实现我的目标:

MpegCompression::compressVideo(Video object)    
MpegCompression::compressAudio(Audio object)

答案 1 :(得分:11)

策略模式用于将各种算法暴露给标准化接口。一个典型的例子可以是一个排序实用程序,它允许用户(程序员)在各种排序算法之间进行选择,每个排序算法都通过相同的接口调用。

访客模式生活在不同的层面。它详细说明了一种机制,通过该机制,对象可以接受对另一个对象(访问者)的引用,该对象公开了目标对象可以调用的预定接口。当然,不同的访问者会呈现相同的界面,但具有不同的实现。

回到我们的例子,可以通过策略模式或访问者模式实现排序算法的集合。

使用策略方法,每个算法都呈现相同的接口,并将目标对象的数组作为参数。使用访问者模式,它将是将“访问”算法作为参数的目标数组。在这种情况下,目标将“接受()”选定的访问者,并在我们的示例中调用目标的排序方法时调用其“visit()”方法。

同一枚硬币的两面......

这有意义吗?

答案 2 :(得分:5)

访客就像一夜情 - 你在调用accept函数时创建它然后它们被分开并且访问者可以从内存中清除,它不会占用使用它的类的任何空间

战略就像一场婚姻 - 你创造了一个对象,它生活在一个使用它的类中,占用记忆,有一个房间并在早上让自己变成咖啡:)。 当然,他们可以离婚并转到另一个班级,但该班级也会生活在其所有者的背景下。

希望它能帮助你记住:)

答案 3 :(得分:5)

访问者是一种策略,但是具有多种方法,并且它允许Double调度。访客还允许在运行时在两个具体对象之间进行安全绑定。

注意:这是用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
    }
}

如果策略与主题之间没有直接关系,则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
    }
}

输出为Applying a very specific Porsche repair。 问题在于,这条线不是抽象的,而是具体的:

Porsche porsche = new Porsche();

我们想将其编写为(或在构造函数中注入Car的实例,我们要应用Dependency Inversion Principle):

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
    }
}

由于方法重载,访问者与主题(汽车)之间存在具体的约束。由于保时捷使用方法重载,因此无法将其作为法拉利进行维修。我们还通过实现以下方法解决了先前说明的问题(我们不能使用Dependency Inversion):

public void repair(RepairVisitorInterface repairVisitor)
{
    repairVisitor.repair(this);
}

this引用将返回对象的具体类型,而不是抽象(Car)类型。

答案 4 :(得分:2)

定义的区别在于,使用运算符重载,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.Touch(true); //touched my head
        person2.Touch(true);  //knocked me out!

答案 5 :(得分:1)

我将策略模式视为将方法/策略注入对象的一种方式,但通常该方法的签名需要一些值参数并返回结果,因此它不会与策略的用户耦合: 来自Wikipedia

class Minus : ICalculateStrategy {
    public int Calculate(int value1, int value2) {
        return value1 - value2;
    }
}

访问者通过双重调度与用户耦合,通常保持状态。 好的例子here,我只是从那里复制:

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();",它与其操作的类型相结合。如果我删除或更改"项目"会发生什么?物业形式瓶? ......所有访客都会失败。

答案 6 :(得分:0)

似乎第二个图是访问者模式给我...因为对于策略模式,类包含的数据结构往往只有一个,没有子类(或者子类保持这部分的相同行为)。该策略适用于同一结构的不同操作。

答案 7 :(得分:0)

如果您只有一个上下文或元素,并且需要对该上下文执行不同的操作,那么您可以选择Strategy Pattern。这是上面回答中提到的1:M relationshipjava.util.Comparator是战略设计模式的一个很好的例子。在那里,我们可以为同一个集合(上下文或元素)提供不同的排序策略。

另一方面,假设您有多个elenents都符合共同合同,并且需要对每个elenents执行不同的操作。例如,考虑使用车身,发动机和车轮等的洗车用品,每个都可以使用蒸汽或水清洗。这是Visitor Pattern的好用。但请确保您的上下文元素保持不变并且永远不会更改。如果元素将要更改,请说Door元素添加到Car,那么您需要更改所有访问者,在每个访问者中添加一个新方法并违反OCP性质图案。所以,这是上述答案中陈述的M:N关系。

如果您对进一步了解这两个Design Patterns之间的微妙差异感兴趣,建议您阅读this article

答案 8 :(得分:0)

我会尽力回答最短的问题。

这两种模式是相辅相成的:例如,您可以使用访问者来更改图的所有节点上的策略。

答案 9 :(得分:0)

如果我们从GoF书中查看这两种模式的UML,就会发现它们完全不同。

访问者

Visitor UML


策略

Strategy UML


图中有一些重要的区别。

  • 策略基于组成。访客没有构成关系。
  • 策略基于对接口的编码。访客基于对实现的编码。
  • 策略以多种方式实现一项操作。访客执行多项操作。

UML本身并不能捕捉驱动这些模式的不同动机。

  • 策略是没有一流功能的语言的面向对象解决方案。由于现代语言采用闭包或lambda,因此对策略模式的需求减少了。
  • Visitor是针对语言的面向对象解决方案,无需模式匹配或多次分派。随着现代语言采用这些功能,访客也变得过时。

答案 10 :(得分:-6)

他们的不同之处在于:

  1. 动机
  2. 意图
  3. 实施
  4. 不确定通过比较两个不同的内容获得了什么,但将StrategyVisitor进行了比较。

    两者有什么相同之处可以看出他们的差异?