Правильный способ использования 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) объекты.
Интересную дискуссию о плюсах и минусах каждого дизайна, а также о том, когда предпочесть один дизайн другому, можно найти здесь.