Страницы

Поиск по вопросам

суббота, 1 июня 2019 г.

Как избавиться от параллельных иерархий наследования?

Я решаю задачу о нахождении лидера (leader election) Это чисто алгоритмическая задача, у которой есть 2 формы: однонаправленное кольцо и двунаправленное. Для представления данных я создал свой собственный класс для списка, закрученного в кольцо. То есть у меня два алгоритма, по одному для каждой формы задачи.
public abstract class MyAbstractRoundList {
protected int size; protected Agent[] arr = new Agent[1]; protected int index = 0;
public boolean isEmpty() { return size == 0; }
public int size() { return size; }
public void add(Agent agent) { if (size >= arr.length) { Agent[] temp = arr; arr = new Agent[temp.length * 2]; System.arraycopy(temp, 0, arr, 0, temp.length); } arr[size++] = agent; } // и дальше еще много методов
Далее я создаю наследников этого класса для однонаправленного режима и для двунаправленного. Реализации в них конечно различаются. Далее, у меня есть класс:
public class LeaderElection {
public static void solve(MyAbstractList list, int i) { if (i == 0) { list.initiateStartState(); } else { list.setMessages(); } }
}
В нем у меня решение для однонаправленного режима. И есть похожий класс с решением для двунаправленного, но реализации конечно тоже различаются. Что бы не прописывать в клиентском коде
if(isOneDirectMode()){ LeaderElection.solve(); { if(isBiDirectMode()){ BiLeaderElection.solve(); }
Я мог бы так же создать абстрактный класс для решения и его наследников и пользоваться полиморфизмом (так я и делаю для списков). Но вот тут и возникает проблема: у меня получаются параллельные иерархии наследования. Если появится новый режим я должен буду добавить новый класс подкласс MyAbstractRoundList и новый подкласс решения. Можно как то избавиться от этого? Я вижу только один способ: Можно было бы сделать в MyAbstractRoundList абстрактный метод solve() и реализовывать его в подклассах каждого режима так как это требуется для конкретного режима. Но это плохо, потому что тогда у меня подклассы MyAbstractRoundList будут не только хранить данные, но и решать задачу. То есть выполнять 2 функции.


Ответ

Они не параллельны. Изменения в одной иерархии никак не влекут за собой изменения в другой иерархии.
Если появится новый режим я должен буду добавить новый класс подкласс MyAbstractRoundList и новый подкласс решения. Можно как то избавиться от этого? Я вижу только один способ: Можно было бы сделать в MyAbstractRoundList абстрактный метод solve() и реализовывать его в подклассах каждого режима так как это требуется для конкретного режима.
Решение задачи - это поведение. Поведение лучше описывать интерфейсами.
public interface Solver { void solve(MyAbstractList list, int i); } ... public class LeaderElection implements Solver {
... public class BiLeaderElection implements Solver {
... public class MultiTreadingLeaderElection implements Solver {
У вас отдельная иерархия наследования классов, отвечающих за хранение данных, и отдельная иерархия классов, отвечающих за решения. Это нормально. Если появится новый вид хранения (новый тип списка), вы добавите новый класс-наследник MyAbstractRoundList. Это никак не связано с решениями, новый класс может использоваться и существующими решениями. Если потребуется добавить новый способ решения, вы добавите новый класс, реализующий интерфейс Solver, и это не обязательно должно отразиться на иерархии классов для хранения.
При таком подходе у вас всегда будет работать код solver.solve(abstractList, i);, если в переменную solver положить объект любого класса, реализующего интерфейс Solver, а в переменную abstractList - любой объект класса-наследника MyAbstractRoundList, и самое главное - этот код не придется менять при добавлении новых классов, как и должно быть с точки зрения ООП.

UPD. Есть данные, есть поведение. В рамках класса поля описывают данные, методы - поведение. Например, я руковожу бригадой роботов. Одни из них могут строить, другие - переносить, третьи - разрушать, четвертые - поливать. Робот-строитель, например, может принести себе стройматериал, но только в небольшом количестве, таким образом, он может и строить, и носить. А робот-носитель - только носить. И если вдруг поступает задача срочно разгрузить вагон стройматериалов, то мне надо собрать всех роботов, умеющих носить. И мне плевать, что это за роботы. Хоть робот-поливалка. Если может носить - пусть идет носить. Я просто выберу роботов с нужным мне поведением, и буду уверен, что они смогут сделать то, что мне надо.
public interface Carrier { void carry(); //нести }
public interface Builder { void build(); //построить }
public interface Destroer { void destroy(); //сломать }
public class RobotBuilder extends Robot implements Builder, Carrier { public void carry() {
}
public void build() {
} }
public class RobotDestroer extends Robot implements Destroer { public void destroy() {
} }
public class RobotCarrier extends Robot implements Carrier { public void carry() {
} }

Комментариев нет:

Отправить комментарий