Правильный способ использования Composite Pattern

Я изучаю шаблон составного дизайна и не могу понять, как я могу обращаться с каждым компонентом одинаково (Leaf и Composite), когда всякий раз, когда я пытаюсь добавить или удалить компонент из Leaf, у меня появляется ошибка (или ничего не должно происходить), Мне кажется, что это ужасный дизайн, потому что он нарушает правило наследования, чтобы относиться к объектам одинаково. Единственным способом было бы как-то отличить Composite от Leaf и всегда быть в курсе того, с чем я работаю. Но это возвращает меня к первоначальной проблеме...

Составная часть:

public abstract class Equipment {
    private String name;

    protected Equipment(String name){
        this.name = name;
    }

    public String name(){
        return name;
    }


    public abstract int power();
    public abstract double netPrice();
    public abstract double discountPrice();

    public abstract void add(Equipment equipment);
    public abstract void remove(Equipment equipment);

    public Iterable<Equipment> createIterator(){
        return Collections.emptyList();
    }
}

Композитный:

public abstract class CompositeEquipment extends Equipment{
    private final List<Equipment> elements;

    protected CompositeEquipment(String name) {
        super(name);
        elements = new LinkedList<>();
    }

    @Override
    public double netPrice() {
        Iterable<Equipment> iter = createIterator();
        double total = 0;

        for (Iterator<Equipment> i = iter.iterator(); i.hasNext() ;) {
            Equipment next = i.next();
            total += next.netPrice();
        }

        return total;
    }

    @Override
    public Iterable<Equipment> createIterator() {
        return elements;
    }

    @Override
    public void remove(Equipment equipment){
        elements.remove(equipment);
    }

    @Override
    public void add(Equipment equipment){
        elements.add(equipment);
    }

}

Лист:

public class FloppyDisk extends Equipment{

    public FloppyDisk(String name) {
        super(name);
    }

    @Override
    public int power() {
        return 1;
    }

    @Override
    public double netPrice() {
        return 3;
    }

    @Override
    public double discountPrice() {
        return 2.2;
    }

    @Override
    public void add(Equipment equipment) {
        //we well do nothing here because thats the final element of the tree
    }

    @Override
    public void remove(Equipment equipment) {
        //we well do nothing here because thats the final element of the tree
    }

}

Проблема, которую я вижу:

public void extendTheTree(Equipment equipment){
    equipment.add( new CompositeWithLeafs() );  //lets hope it is a Composite not a Leaf!!!
}

Итак, как я должен использовать этот шаблон тогда или в каком сценарии??? Единственное решение, которое я вижу, это избавиться от концепции Leaf и использовать только Composits.

2 ответа

Корень вашего недоразумения в том, что вы взяли методы, которые имеют смысл для Leaf, и методы, которые имеют смысл для Composite, и поместили объединение этих методов в public abstract class Equipmentэто ваш компонент. Таким образом, вы получили общего предка для Leaf и Composite, часть которого предка не имеет смысла для Leaf. Я говорю о add а также remove методы, которые не имеют смысла для Leaf, и, следовательно, не должны быть частью компонента в первую очередь. Нужно ли использовать абстрактный класс или интерфейс для представления вашего Компонента - это еще одна проблема, и вы можете найти идеальный анализ на этом сайте. Но дело в том, что Компонент должен содержать пересечение методов Leaf и методов Composite, набор методов, которые можно использовать на объекте, не зная, является ли он Leaf или Composite. Более формально, Компонент должен определять общий интерфейс, который должен быть реализован Листом и Композитом. Если вы пойдете по этому пути, вы обнаружите, что вы никогда не могли add к Листу, потому что у интерфейса Компонента не должно быть такого метода для переопределения, и у Листа также не должно быть такого метода. К лучшему или худшему, вы можете только add к чему-то, что вы знаете, является составным.

Из Википедии :

Существует два варианта дизайна для определения и реализации операций, связанных с дочерними элементами, таких как добавление/удаление дочернего компонента в/из контейнера (add(child)/remove(child)) и доступ к дочернему компоненту (getChild()):

Дизайн для единообразия: операции, связанные с дочерними элементами, определяются в интерфейсе компонента. Это позволяет клиентам одинаково обрабатывать конечные и составные объекты. Но безопасность типов теряется, потому что клиенты могут выполнять операции, связанные с дочерними объектами, над объектами Leaf.

Дизайн для безопасности типов: дочерние операции определены только в классе Composite. Клиенты должны по-разному обращаться с конечными и составными объектами. Но достигается безопасность типов, поскольку клиенты не могут выполнять операции, связанные с дочерними объектами, над объектами Leaf.

Шаблон проектирования Composite делает упор на единообразие, а не на безопасность типов.

В вашем примере вы выбрали дизайн для единообразия, добавив операции, связанные с дочерними элементами (т.е. добавить/удалить) в абстрактный класс вашего компонента (оборудования), поэтому вы теряете безопасность типов, поскольку ваши клиенты могут вызывать методы добавления/удаления на листе (FloppyDisk) объекты.

Интересную дискуссию о плюсах и минусах каждого дизайна, а также о том, когда предпочесть один дизайн другому, можно найти здесь.

Другие вопросы по тегам