【问题标题】:OO design pattern: How to add methods dynamically?OO 设计模式:如何动态添加方法?
【发布时间】:2014-11-26 20:44:02
【问题描述】:

我已经在 C#(或 Java,无关紧要)中实现了一个简单的树结构,其中一切都围绕抽象类 Node 和它的一些子类。 Node 提供了与数学图论相关的方法和属性。例如 Node.Parent 引用父节点和 Node.Children 子节点

class Node {  
    Node Parent;  
    Node[] Children;

    void appendNode(Node node) { ... }
}  

我正在使用树来执行计算。计算涉及大量递归,我还需要为每个节点存储中间值。对于每个计算,我都为 Node 类引入了额外的属性和方法,例如

class Node {  
    Node Parent;  
    Node[] Children; 

    // Calculate weight
    int weight; // current weight
    void recalculateWeight() {
        // perform some heavily recursive stuff
        // involving Parent.recalculateWeight()
        // and update the value of the variable weight
    }

    int price; // current price 
    void recalculatePrice() {
        // perform some heavily recursive stuff
        // involving Parent.recalculatePrice()
        // and update the value of the variable price
    }

    void appendNode(Node node) {
        // ...
        recalculateWeight();
        recalculatePrice();
    }
} 

但现在我不得不放弃这种方法,因为计算值应该在不更改 Node 类的情况下动态添加。动态意味着其他人应该能够仅依靠 Node 类的“图论方法”在给定的树上实现他自己的计算。

您知道什么是好的设计模式吗?

【问题讨论】:

  • 称为子类
  • @zapl 子类化涉及继承,其中涉及静态地向类添加方法——这意味着在编译时。此外,继承不是一种设计模式——它是实现它们的一种手段。装饰器模式或许能解决问题
  • OO 设计发生在编译时,或者实际上在设计人员心目中之前。如果您真的想在运行时添加方法,那么您至少不在 OO 模式的世界中。
  • @zapl 你知道装饰器模式是什么吗?装饰器模式不会在编译时向类添加方法。装饰器模式允许我们在运行时向对象添加职责;很明显,不用说,添加的那些操作必须先编译。不过这些操作是动态添加的。
  • @zapl 你是对的。继承也可以解决这个问题。让我把它留在这句话上:“对于每一个复杂的问题,都有一个清晰、简单和错误的答案。” - H. L. Mencken

标签: java c# oop design-patterns


【解决方案1】:

这叫Visitor 模式。

interface Visitor{

    visit(Node node);

}

class Node{

   //...


   void accept(Visitor v){
       //feel free to change visit order to viist children first
       v.visit(this);
       for(Node child : children){
          v.visit(child);
       }

   }
}

然后您可以对不同的访问者进行所有不同的计算。创建新类型的计算或遍历根本不会更改 Node 类。您只需创建一个新的访问者实现。

class WeightVisitor implements Visitor{

   int weight = 0;

   void visit(Node n){
        weight += ...
   }

}

然后每次要计算重量

WeightVisitor visitor = new WeightVisitor();

rootNode.accept(visitor);

int weight = visitor.getWeight();

【讨论】:

  • 您错过了访问者模式中重要的双重调度部分:gist.github.com/anonymous/03ddd63b83566bfd1412
  • @zapl 感谢您发现错字。我的意思是输入v.visit(this) 而不是visit(this)。我已经解决了我的答案谢谢!
  • 这实际上不是我的意思。比较en.wikipedia.org/wiki/Visitor_pattern - 访问者界面中有多种方法。访问者模式解决的问题是只有Node 的子类具有权重属性。例如。您的 WeightVisitor 需要 visit(WeightNode n) 或类似方法,以便它可以访问该属性。您目前只是以一种更花哨的方式传递Nodes,但实际上并没有解决问题。您展示的模式更像是一种策略模式,您可以在其中将不同的实现应用于您的实体。
  • @zapl 取决于 weight 在 OP 上下文中的含义,它不一定是属性。 OP 似乎只有一个属性,因为递归解决方案需要一个放置数据的地方。权重可能类似于“后代节点数”,只需要计算访问调用。您是正确的,不同的实现是不同的策略。然而,这些策略如何发挥作用的机制是Visitor 模式。现实世界的项目通常需要多种模式协同工作。
  • IMO 不是 访问者模式,因为您不需要接受,然后访问部分,您可以完全从外部控制 gist.github.com/anonymous/a32a2c33cbb988b8bf99 - 与 访问者模式 IMO 是“访问者模式是一种将算法与其运行的对象结构分离的方式”意味着算法是固定的,数据结构是变化的。在这里,节点结构是固定的,访问算法会发生变化。还有这里的“访问者和迭代器”部分oodesign.com/visitor-pattern.html
【解决方案2】:

这取决于您要达到的目标。使用节点图有两种不同的方式:

  • 节点只是数据的容器,节点库的用户可以使用它们来存储数据和计算各种事物。这类似于LinkedList,它允许您将数据存储在节点中、遍历列表、删除节点等。甚至递归。但这不需要对节点本身进行任何更改。这些方法完全由客户负责在外部实施。您的节点应该只提供以各种方式迭代它们的方法。并且可能提供一种类型安全(即像List<Type> 这样的通用)方式来存储自定义数据。

  • 您正在为用户定义的节点提供图表。用户将以自定义方式扩展 Node 类来执行自定义操作。用户仍会使用您提供的图形功能,例如您可以提供的 Tree.rebalance(Node root) 方法。您的节点类和其他方法应该允许的是使扩展变得容易。

例如,您应该考虑将类和其他方法设为通用,以便用户可以完全使用自定义子类型

// That's what you provide. Has all the base functionality of a node
class Node<T extends Node<T>> {
    private T parent;
    private List<T> children;
    public void setParent(T parent) {
        this.parent = parent;
    }
    public T getParent() {
        return this.parent;
    }
    // ...
}

// that's what the user can do with it
class WeightedNode extends Node<WeightedNode> {
    public int weight = 5;

    public void update() {
        WeightedNode parent = getParent();
        // ^^ Typesafe access to WeightedNode!
        this.weight = parent.weight + 1;
        parent.update();
    }
}

class User {
    void use() {
        WeightedNode node = new WeightedNode();
        node.update();
    }
}

【讨论】:

    猜你喜欢
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 2022-10-15
    • 1970-01-01
    • 1970-01-01
    • 2017-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多