What is the difference between Strategy pattern and Visitor Pattern?

后端 未结 11 1491
灰色年华
灰色年华 2021-01-29 22:18

I have trouble understanding these two design patterns.

Can you please give me contextual information or an example so I can get a clear idea and be able to map the dif

相关标签:
11条回答
  • 2021-01-29 22:25

    If you have just one single context or element and need to perform different operations on that context, then you can choose Strategy Pattern. This is the 1:M relationship mentioned in above answer. java.util.Comparator is a good example of Strategy design pattern in action. There we can have different sorting strategies for the same collection (context or element).

    On the other hand, say you have multiple elenents all complies to a common contract and need to perform different operations on each of them. For an example consider a car wash usecase where you have body, engine and wheel etc and each of which can be washed using either steam or water. That is a good use of Visitor Pattern. But make sure that your context elements stay intact and never changes. If the elements are going to change say adding a Door element to the Car, then you need to change all the Visitors adding one new method in each of them and violating OCP nature of the pattern. So, this is the M:N relationship stated in the above answer.

    If you are further interested in reading more about the subtle differences between the two Design Patterns, I suggest you reading this article.

    0 讨论(0)
  • 2021-01-29 22:27

    A Strategy pattern is used to expose various algorithms to a standardized interface. A typical example could be a sort utility that would let the user (programmer) choose between various sort algorithms each called via the same interface.

    A Visitor pattern lives at a different level. It details a mechanism with which objects can accept a reference to another object (the visitor) which exposes a predetermined interface that the target object can call upon itself. Of course, different visitors would present the same interface but have different implementations.

    Coming back to our example, a collection of sort algorithms could be implemented either via the Strategy pattern or via the Visitor pattern.

    With the Strategy method, each algorithm presents the same interface and takes arrays of target objects as parameters for example. With the Visitor pattern, it would be the target array that takes the "visiting" algorithm as a parameter. In this case, the target would "accept()" the selected visitor and call its "visit()" method upon invocation of the target's sort method in our example.

    Two sides of the same coin...

    Does this make sense?

    0 讨论(0)
  • 2021-01-29 22:28

    I see strategy pattern as a way to inject a method/strategy into an object, but typically the signature of that method takes some value params and returns a result so it's not coupled with the user of the strategy: From Wikipedia :

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

    Visitor instead is coupled with the user through double dispatch and typically keeps state. Good example here, I'll just copy from there:

    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
    }
    

    As you can see the visitor has state(public int Count) and it operates on a list of know types BlisterPack, Bottle, Jar. So if you want to support a new type you need to change all visitors by adding that type.

    Also it's coupled with the types it operates on because of "visitor.Visit(this);". What would happen if I remove or change the "Items" property form bottle? ... all visitors would fail.

    0 讨论(0)
  • 2021-01-29 22:33

    The strategy pattern is like a 1:many relationship. When there is one type of object and I want to apply multiple operations to it, I use the strategy pattern. For example, if I have a Video class that encapsulates a video clip, I might want to compress it in different ways. So I create a bunch of strategy classes:

    MpegCompression
    AviCompression
    QuickTimeCompression
    

    and so on.

    I think of the visitor pattern as a many:many relationship. Let's say my application grows to to include not just video, but audio clips as well. If I stick with the strategy pattern, I have to duplicate my compression classes-- one for video and one for audio:

    MpegVideoCompression
    MpegAudioCompression
    

    and so on...

    If I switch to the visitor pattern, I do not have to duplicate the strategy classes. I achieve my goal by adding methods:

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

    [UPDATE: with Java] I used the visitor pattern in a Java app. It came out a little different than described above. Here is a Java version for this example.

    // 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) {
        System.out.println("Mpeg video compression");
      }
    
      public void compress(Audio object) {
        ...
      }
    }
    

    And now the interface and class to be visited:

    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);
    }
    
    0 讨论(0)
  • 2021-01-29 22:35

    The defining difference is that the Visitor offers a different behavior for subclasses of the element, using operator overloading. It knows the sort of thing it is working upon, or visiting.

    A Strategy, meanwhile, will hold a consistent interface across all its implementations.

    A visitor is used to allow subparts of an object to use a consistent means of doing something. A strategy is used to allow dependency injection of how to do something.

    So this would be a visitor:

    class LightToucher : IToucher{
        string Touch(Head head){return "touched my head";}
        string Touch(Stomach stomach){return "hehehe!";}
    }
    

    with another one of this type

    class HeavyToucher : IToucher{
       string Touch(Head head){return "I'm knocked out!";}
       string Touch(Stomach stomach){return "oooof you bastard!";}
    }
    

    We have a class that can then use this visitor to do its work, and change based upon it:

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

    So if we do this var person1 = new Person(new LightToucher()); var person2 = new Person(new HeavyToucher());

            person1.Touch(true); //touched my head
            person2.Touch(true);  //knocked me out!
    
    0 讨论(0)
  • 2021-01-29 22:36

    A Visitor is a strategy but with multiple methods and it allows Double dispatch. The Visitor also allows for safe binding between two concrete objects at runtime.

    Note: This is an example written in Java. For example C# introduced the dynamic keyword, therefor the example of double dispatch is not useful in C#.

    Strategy pattern

    Consider the following example and the output:

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

    The Strategy pattern is working fine if there is no direct relationship between the strategy and the subject. For example, we don't want the following to happen:

    ...
        public static void main(String[] args)
        {
            Car porsche = new Porsche();
            porsche.repair(new FerrariRepairStrategy()); //We cannot repair a Porsche as a Ferrari!
        }
    ...
    

    So in this case we can use the visitor pattern.

    Visitor

    The problem

    Consider the code below:

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

    The output is Applying a very specific Porsche repair. The problem is that this line is not abstract, but concrete:

    Porsche porsche = new Porsche();
    

    We want to write it as (or inject an instance of Car in the constructor, we want to apply the Dependency Inversion Principle):

    Car porsche = new Porsche();
    

    But when we change this line, the output will be:

    Applying a very generic and abstract repair
    

    Not what we want!

    The solution; using double dispatch (and the Visitor pattern)

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

    Because of method overloading, there is a concrete binding between the visitor and the subject (Car). There is no way a Porsche can be repaired as a Ferrari, since it uses method overloading. Also we solved the previously explained problem (that we cannot use Dependency Inversion), by implementing this method:

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

    The this reference will return the concrete type of the object, not the abstract (Car) type.

    0 讨论(0)
提交回复
热议问题