Подтвердить что ты не робот

Композиция против наследования в MVP

Я использую шаблон MVP для разработки приложения большого масштаба. Во время работы над разработкой я пришел к вопросу о том, следует ли использовать состав или наследование. Например: допустим, что у меня есть форма с именем Foo с полями A и B. В другой части приложения у меня есть форма Бар, которая имеет те же поля A и B, но дополнительное поле C.

В настоящее время код написан с использованием метода наследования, где вид формы Бар наследуется от формы Foo. Ведущие затем обрабатывают данные, немного отличающиеся от модели. Это работает довольно просто, но превосходит меня, следует ли следовать правилу "А", поскольку даже когда формы различны, они обрабатывают общие входы (A и B).

Однако здесь я думал о "композиции над наследованием" и "Принцип замещения Лискова" и подумал, что я должен быть используя композицию вместо наследования. Однако, поскольку я использую MVP, он был более сложным, чем ожидалось, потому что мне нужно иметь презентатора для формы Foo с полями A и B затем презентатор для Бар с полем C и ссылкой на презентатора Foo, чтобы он мог вводить поля A и B.

Проблема заключается в том, что он оказался больше кода, так как мне придется добавлять некоторые родоразрядники и сеттеры в презентаторе Foo, чтобы он мог передавать данные на Bar. Это так или иначе похоже на то, что я нарушаю MVP, чтобы обеспечить состав.

Итак, мои вопросы:

Действительно ли лучше для моего случая использовать композицию над наследованием? Почему?

Использует ли композиция "break" MVP?

4b9b3361

Ответ 1

Действительно ли лучше для моего случая использовать композицию над наследованием? Почему?

Да. Поскольку композиция более надежна, более безопасна, удобна в обслуживании, более открыта, более документирована и более понятна в больших приложениях. ИМХО.:)

Использует ли композиция "break" MVP?

Да. Это нарушает вид простого MVP, который вы сейчас делаете. Композиция позволяет вам выбирать, как связывать ваш код, и это очень хорошо для больших приложений. Он использует больше кода, потому что вам нужно уточнить, как вы связываетесь.

Для простого приложения очень разумно расти и стать хорошим кандидатом для перехода от простого наследования MVP к более сложной композиции. Это шаг развязки, который позволяет отвязать по-новому.

Это похоже на то, как многие простые веб-приложения переходят на приложения, управляемые API-интерфейсом. Это, по сути, развязка представлений пользователей на переднем конце от моделей хранилища.

Ответ 2

когда формы различны, они обрабатывают общие входы (A и B).

это означает, что ведущий Foo концептуально отличается от ведущего Bar и просто имеет общий вклад, поэтому они не должны быть связаны по наследству. извлеките код, который обрабатывает общие входы в класс утилиты и повторное использование, как в презентаторе Foo, так и в презентаторе Bar.

в случае, если концепция Foo изменяется, это не повлияет на Bar (и наоборот: если концепция Bar не может измениться без изменения концепции Foo, тогда это "есть A", и наследование может быть использовано действительно)

когда сомневаетесь, всегда предпочитайте композицию

Ответ 3

Более чистая композиция будет состоять из классов:

Модели: A, B, C, Foo, Bar
Просмотры: AView, BView, CView, FooView, BarView
Презентаторы: Атрибут, BPresentor, CPresentor, FooPresentor, BarPresentor

Где FooView содержит AView и BView, BarView содержит AView, BView и CView и у предикторов есть аналогичный состав.

Эта композиция делает A, B и C (вместе с их представлениями и презентаторами) модульными, так что вы можете смешивать и сопоставлять по своему усмотрению, а составные классы (Foo и Bar) имеют дело с интеграцией.

Это может использоваться вместе с наследованием: Если Bar является конкретным случаем Foo, то Bar должен наследовать от Foor, а BarPresentor может наследовать от FooPresentor. Тем не менее, я бы рассматривал наследование взглядов более в каждом случае, поскольку взгляды могут или не могут быть подходящими для наследования в зависимости от их поведения.

Ответ 4

Давайте начнем с основ, самое главное, что вы должны знать о классах, что подкласс всегда также является полным экземпляром суперкласса. Поэтому, если вы определяете переменную поля в суперклассе, это поле всегда создается, если вы создаете экземпляр подкласса. Вы можете использовать super.getVariable(), чтобы получить эту переменную в подклассе, чтобы повторно использовать поле (переменная класса, поле, флаг, что все то же самое в программировании OO). Но вы также можете просто вызвать subclassInstance.getVariable() извне, и вы получите одно и то же поле (без необходимости изменять его подклассом). Поэтому вам часто не нужно называть "супер" в вашем подклассе вообще, поскольку вы обычно просто хотите получить/установить поле этого суперкласса (включая абстрактные классы!) Извне. Поскольку вы всегда должны устанавливать переменные поля как частные, я всегда предлагаю никогда не называть "супер" для доступа к любым переменным поля (потому что даже с помощью супер вы не можете получить доступ к частным методам/области вашего суперкласса... на самом деле один из величайших ошибки в Java, поскольку он не может обеспечить полную инкапсуляцию дерева классов в отношении других классов... поэтому вам нужно обычно вызывать методы protected, такие как super.getField(), что раздражает, но необходимо).

Теперь давайте начнем с моделей данных: у вас есть modelA и modelB. Вы можете наследовать их из суперкласса или просто из объекта. Но если вы только наследуете Object, вы можете определить простой (Java!) Интерфейс и реализовать этот интерфейс в modelA и modelB. Затем вы обрабатываете эти два класса только через интерфейс (они оба являются "объектами интерфейса" и могут обрабатываться в целом). Если у вас есть modelC, состоящий из modelA и modelB, вы просто используете экземпляр (иногда также называемый "разыменованием" ) обеих моделей внутри modelC, больше не нужен код. Таким образом, вы можете выбрать эти модели как можно малыми и простыми ( "beans" ). Компонентная реализация, обычным способом для структур данных.

Если у вас есть GUI или формы, это выглядит иначе. Вероятно, у вас много общего кода, и вы не хотите разбить этот код на десятки разных классов, а затем потянуть компоненты вместе в классе контроллера/презентатора. Таким образом, вы можете определить абстрактный класс, который содержит все общие поля/флаги и несколько методов для их доступа и изменения. И затем вы вызываете эти общие методы из формы A и формы B и повторно используете код.

Говорит ли вам теория множеств? У вас есть два круга: A и B и пересечение A и B. Абстрактный класс - это пересечение, подклассы formA и formB - это установленные различия. Изучение правильных программных кодов... понимание теории множеств.; -)

Чтобы сказать это в ваших словах: большая часть кода формы Foo будет в абстрактном суперклассе Foobar, этот класс сможет обрабатывать A и B. Затем вы наследуете форму Foo и формируете Bar, а в то время как C, вероятно, в основном останется подмножеством Foobar, вы добавляете hability в Bar для обработки C, что заданное различие.

В конце концов, Бар не будет Foo в любое время, оба из них будут только Foobar. У вас есть новые общие поля/флаги? Нет проблем, вы переносите свой код в Foobar, и вы можете использовать его в обоих подклассах!

Но что, если в один прекрасный день вам понадобится третий компонент FooToo, который немного отличается от Foo? Нет проблем, создайте FooBarFoo абстрактный класс extends FooBar, а затем создайте Foo и FooToo как подклассы. Конечным результатом будет дерево классов, где корни (обычно) абстрактные классы, а листы - это реальные классы, эта структура обеспечивает максимальное повторное использование кода (и не изменяет имена классов, поэтому вам не нужно менять любой другой код с использованием класса Foo).

Вы сказали, что будете внедрять сеттер/получатель в свои формы (или его ведущий)? Затем вы также должны использовать модели modelA и modelB (но no modelC, поскольку C используется только в Bar и не Foo). Эти модели используются в качестве оболочки для переноса данных между Foo и Bar. И этот поток данных должен контролироваться ведущим, а не Fooили Бар.

Итак, ваш вопрос заканчивается так: что такое презентатор? Фактически, презентатор - это код, который запускает компоненты GUI и модели данных. Это "кадр", который использует GUI-компоненты в одной руке, и использует геттер/сеттер моделей данных с другой стороны. Это промежуточное ПО между двумя уровнями, слой GUI и слой данных, и даже между различными компонентами GUI и другой моделью данных.

Таким образом, обычно есть только два способа сделать это: без презентатора/контроллера или с ним. Без этого вам нужно будет скопировать-вставить много кода компонента swing в класс презентатора. И что? Правильно, когда вы используете компонент swing, вы всегда будете использовать шаблон (M) VP, это невозможно сделать другим!

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

Итак, когда мы начнем говорить о компонентах графического интерфейса и графическом интерфейсе. Возможно создание "как можно большего количества компонентов презентатора", так как вы могли бы взять простой HTML-сайт и сделать из него десятки сайтов PHP, используя метод "include (...)". Но я могу заверить вас, что компонентный дизайн не всегда улучшает ремонтопригодность кода! Если я могу сделать что-то только с одним классом, и я могу сделать это понятным и читаемым, я скорее пойду с одним классом, а не с десятью. Один презентатор = один класс, или, если быть более конкретным: один графический интерфейс/вкладка = один класс.

И снова, если у вас есть 2 похожих кадра/вкладки, но они не то же самое, что делать? Перенесите общий код в абстрактный класс и создайте 2 подкласса, не так ли? Нет, сначала вам нужно подумать о том, чем делится этот графический интерфейс. Разделили ли они флаги? Поэтому переместите флаг в абстрактный суперкласс. Но они просто ведут себя иначе? Ну, вы просто реализуете два разных метода внутри одного класса, и вы вызываете их каждый раз, когда вам это нужно. Это самое важное.

Чтобы сказать это в ваших словах: GUI Pres1 использует Foo, Bar, A, B и C. И GUI Pres2 находится только в другом классе, если он имеет разные флаги. В противном случае вы установите флаг Pres1 и флаг Pres2 и отметьте этот флаг внутри своих методов. По if(flag="Pres1"){} else if(flag="Pres2"){}. Таким образом, вы получите максимальную гибкость и возможность повторного использования кода.

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

"Компонент" всегда означает "что-то с конструктором". Но иногда вы просто используете метод, а не компонент для чего-то! Поэтому, если кто-то говорит вам, что "компонентный дизайн - это все", он говорит вам, что "конструкторский дизайн - это все". Но для создания конструкторов необходимо иметь полевые переменные/флаги! Без переменных поля совершенно бессмысленно создавать новый класс, только ради этого.

Внимание: "Компонент" означает не метод. Понятно, что вы будете использовать множество методов внутри вашего GUI-класса (ов), обрабатывая вещи очень легко, поэтому в конце вы просто вызываете несколько методов. Поэтому не смешивайте компоненты и методы! Я всегда предлагаю сильный метод-ориентированный дизайн, потому что он все об использовании меньших строк кода. Поэтому определите как можно больше методов... но также как меньше классов/компонентов, как вы можете.

Ответ 5

Конечно, когда Foo не расширяет Bar, вам нужно добавить больше кода, потому что у вас есть дополнительные геттеры и сеттеры. Но очень большое преимущество в том, что Foo больше не зависит от Bar. Это может показаться очень незначительным, но представьте, как это будет выглядеть, если вы используете inhenritance с более чем 50 классами... это было бы ад, без какой-либо логики, и это было бы очень сложно, если бы вам пришлось изменить используемый компонент в классе, который расширен несколькими другими классами.

По причине maintenace избегайте использования наследования. Как вы сказали, "Бар - это не Foo", поэтому Bar не должен расширять Foo. Для того, что я испытал, наследование никогда не является хорошим решением и должно использоваться только для семейства классов (например, при использовании составного шаблона).