Разве «глубокие композиционные иерархии» тоже не плохи?


19

Извиняюсь, если «Составная иерархия» не вещь, но я объясню, что я имею в виду под этим вопросом.

Нет ни одного программиста ОО, который бы не сталкивался с вариациями «Сохраняйте иерархию наследования плоскими», «Предпочитайте композицию над наследованием» и так далее. Тем не менее, глубокие композиционные иерархии также кажутся проблематичными.

Допустим, нам нужна коллекция отчетов, детализирующих результаты эксперимента:

class Model {
    // ... interface

    Array<Result> m_results;
}

Каждый результат имеет определенные свойства. К ним относятся время эксперимента, а также некоторые метаданные с каждой стадии эксперимента:

enum Stage {
    Pre = 1,
    Post
};

class Result {
    // ... interface

    Epoch m_epoch;
    Map<Stage, ExperimentModules> m_modules; 
}

Ок, отлично. Теперь у каждого экспериментального модуля есть строка, описывающая результаты эксперимента, а также коллекция ссылок на экспериментальные наборы образцов:

class ExperimentalModules {
    // ... interface

    String m_reportText;
    Array<Sample> m_entities;
}

И тогда каждый образец имеет ... ну, вы получите картину.

Проблема в том, что, если я моделирую объекты из моего домена приложения, это кажется очень естественным соответствием, но в конце концов, это Resultпросто тупой контейнер данных! Кажется, не стоит создавать для него большую группу классов.

Если предположить, что структуры данных и классы, показанные выше, правильно моделируют отношения в предметной области, есть ли лучший способ моделировать такой «результат», не прибегая к глубокой иерархии композиции? Есть ли какой-то внешний контекст, который поможет вам определить, является ли такой дизайн хорошим или нет?


Не понимаю часть внешнего контекста.
Тулаинс Кордова

@ TulainsCórdova я пытаюсь спросить здесь: «Есть ли ситуации, для которых глубокая композиция является хорошим решением»?
AwesomeSauce

Я добавил ответ.
Тулаинс Кордова

5
Да, точно. Проблемы, которые люди пытаются решить, заменив наследование композицией, волшебным образом не исчезают только потому, что вы поменяли одну стратегию объединения функций на другую. Они оба являются решениями для управления сложностью, и эта сложность все еще существует, и с ней нужно как-то справляться.
Мейсон Уилер

3
Я не вижу ничего плохого в глубоких моделях данных. Но я также не понимаю, как вы могли бы смоделировать это с помощью наследования, поскольку это отношение 1: N на каждом уровне.
USR

Ответы:


17

Вот что такое Закон Деметры / принцип наименьшего знания . Пользователь Modelне обязательно должен знать об ExperimentalModulesинтерфейсе, чтобы выполнять свою работу.

Общая проблема заключается в том, что когда класс наследует от другого типа или имеет открытое поле с каким-либо типом или когда метод принимает тип в качестве параметра или возвращает тип, интерфейсы всех этих типов становятся частью эффективного интерфейса моего класса. Возникает вопрос: эти типы, от которых я зависит: использует ли это весь смысл моего класса? Или это просто детали реализации, которые я случайно раскрыл? Потому что, если они являются деталями реализации, я просто показал их и позволил пользователям моего класса зависеть от них - мои клиенты теперь тесно связаны с моими деталями реализации.

Поэтому, если я хочу улучшить сплоченность, я могу ограничить эту связь, написав больше методов, которые делают полезные вещи, вместо того, чтобы требовать от пользователей доступа к моим частным структурам. Здесь мы могли бы предложить методы для поиска или фильтрации результатов. Это облегчает рефакторинг моего класса, так как теперь я могу изменить внутреннее представление, не меняя интерфейс.

Но это не без затрат - интерфейс моего открытого класса теперь раздут с операциями, которые не всегда нужны. Это нарушает принцип сегрегации интерфейса: я не должен заставлять пользователей зависеть от большего количества операций, чем они фактически используют. И именно здесь дизайн важен: дизайн о принятии решения , какой принцип является более важным в вашем контексте, а также о поиске подходящего компромисса.

При разработке библиотеки, которая должна поддерживать совместимость с исходными кодами для нескольких версий, я был бы более склонен более тщательно следовать принципу наименьших знаний. Если моя библиотека использует другую библиотеку для внутреннего использования, я никогда не предоставлю пользователям ничего, что определено этой библиотекой. Если бы мне нужно было выставить интерфейс из внутренней библиотеки, я бы создал простой объект-обертку. Здесь могут помочь шаблоны дизайна, такие как шаблон моста или шаблон фасада.

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


Вы упомянули о серьезной проблеме, с которой я столкнулся; Идея, что мне нужно было углубиться в реализацию, чтобы сделать что-то полезное на более высоких уровнях абстракции. Очень полезная дискуссия, спасибо.
AwesomeSauce

7

Разве «глубокие композиционные иерархии» тоже не плохи?

Конечно. Правило должно на самом деле сказать: «Держите все иерархии как можно более плоскими».

Но глубокие иерархии композиции менее хрупкие, чем иерархии глубокого наследования, и более гибкие для изменения. Интуитивно, это не должно вызывать удивления; Семейные иерархии одинаковы. Ваши отношения с кровными родственниками зафиксированы в генетике; ваши отношения с друзьями и вашим супругом нет.

Ваш пример не кажется мне "глубокой иерархией". Форма наследуется от прямоугольника, который наследуется от набора точек, который наследуется от координат x и y, а я еще даже не получил полную оценку.


4

Перефразируя Эйнштейна:

сохранить все иерархии как можно более плоскими, но не более плоскими

Это означает, что если проблема, которую вы моделируете, такова, у вас не должно быть сомнений в том, чтобы моделировать ее как есть.

Если приложение было просто гигантской электронной таблицей, то вам не нужно моделировать иерархию композиции как она есть. В противном случае сделай это. Я не думаю, что это бесконечно глубоко. Просто держите получатели, которые возвращают коллекции, ленивыми, если заполнение таких коллекций стоит дорого, например, запрашивайте их в хранилище, которое извлекает их из базы данных. Под ленивым я имею в виду, не заселять их, пока они не понадобятся.


1

Да, вы можете моделировать его как реляционные таблицы

Result {
    Id
    ModelId
    Epoch
}

и т. д., что часто может привести к упрощению программирования и простому отображению в базе данных. но за счет потери жесткого кодирования ваших отношений


0

Я действительно не знаю, как решить эту проблему в Java, потому что Java построена на идее, что все является объектом. Вы не можете подавить промежуточные классы, заменив их словарем, потому что типы в ваших двоичных объектах данных неоднородны (например, отметка времени + список или строка + список ...). Альтернатива замены класса контейнера его полем (например, передача переменной «m_epoch» на «m_modules») просто плохая, потому что она создает неявный объект (вы не можете иметь один без другого) без особого выгода.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.