Страницы

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

суббота, 30 ноября 2019 г.

Почему композиция не нарушает Принцип единственной обязанности?

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


Я решаю задачу о нахождении лидера (leader election).

Задача чисто алгоритмическая. Есть 2 формы задачи. У меня есть абстрактный класс
для представления данных и абстрактный класс Solver. Для каждой формы задачи эти классы
я расширяю в соответствии с нуждами этой формы задачи. То есть, решая задачу для первой
формы мне нужно написать так в клиентском коде:

MyData data = new MyDataForm1();
MySolver solver = new MySolverForm1();


Это можно было бы скомпозировать так: 

public abstract class AbstractTask{
// some code
}

public class Task1{
    MyData data = new MyDataForm1();
    MySolver solver = new MySolverForm1();

    //some code
}

public class Task2{
    MyData data = new MyDataForm2();
    MySolver solver = new MySolverForm2();

    //some code
}


Тогда в клиентском коде для первой формы, например, можно будет делать так:

AbstractTask task = new Task1();


Это же композиция? Но тут такая проблема: Получается что у классов Task 2 обязанности.
Они и данные хранят и задачу решают (да, делегируя это экземплярам MyData и MySolver,
но все же). И так ведь получается всегда при композиции. Мы включаем экземпляры нескольких
классов в один класс в качестве полей. У включенных классов были какие то обязанности.
Значит у включающего класса будет несколько обязанностей. 

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


Ответы

Ответ 1



У включенных классов были какие то обязанности. Значит у включающего класса будет несколько обязанностей. Не-а. Только его собственная. Во всяком случае, так будет выглядеть со стороны. Действует инкапсуляция. Обязанность вложенного класса является уже внутренней деталью реализации и эта деталь не должна быть видна тому, кто этим классом пользуется. Из этого же вытекает правило, по которому можно понять, разумно ли применение композиции в каждом конкретном случае: полностью ли "внутренняя" обязанность "погружена" во "внешнюю" (без существенного дополнительного "натягивания").

Ответ 2



Я понимаю тут так, что это более высокий уровень абстракции. Например, на уровне отдельных деталей машины можно рассматривать мотор, магнитолу и руль как отдельные классы, которые выполняют какую-то обязанности. Но потом мы начинаем рассматривать машину. Она состоит из этих частей. Но теперь мы рассматриваем на более высоком уровне абстракции (нам просто надо ездить), поэтому можно сказать что класс Машина все-таки выполняют всего одну задачу (езда). Вы здесь сами верно ответили на вопрос. Дело в том, что у нового класса будет обязанность, отличная от обязанностей объектов, которые он в себя включает. Если говорить конкретно, то обязанность нового класса заключается в том, чтобы соединять вместе данные и алгоритм решения. В последующем может обнаружиться, что такой объект будет весьма полезен, если понадобятся промежуточные шаги. Эта обязанность звучит несколько абстрактно, но тем не менее имеет право на жизнь. И, как можно заметить, она более высокоуровневая, чем обязанности включаемых классов. Об этом я вскользь упоминал в предыдущем ответе на ваш вопрос об SRP. Можно вывести правило: чем выше уровень "модуля", тем более высокоуровневыми являются его обязанности. Возьмем тип float. Его обязанность -- реализовывать работу с числами с плавающей точкой (представление, плюс базовые математические операции). Используя тип float (т.е. используя композицию), вы пишете функцию расчета гипотенузы по заданным катетам. Обязанность этой функции -- считать гипотенузу. Дальше вы включаете эту функцию в пакет PlanimetryAlgorithms. Обязанность этого пакета -- предоставлять различные алгоритмы, связанных с планиметрий (т.е. геометрией фигур на плоскости). Пакет PlanimetryAlgorithms может, в свою очередь, входить в библиотеку Geometry, куда также будут входить пакеты для других разделов геометрии. Обязанность этой библиотеки -- предоставлять различные функции, касающиеся геометрии вообще. Как видно, с повышением гранулярности обязанность становится более высокоуровневой, но при этом ее единственность соблюдается. Функция расчета гипотенузы не выдает нам заодно значения всех углов в этом треугольнике, пакет PlanimetryAlgorithms не содержит методов для рисования фигур, а библиотека Geometry не начинает вдруг заниматься физикой.

Ответ 3



Верно, все дело именно в уровне абстракции. Объекты, выполняющие единственную функцию высшего уровня, для выполнения своих обязанностей пользуются функциями включенных в себя объектов. Обязанность электродвигателя как отдельного объекта - превращать электрическую энергию в механическую (вращение вала). Объект, например, наждак выполняет свою функцию - предоставляет инструмент для заточки ножей. А троллейбус - другую функцию.И тот, и другой включают в себя электродвигатель в том или другом виде (наследники абстрактного электродвигателя), и используют его для выполнения своей функции. Также у нас бывают объекты типа "швейцарский нож": объект вроде один, но функций как бы много. С этим нужно быть осторожным при проектировании, чтобы не включить "посторонних" функций и не нарушить принцип. Тоже хороший пример композиции.

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

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