使用Composite类:客户端如何确定它是否是复合的

时间:2015-01-09 04:05:54

标签: java

我知道这个here存在类似的问题。它比我在这里提出的问题更多地考虑了类特定行为的问题。

考虑以下复合模式的简单实现:

interface Item {
    int getWeight();
}

class SimpleItem implements Item {
    private int weight;
    public int getWeight() {
        return weight;
    }
}

class Container implements Item {
    private List<Item> items;
    private int weight;
    public void add(Item item) {
        items.add(item);
    }
    public int getWeight() {
        return weight + items.stream().mapToInt(Item::getWeight).sum();
    }
}

现在考虑Item的用户如何确定它是否是容器。例如,Container pushAdd中需要一个方法,该方法将层次结构中的项目推送到其中没有容器的容器。容器只知道Items,它不知道那些项是Containers还是SimpleItems或其他一些实现Item的类。

有三种可能的解决方案:

1。 使用实例和转换

public void pushAdd(Item item) {
    Optional<Container> childContainer = items.stream()
        .filter(item instanceof Container)
        .map(item -> (Container)item)
        .findAny();
    if (childContainer.isPresent()) {
        childContainer.get().pushAdd(item);
    } else {
        add(item);
    }
}

2。 实现是/作为方法

public pushAdd(Item item) {
    Optional<Container> childContainer = items.stream()
        .filter(Item::isContainer)
        .map(Item::asContainer);
    ....
}

3。 访问者模式(我省略了简单的accept实现)。

interface ItemVisitor {
    default void visit(SimpleItem simpleItem) { throw ...}
    default void visit(Container container) { throw ... };
}

public pushAdd(Item item) {
    Optional<Container> childContainer = ... (using instanceOf);
    if (childContainer.isPresent()) {
        childContainer.get().accept(new ItemVisitor(item) {
            void visit(Container container) {
                container.pushAdd(item);
            }
        };
    } else {
        add(item);
    }
}

第一个是邪恶的,因为它使用了instanceof和cast。第二个是邪恶的,因为它将容器的知识强制为物品 - 当物品的其他子类被创建时,它会变得更糟。第三部分并不能帮助您了解是否可以在调用访问者之前添加到项目中。您可以捕获异常,但这似乎是对我的例外滥用:在访问之前有更好的方法来检查。

所以我的问题是:是否有另一种模式可以用来避免强制转换和instanceof而不必将子类的知识推到层次结构中?

2 个答案:

答案 0 :(得分:1)

我认为当我说访问者模式在java中并不完全流行时,我代表任何java人。所以上面的内容可以这样实现(我将在这里使用接口,因为根据我的经验,它们更灵活):

interface Item { /* methods omitted */ }

interface SimpleItem extends Item { /* methods omitted */ }

interface ContainerItem extends Item { /* methods omitted */ }

// telling clients that this can throw an exception or not
// here is a whole different design question :)
interface ItemVisitor { void visit(Item i) /* throws Exception */; }

class MyVisitor implements ItemVisitor {
  void visit(Item i) {
    if (i instanceof SimpleItem) {
      // handle simple items
    } else if (i instanceof ContainerItem) {
      // handle containers using container specific methods
    } else {
      // either throw or ignore, depending on the specifications
    }
  }
}

最新的JVM上instanceof的成本相当低,所以我不会太担心,除非你能证明传统访问者的速度要快得多。

代码的可读性和可维护性可以说是相同的,但有一些差异。首先,如果将新界面添加到修改现有访问者的层次结构中,则不需要更改这些访问者。另一方面,更容易忽视 需要更改的访问者(特别是在您无法控制的客户端代码中),因为访问者并未明确要求客户这样做,但是,嘿,这是维护代码的本质和需要访问的设计的一般缺点。

这种模式的另一个优点是不需要访问的客户不需要担心(没有accept方法),即更短的学习曲线

最后,我认为这种模式更接近&#34;纯粹主义者&#34; OOD在某种意义上说接口层次结构不包含虚假方法(visitcanAddItems等),即有no "tags"种类。

答案 1 :(得分:0)

事实上似乎没有人发布一个答案,即没有比我提出的3更好的选择。

所以我会发布我的首选解决方案,看看是否有人可以改进它。在我看来,最好的选择是选项2和3的组合。我不认为拥有canAddItems Item成员太邪恶了 - 可以说Item的实施者告诉你是合理的如果你可以添加项目。但访客似乎是隐藏如何添加项目细节的好方法。

所以,fwiw这是我对我提出的问题的最佳妥协。我仍然不是百分之百满意。特别是,如果实现了可以添加项目的另一个类,则添加项目的访问者将会中断。但这可能是你想要的,因为它正在改变pushAdd的语义。

interface Item {

    int getWeight();

    void accept(ItemVisitor visitor);

    default boolean canAddItems() {
        return false;
    }

}

interface ItemVisitor {

    default void visit(SimpleItem simpleItem) {
        throw new IllegalArgumentException("ItemVisitor does not accept SimpleItem");
    }

    default void visit(Container container) {
        throw new IllegalArgumentException("ItemVisitor does not accept Container");
    }
}

class SimpleItem implements Item {

    private int weight;

    public int getWeight() {
        return weight;
    }

    public void accept(ItemVisitor visitor) {
        visitor.visit(this);
    }
}

class Container implements Item {

    private List<Item> items;
    private int weight;

    public void add(Item item) {
        items.add(item);
    }

    public int getWeight() {
        return weight + items.stream().mapToInt(Item::getWeight).sum();
    }

    public void accept(ItemVisitor visitor) {
        visitor.visit(this);
    }

    public void pushAdd(Item item) {
        Optional<Item> child = items.stream().filter(Item::canAddItems).findAny();
        if (child.isPresent()) {
            child.get().accept(new ItemVisitor() {
                public void visit(Container container) {
                    container.add(item);
                }
            });
        } else {
            add(item);
        }
    }

}