访客模式的替代方案?

时间:2009-06-12 10:04:42

标签: oop design-patterns visitor

我正在寻找访客模式的替代方案。让我只关注模式的几个相关方面,同时跳过不重要的细节。我将使用一个Shape示例(抱歉!):

  1. 您有一个实现IShape接口的对象层次结构
  2. 您要对层次结构中的所有对象执行许多全局操作,例如Draw,WriteToXml等......
  3. 很容易直接潜入并向IShape界面添加Draw()和WriteToXml()方法。这不一定是件好事 - 只要你想添加一个要在所有形状上执行的新操作,每个必须更改的每个IShape派生类
  4. 为每个操作实现访问者,即Draw访问者或WirteToXml访问者在一个类中封装该操作的所有代码。然后,添加新操作就是创建一个新的访问者类,该类对所有类型的IShape执行操作
  5. 当你需要添加一个新的IShape派生类时,你基本上遇到了与3中相同的问题 - 必须更改所有访问者类以添加一个方法来处理新的IShape派生类型
  6. 您阅读有关访客模式的大多数地方表明第5点几乎是模式工作的主要标准,我完全同意。如果修改了IShape派生类的数量,那么这可能是一种非常优雅的方法。

    所以,问题是当添加一个新的IShape派生类时 - 每个访问者实现需要添加一个新方法来处理该类。这充其量是令人不愉快的,在最坏的情况下是不可能的,并且表明这种模式并非真正用于应对这种变化。

    所以,问题是有没有人遇到过处理这种情况的替代方法?

8 个答案:

答案 0 :(得分:15)

您可能想查看Strategy pattern。这仍然可以让您分离关注点,同时仍然可以添加新功能,而无需更改层次结构中的每个类。

class AbstractShape
{
    IXmlWriter _xmlWriter = null;
    IShapeDrawer _shapeDrawer = null;

    public AbstractShape(IXmlWriter xmlWriter, 
                IShapeDrawer drawer)
    {
        _xmlWriter = xmlWriter;
        _shapeDrawer = drawer;
    }

    //...
    public void WriteToXml(IStream stream)
    {
        _xmlWriter.Write(this, stream);

    }

    public void Draw()
    {
        _drawer.Draw(this);
    }

    // any operation could easily be injected and executed 
    // on this object at run-time
    public void Execute(IGeneralStrategy generalOperation)
    {
        generalOperation.Execute(this);
    }
}

更多信息在此相关讨论中:

  

Should an object write itself out to a file, or should another object act on it to perform I/O?

答案 1 :(得分:13)

存在“默认访问者模式”,其中您正常执行访问者模式,然后通过将所有内容委托给具有签名{{1的抽象方法来定义实现IShapeVisitor类的抽象类}}

然后,在定义访问者时,扩展此抽象类而不是直接实现接口。您可以覆盖当时了解的visitDefault(IShape) *方法,并提供合理的默认值。但是,如果确实没有任何方法可以提前确定合理的默认行为,那么您应该直接实现该接口。

当您添加新的visit子类时,您修复了抽象类以委托其IShape方法,并且指定默认行为的每个访问者都会获得新{{1}的行为}}

如果你的visitDefault类自然地落入层次结构中,那么这种变体就是通过几种不同的方法使抽象类委托;例如,IShape可能会执行:

IShape

这使您可以定义访问者,以您希望的任何特定级别指定他们的行为。

不幸的是,没有办法避免做一些事情来指定访问者在新课程中的行为方式 - 您可以提前设置默认设置,也可以不设置默认设置。 (另见this cartoon的第二个小组)

答案 2 :(得分:6)

答案 3 :(得分:4)

访客设计模式是一种解决方法,而不是问题的解决方案。简短的回答是pattern matching

答案 4 :(得分:2)

无论您采用什么路径,访问者模式当前提供的备用功能的实现都必须“了解”它正在处理的接口的具体实现。因此,您无需为每个额外的实现编写额外的“访问者”功能。这就是说你正在寻找的是一种更灵活和结构化的方法来创建这个功能。

您需要从形状界面中分离出访客功能。

我建议通过抽象工厂创建一种创造论方法,为访客功能创建替代实现。

public interface IShape {
  // .. common shape interfaces
}

//
// This is an interface of a factory product that performs 'work' on the shape.
//
public interface IShapeWorker {
     void process(IShape shape);
}

//
// This is the abstract factory that caters for all implementations of
// shape.
//
public interface IShapeWorkerFactory {
    IShapeWorker build(IShape shape);
    ...
}

//
// In order to assemble a correct worker we need to create
// and implementation of the factory that links the Class of
// shape to an IShapeWorker implementation.
// To do this we implement an abstract class that implements IShapeWorkerFactory
//
public AbsractWorkerFactory implements IShapeWorkerFactory {

    protected Hashtable map_ = null;

    protected AbstractWorkerFactory() {
          map_ = new Hashtable();
          CreateWorkerMappings();
    }

    protected void AddMapping(Class c, IShapeWorker worker) {
           map_.put(c, worker);
    }

    //
    // Implement this method to add IShape implementations to IShapeWorker
    // implementations.
    //
    protected abstract void CreateWorkerMappings();

    public IShapeWorker build(IShape shape) {
         return (IShapeWorker)map_.get(shape.getClass())
    }
}

//
// An implementation that draws circles on graphics
//
public GraphicsCircleWorker implements IShapeWorker {

     Graphics graphics_ = null;

     public GraphicsCircleWorker(Graphics g) {
        graphics_ = g;
     }

     public void process(IShape s) {
       Circle circle = (Circle)s;
       if( circle != null) {
          // do something with it.
          graphics_.doSomething();
       }
     }

}

//
// To replace the previous graphics visitor you create
// a GraphicsWorkderFactory that implements AbstractShapeFactory 
// Adding mappings for those implementations of IShape that you are interested in.
//
public class GraphicsWorkerFactory implements AbstractShapeFactory {

   Graphics graphics_ = null;
   public GraphicsWorkerFactory(Graphics g) {
      graphics_ = g;
   }

   protected void CreateWorkerMappings() {
      AddMapping(Circle.class, new GraphicCircleWorker(graphics_)); 
   }
}


//
// Now in your code you could do the following.
//
IShapeWorkerFactory factory = SelectAppropriateFactory();

//
// for each IShape in the heirarchy
//
for(IShape shape : shapeTreeFlattened) {
    IShapeWorker worker = factory.build(shape);
    if(worker != null)
       worker.process(shape);
}

这仍然意味着您必须编写具体实现来处理'shape'的新版本,但由于它与形状界面完全分离,您可以在不破坏原始界面和与之交互的软件的情况下改进此解决方案。它充当了IShape实现的一种脚手架。

答案 5 :(得分:1)

如果您使用的是Java:是的,它被称为instanceof。人们过分害怕使用它。与访客模式相比,它通常更快,更直接,而且不受第5点的困扰。

答案 6 :(得分:1)

如果你有n IShape个和m个操作对每个形状表现不同,那么你需要n * m个别函数。将这些全部放在同一个班级对我来说似乎是一个可怕的想法,给你一些上帝的对象。所以它们应该通过IShape分组,通过在IShape接口中放置m个函数,每个操作一个,或者通过操作(通过使用访问者模式)分组,通过放置n个函数,一个每个操作/访客类中的每个IShape

您必须在添加新IShape时更新多个类,或者在添加新操作时,无法绕过它。

如果您正在寻找实现默认IShape函数的每个操作,那么这将解决您的问题,如Daniel Martin的答案:https://stackoverflow.com/a/986034/1969638,尽管我可能会使用重载:

interface IVisitor
{
    void visit(IShape shape);
    void visit(Rectangle shape);
    void visit(Circle shape);
}

interface IShape
{
    //...
    void accept(IVisitor visitor);
}

答案 7 :(得分:0)

我实际上已经使用以下模式解决了此问题。我不知道它是否有名字!

public interface IShape
{
}

public interface ICircleShape : IShape
{
}

public interface ILineShape : IShape
{
}

public interface IShapeDrawer
{
    void Draw(IShape shape);

    /// <summary>
    /// Returns the type of the shape this drawer is able to draw!
    /// </summary>
    Type SourceType { get; }
}

public sealed class LineShapeDrawer : IShapeDrawer
{
    public Type SourceType => typeof(ILineShape);
    public void Draw(IShape drawing)
    {
        if (drawing is ILineShape)
        {
            // Code to draw the line
        }
    }
}

public sealed class CircleShapeDrawer : IShapeDrawer
{
    public Type SourceType => typeof(ICircleShape);
    public void Draw(IShape drawing)
    {
        if (drawing is ICircleShape)
        {
            // Code to draw the circle
        }
    }
}

public sealed class ShapeDrawingClient
{
    private readonly IDictionary<Type, IShapeDrawer> m_shapeDrawers =
        new Dictionary<Type, IShapeDrawer>();

    public void Add(IShapeDrawer shapeDrawer)
    {
        m_shapeDrawers[shapeDrawer.SourceType] = shapeDrawer;
    }

    public void Draw(IShape shape)
    {
        Type[] interfaces = shape.GetType().GetInterfaces();
        foreach (Type @interface in interfaces)
        {
            if (m_shapeDrawers.TryGetValue(@interface, out IShapeDrawer drawer))
              {
                drawer.Draw(drawing); 
                return;
              }

        }
    }
}

用法:

        LineShapeDrawer lineShapeDrawer = new LineShapeDrawer();
        CircleShapeDrawer circleShapeDrawer = new CircleShapeDrawer();

        ShapeDrawingClient client = new ShapeDrawingClient ();
        client.Add(lineShapeDrawer);
        client.Add(circleShapeDrawer);

        foreach (IShape shape in shapes)
        {
            client.Draw(shape);
        }

现在,如果有人作为我的图书馆用户定义了IRectangleShape并想绘制它,他们只需定义IRectangleShapeDrawer并将其添加到ShapeDrawingClient的抽屉列表中即可!