首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >替代访客模式?

替代访客模式?
EN

Stack Overflow用户
提问于 2009-06-12 10:04:41
回答 8查看 19.6K关注 0票数 56

我正在寻找一个替代访客模式。让我专注于模式的几个相关方面,同时跳过不重要的细节。我将使用一个形状示例(对不起!):

  1. 您有实现IShape接口的对象的层次结构。
  2. 您有许多要对层次结构中的所有对象执行的全局操作,例如绘制、WriteToXml等。
  3. 很容易直接深入到IShape接口中,并向IShape接口中添加Draw()和IShape()方法。这不一定是一件好事,每当您希望添加要在所有形状上执行的新操作时,必须更改每个IShape派生类。
  4. 实现每个操作的访问者,即抽签访问者或WirteToXml访问者,将该操作的所有代码封装在一个类中。然后,添加一个新操作就是创建一个对所有类型的IShape执行操作的新访问者类。
  5. 当您需要添加一个新的IShape派生类时,您实际上遇到了与3中相同的问题,必须更改所有的访问者类,以添加一个方法来处理新的IShape派生类型。

大多数你读过访问者模式的地方都说,第5点是模式工作的主要标准,我完全同意。如果IShape派生类的数量是固定的,那么这是一种非常优雅的方法。

因此,问题是当添加一个新的IShape派生类时,每个访问者实现都需要添加一个新的方法来处理该类。这充其量是令人不快的,在最坏的情况下也是不可能的,这表明这种模式并不真正是为了应对这种变化而设计的。

那么,问题是,有没有人遇到过变通的方法来处理这种情况?

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2009-06-12 10:11:51

您可能想看看战略模式。这仍然使您可以分离关注点,同时仍然可以添加新功能,而不必更改层次结构中的每个类。

代码语言:javascript
复制
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);
    }
}

更多信息载于这一相关讨论:

对象应该将自身写入文件,还是应该由另一个对象对其执行I/O操作?

票数 16
EN

Stack Overflow用户

发布于 2009-06-12 10:35:47

这里有一个“默认的访问者模式”,在该模式中,您将访问者模式作为正常操作,然后定义一个抽象类,该类通过将所有内容委托给带有签名IShapeVisitor的抽象方法来实现您的visitDefault(IShape)类。

然后,在定义访问者时,扩展这个抽象类,而不是直接实现接口。您可以覆盖当时知道的visit*方法,并提供一个合理的默认设置。但是,如果真的没有任何方法可以提前找出合理的默认行为,那么应该直接实现接口。

当您添加一个新的IShape子类时,您可以修复抽象类,将其委托给它的visitDefault方法,而每个指定默认行为的访问者都会得到新IShape的行为。

如果您的IShape类自然落入层次结构,则对此的一个变化是通过几种不同的方法使抽象类委托;例如,DefaultAnimalVisitor可能会这样做:

代码语言:javascript
复制
public abstract class DefaultAnimalVisitor implements IAnimalVisitor {
  // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake
  public void visitLion(Lion l)   { visitFeline(l); }
  public void visitTiger(Tiger t) { visitFeline(t); }
  public void visitBear(Bear b)   { visitMammal(b); }
  public void visitSnake(Snake s) { visitDefault(s); }

  // Up the class hierarchy
  public void visitFeline(Feline f) { visitMammal(f); }
  public void visitMammal(Mammal m) { visitDefault(m); }

  public abstract void visitDefault(Animal a);
}

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

不幸的是,我们无法避免做一些事情来指定访问者将如何使用一个新类--要么可以提前设置默认设置,要么就不能设置。(参见这幅漫画的第二个面板)

票数 13
EN

Stack Overflow用户

发布于 2009-06-12 13:16:55

我为金属切割机维护一个CAD/CAM软件。所以我对这个问题有一些经验。

当我们第一次转换我们的软件(它第一次发布于1985年!)对于一个面向对象的设计,我做了你不喜欢的事情。对象和接口都有绘图、WriteToFile等。在转换过程中发现和阅读有关设计模式的内容对我们有很大帮助,但是仍然有很多不好的代码气味。

最后,我意识到这些类型的操作都不是对象真正关心的问题。而是需要执行各种操作的各个子系统。我通过使用现在称为被动视点命令对象和软件层之间定义良好的接口来处理这个问题。

我们的软件基本上是这样的。

  • 实现各种表单接口的表单。这些表单是将事件传递到UI层的shell。
  • 通过表单接口接收事件和操作表单的UI层。
  • UI层将执行所有实现命令接口的命令
  • UI对象有自己的接口,命令可以与之交互。
  • 这些命令获取所需的信息,对其进行处理,操作模型,然后向UI对象报告,UI对象将使用表单执行所需的任何操作。
  • 最后给出了包含系统各个对象的模型。像形状程序,切割路径,切割表,和金属片。

因此,在UI层中处理绘图。我们对不同的机器有不同的软件。因此,虽然我们所有的软件共享相同的模型并重用许多相同的命令。他们处理绘画之类的事情很不一样。例如,对于路由器机器和使用等离子手电筒的机器来说,切割表是不同的,尽管它们都是一个巨大的X平板表。这是因为就像汽车一样,这两台机器的制造方式也有很大的不同,这样对客户来说就有了视觉上的差异。

至于形状,我们所做的如下

我们有形状程序,通过输入的参数产生切割路径。切割路径知道产生了哪种形状程序。然而,切割路径不是形状。它仅仅是在屏幕上绘制和裁剪形状所需的信息。这种设计的一个原因是,当从外部应用程序导入时,没有形状程序就可以创建切割路径。

这种设计允许我们将切割路径的设计与形状的设计分开,而形状的设计并不总是一样的。在您的情况下,您需要打包的可能是绘制形状所需的信息。

每个形状程序都有许多实现IShapeView接口的视图。通过IShapeView接口,shape程序可以告诉泛型的形状表单,如何设置自己来显示该形状的参数。泛型形状表单实现IShapeForm接口,并向ShapeScreen对象注册自己。ShapeScreen对象向我们的应用程序对象注册自己。形状视图使用在应用程序中注册自己的任何形状屏幕。

我们有客户喜欢以不同方式输入形状的多个视图的原因。我们的客户群被分成两部分:喜欢在表格中输入形状参数的人和喜欢在前面输入形状的图形表示的人。我们还需要访问参数,有时通过一个最小的对话框,而不是我们的完整的形状进入屏幕。因此出现了多个视图。

操纵形状的命令分为两种类型之一。他们要么操纵切割路径,要么操纵形状参数。通常,为了操作形状参数,我们要么将它们抛回形状输入屏幕,要么显示最小的对话框。重新计算形状,并将其显示在同一位置。

对于切割路径,我们将每个操作捆绑在一个单独的命令对象中。例如,我们有命令对象

ResizePath、RotatePath、MovePath、SplitPath等。

当我们需要添加新的功能时,我们添加另一个命令对象,在右边的UI屏幕中找到一个菜单、键盘或工具栏按钮槽,并设置UI对象以保存该命令。

例如

代码语言:javascript
复制
   CuttingTableScreen.KeyRoute.Add vbShift+vbKeyF1, New MirrorPath

代码语言:javascript
复制
   CuttingTableScreen.Toolbar("Edit Path").AddButton Application.Icons("MirrorPath"),"Mirror Path", New MirrorPath

在这两种情况下,命令对象MirrorPath都与所需的UI元素相关联。在MirrorPath的execute方法中,反映特定轴上路径所需的全部代码。该命令可能有自己的对话框,或者使用UI元素之一询问用户要镜像的轴。所有这些都不是创建访问者,也不是向路径添加方法。

您会发现,可以通过将操作绑定到命令中来处理很多操作。然而,我警告说,这不是一个黑人或白人的情况。您仍然会发现,某些东西作为原始对象上的方法工作得更好。在5月份的经验中,我发现我过去在方法中所做的工作中有80%能够被移动到命令中。最后的20%只是简单的工作在对象上更好。

现在有些人可能不喜欢这样,因为它似乎违反了封装。从过去十年把我们的软件维持为面向对象的系统,我不得不说,你能做的最重要的长期事情就是清楚地记录软件的不同层之间和不同对象之间的交互。

将操作捆绑到Command对象中有助于实现这一目标,而不是对封装理想的盲目投入。所有需要对镜像做的事情--一条路径被捆绑在镜像路径命令对象中。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/985960

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档