Страницы

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

пятница, 13 марта 2020 г.

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

#java #ооп #архитектура


Я решаю задачу о нахождении лидера (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 функции.
    


Ответы

Ответ 1



Они не параллельны. Изменения в одной иерархии никак не влекут за собой изменения в другой иерархии. Если появится новый режим я должен буду добавить новый класс подкласс 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() { } }

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

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