Например, класс обычно имеет члены класса и методы, например:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Но после прочтения принципа единой ответственности и открытого закрытого принципа, я предпочитаю разделить класс на DTO и вспомогательный класс только статическими методами, например:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Я думаю, что это соответствует принципу Единой ответственности, потому что теперь ответственность CatData состоит в том, чтобы хранить только данные, не заботясь о методах (также для CatMethods). И это также соответствует принципу открытого закрытого типа, потому что добавление новых методов не должно изменять класс CatData.
Мой вопрос, это хорошо или анти-шаблон?
object-oriented
static-methods
dto
ocomfd
источник
источник
Ответы:
Вы показали две крайности («все частные и все (возможно, не связанные) методы в одном объекте» против «все публичное и никаких методов внутри объекта»). ИМХО хорошее ОО моделирование не из них , сладкое место где-то посередине.
Одним лакмусовым тестом того, какие методы или логика принадлежат классу, а что - внешнему, является проверка зависимостей, которые будут представлены методами. Методы, которые не вводят дополнительных зависимостей, хороши, если они хорошо подходят для абстракции данного объекта . Методы, которые требуют дополнительных внешних зависимостей (например, библиотека чертежей или библиотека ввода-вывода), редко подходят. Даже если бы вы делали зависимости исчезающими, используя внедрение зависимостей, я все равно дважды подумал бы, действительно ли необходимо размещать такие методы внутри класса домена.
Таким образом, вы не должны делать каждый член открытым и не должны реализовывать методы для каждой операции над объектом внутри класса. Вот альтернативное предложение:
Теперь
Cat
объект предоставляет достаточную логику, чтобы позволить окружающему коду легко реализоватьсяprintInfo
иdraw
без публичного раскрытия всех атрибутов. Подходящее место для этих двух методов, скорее всего , не является класс богаCatMethods
(так какprintInfo
иdraw
это , скорее всего , разные проблемы, поэтому я думаю , что это очень маловероятно , что они принадлежат к одному классу).Я могу себе представить,
CatDrawingController
который реализуетdraw
(и, возможно, использует внедрение зависимости для получения объекта Canvas). Я также могу представить другой класс, который реализует некоторый вывод и использование консолиgetInfo
(поэтомуprintInfo
может устареть в этом контексте). Но для принятия разумных решений необходимо знать контекст и то, какCat
класс будет фактически использоваться.Именно так я и интерпретировал критику Фаулерской модели предметной области - для логики многократного использования (без внешних зависимостей) сами доменные классы являются хорошим местом, поэтому их следует использовать для этого. Но это не означает, что нужно реализовывать какую-либо логику, а совсем наоборот.
Также обратите внимание, что в приведенном выше примере все еще остается место для принятия решения о (im) изменчивости. Если
Cat
класс не будет выставлять какие-либо сеттеры и самImage
является неизменяемым, этот дизайн позволит сделатьCat
неизменным (чего не будет подход DTO). Но если вы считаете, что неизменность не требуется или не помогает в вашем случае, вы также можете пойти в этом направлении.источник
getInfo()
может вводить в заблуждение человека, задающего этот вопрос. Это тонко смешивает обязанности по представлению, как этоprintInfo()
делает, побеждая цельDrawingController
классаDrawingController
. Я согласен с этимgetInfo()
иprintInfo()
ужасные имена. РассмотримtoString()
иprintTo(Stream stream)
Поздний ответ, но я не могу удержаться.
В большинстве случаев большинство правил, применяемых без размышления, в основном идут ужасно неправильно (включая это).
Позвольте мне рассказать вам историю о рождении объекта на фоне хаоса некоего правильного, быстрого и грязного процедурного кода, который произошел не по замыслу, а из отчаяния.
Мой стажер и я занимаемся парным программированием, чтобы быстро создать какой-то одноразовый код для очистки веб-страницы. У нас нет абсолютно никаких оснований ожидать, что этот код будет жить долго, поэтому мы просто демонстрируем то, что работает. Мы берем всю страницу в виде строки и отбираем все, что нам нужно, самым удивительным образом. Не суди. Оно работает.
Теперь, делая это, я создал несколько статических методов для измельчения. Мой стажер создал класс DTO, очень похожий на ваш
CatData
.Когда я впервые посмотрел на DTO, это меня подкосило. Годы ущерба, нанесенного Java моему мозгу, заставили меня отшатнуться в общественных местах. Но мы работали в C #. C # не нуждается в преждевременных методах получения и установки, чтобы сохранить ваше право делать данные неизменяемыми или инкапсулированными позже. Без изменения интерфейса вы можете добавлять их в любое время. Может быть, просто так вы можете установить точку останова. Все, не говоря своим клиентам об этом. Да, C #. Бу Ява.
Так что я держал язык за зубами. Я наблюдал, как он использовал мои статические методы для инициализации этой вещи перед ее использованием. У нас было около 14 из них. Это было ужасно, но у нас не было причин для беспокойства.
Тогда нам это было нужно в других местах. Мы обнаружили, что хотим скопировать и вставить код. 14 строк инициализации швыряются вокруг. Становилось больно. Он колебался и спросил меня об идеях.
Я неохотно спросил: "Вы считаете объект?"
Он оглянулся на своего DTO и смутил лицо в замешательстве. «Это объект».
«Я имею в виду реальный объект»
«А?»
«Позвольте мне показать вам кое-что. Вы решаете, полезно ли это»
Я выбрал новое имя и быстро набрал что-то похожее на это:
Это ничего не делало, статические методы еще не делали. Но теперь я поместил 14 статических методов в класс, где они могли бы быть наедине с данными, с которыми они работали.
Я не заставлял своего интерна использовать это. Я просто предложил это и позволил ему решить, хочет ли он придерживаться статических методов. Я пошел домой, думая, что он, вероятно, будет придерживаться того, что у него уже было. На следующий день я обнаружил, что он использовал его в нескольких местах. Он расшифровал остальную часть кода, который все еще был уродливым и процедурным, но теперь эта сложность была скрыта от нас за объектом. Это было немного лучше.
Теперь убедитесь, что каждый раз, когда вы получаете доступ к этому, он выполняет большую часть работы. DTO - хорошее быстрое кешируемое значение. Я беспокоился об этом, но понял, что могу добавить кеширование, если нам когда-нибудь понадобится, не касаясь какого-либо кода. Так что я не собираюсь беспокоиться, пока мы не заботимся.
Я говорю, что вы всегда должны придерживаться ОО-объектов, а не ОТО? Нет. DTO сияет, когда вам нужно пересечь границу, которая удерживает вас от движущихся методов. У DTO есть свое место.
Но так же и объекты OO. Узнайте, как использовать оба инструмента. Узнайте, что стоит каждый. Научитесь решать проблему, ситуацию и стажера. Догма не твой друг здесь.
Поскольку мой ответ уже смехотворно длинен, позвольте мне разубедить вас в некоторых заблуждениях при рассмотрении вашего кода.
Где твой конструктор? Это не показывает мне достаточно, чтобы знать, полезно ли это.
Вы можете делать много глупостей во имя принципа единой ответственности. Я могу утверждать, что строки Cat и строки Cat должны быть разделены. Это методы рисования и изображения должны иметь свой собственный класс. То, что ваша работающая программа - это единственная ответственность, поэтому у вас должен быть только один класс. :П
Для меня лучший способ следовать принципу единой ответственности - найти хорошую абстракцию, которая позволит вам поместить сложность в коробку, чтобы вы могли ее скрыть. Если вы можете дать ему хорошее имя, которое удерживает людей от удивления тем, что они находят, когда они заглядывают внутрь, вы довольно хорошо следовали этому. Ожидать, что это будет диктовать больше решений, чем проблем. Честно говоря, оба ваших кодовых списка делают это, поэтому я не понимаю, почему здесь важен SRP.
Ну нет. Принцип открытого закрытия не связан с добавлением новых методов. Речь идет о возможности изменить реализацию старых методов и необходимости ничего не редактировать. Ничего, что использует тебя, а не твои старые методы. Вместо этого вы пишете новый код где-то еще. Некоторая форма полиморфизма сделает это хорошо. Не вижу этого здесь.
Ну, черт возьми, откуда мне знать? Смотри, в любом случае это имеет свои преимущества и издержки. Когда вы отделяете код от данных, вы можете изменить любой из них без необходимости перекомпилировать другой. Может быть, это очень важно для вас. Может быть, это просто делает ваш код бессмысленно сложным.
Если это заставляет вас чувствовать себя лучше, вы не так уж далеки от того, что Мартин Фаулер называет объектом параметров . Вам не нужно принимать только примитивы в свой объект.
То, что я хотел бы, чтобы вы сделали, - это выработайте понимание того, как выполнять разделение, или нет, в любом стиле кодирования. Потому что, хотите верьте, хотите нет, вам не нужно выбирать стиль. Вы просто должны жить с вашим выбором.
источник
Вы наткнулись на тему дебатов, которая уже более десяти лет вызывает споры среди разработчиков. В 2003 году Мартин Фаулер придумал фразу «Модель анемичной области» (ADM), чтобы описать это разделение данных и функциональности. Он - и другие, которые с ним согласны - утверждают, что «Богатые доменные модели» (смешение данных и функциональности) - это «правильная ОО», и что подход ADM - это не шаблон OO.
Всегда были те, кто отклонял этот аргумент, и эта сторона аргумента становилась все громче и смелее в последние годы с принятием большим количеством разработчиков методов функциональной разработки. Такой подход активно поощряет разделение данных и функциональных проблем. Данные должны быть настолько неизменными, насколько это возможно, поэтому инкапсуляция изменяемого состояния не станет проблемой. В таких ситуациях нет смысла прикреплять функции непосредственно к этим данным. Является ли тогда "не ОО" или нет, не представляет никакого интереса для таких людей.
Независимо от того, на какой стороне забора вы сидите (я сижу очень твердо на стороне «Кстати, Мартин Фаулер говорит о куче старых вещей»), ваше использование статических методов для
printInfo
иdraw
почти повсеместно осуждается. Статические методы трудно подделать при написании модульных тестов. Поэтому, если они имеют побочные эффекты (такие как печать или рисование на каком-либо экране или другом устройстве), они не должны быть статичными или должны иметь выходное местоположение, передаваемое в качестве параметра.Таким образом, вы можете иметь интерфейс:
И реализация, которая внедряется в остальную часть вашей системы во время выполнения (с другими реализациями, используемыми для тестирования):
Или добавьте дополнительные параметры, чтобы удалить побочные эффекты от этих методов:
источник
DTOs - объекты транспорта данных
полезны только для этого. Если вы собираетесь шунтировать данные между программами или системами, желательно, чтобы DTO представляли управляемый поднабор объекта, который касается только структуры данных и форматирования. Преимущество заключается в том, что вам не нужно синхронизировать обновления сложных методов в нескольких системах (если базовые данные не меняются).
Весь смысл ОО состоит в том, чтобы связывать данные и код, действующий на этих данных, вместе. Разделение логического объекта на отдельные классы обычно является плохой идеей.
источник
Многие шаблоны проектирования и принципы противоречат друг другу, и в конечном итоге только ваше суждение как хорошего программиста поможет вам решить, какие шаблоны следует применять для конкретной проблемы.
На мой взгляд, принцип « Сделай самое простое, что могло бы работать» должен победить по умолчанию, если у тебя нет веских причин усложнять дизайн. Так что в вашем конкретном примере я бы выбрал первый дизайн, который является более традиционным объектно-ориентированным подходом с данными и функциями вместе в одном классе.
Вот пара вопросов, которые вы можете задать себе по поводу приложения:
Ответ «да» на любой из этих вопросов может привести к более сложному дизайну с отдельными DTO и функциональными классами.
источник
Это хороший шаблон?
Вы, вероятно, получите здесь разные ответы, потому что люди следуют разным школам мысли. Итак, прежде чем я начну: этот ответ соответствует объектно-ориентированному программированию, как он описан Робертом К. Мартином в книге «Чистый код».
"Правда" ООП
Насколько я знаю, есть 2 интерпретации ООП. Для большинства людей это сводится к тому, что «объект - это класс».
Тем не менее, я нашел другую школу мысли более полезной: «Объект - это то, что что-то делает». Если вы работаете с классами, каждый класс будет одним из двух: « держатель данных » или объект . Держатели данных хранят данные (дух), объекты делают вещи. Это не значит, что у владельца данных не может быть методов! Подумайте о
list
: А наlist
самом деле ничего не делает , но у него есть методы, чтобы обеспечить способ хранения данных .Держатели данных
Вы
Cat
яркий пример держателя данных. Конечно, вне вашей программы кошка - это что-то, что делает что-то , но внутри вашей программы aCat
- это Stringname
, intweight
и Imageimage
.То, что держатели данных ничего не делают, также не означает, что они не содержат бизнес-логики! Если у вашего
Cat
класса есть конструктор, требующийname
,weight
иimage
вы успешно инкапсулировали бизнес-правило, согласно которому у каждого кота есть такие. Если вы можете изменитьweight
после того, какCat
класс был создан, это инкапсулированное другое бизнес-правило. Это очень простые вещи, но это просто означает, что очень важно не понимать их неправильно - и таким образом вы гарантируете, что это невозможно сделать неправильно.Объекты
Объекты что-то делают. Если вы хотите очистить * объекты, мы можем изменить это на «Объекты делают одно ».
Печать информации о кошке - это работа объекта. Например, вы можете вызвать этот объект "
CatInfoLogger
" с помощью открытого методаLog(Cat)
. Это все, что нам нужно знать извне: этот объект регистрирует информацию о кошке, и для этого ему нуженCat
.Внутри этот объект будет иметь ссылки на все, что ему нужно для выполнения своей единственной обязанности - регистрировать кошек. В этом случае это только ссылка на
System
forSystem.out.println
, но в большинстве случаев это будут частные ссылки на другие объекты. Если логика форматирования вывода перед печатью становится слишком сложной,CatInfoLogger
можно просто получить ссылку на новый объектCatInfoFormatter
. Если позже, каждый журнал должен быть также записан в файл, вы можете добавитьCatToFileLogger
объект, который делает это.Держатели данных против объектов - сводка
Теперь, зачем ты все это делаешь? Потому что теперь изменение класса меняет только одну вещь ( способ регистрации кошки в примере). Если вы меняете объект, вы меняете только способ выполнения определенного действия; если вы меняете владельца данных, вы изменяете только то, какие данные хранятся и / или каким образом они хранятся.
Изменение данных может означать, что способ что-то сделать тоже должен измениться, но это изменение не произойдет, пока вы сами не сделаете это, изменив ответственный объект.
Overkill?
Это решение может показаться немного излишним. Позвольте мне быть откровенным: это так . Если вся ваша программа настолько велика, как в примере, не делайте ничего из вышеперечисленного - просто выберите любой из предложенных вами способов, подбрасывая монеты. Там нет никакой опасности в заблуждение другого кода , если нет никакого другого кода.
Однако любое «серьезное» (= больше, чем сценарий или 2) программное обеспечение, вероятно, достаточно велико, чтобы получить выгоду от такой структуры.
Ответ
Превращение «большинства классов» (без какой-либо дополнительной квалификации) в DTO / держатели данных не является анти-паттерном, потому что на самом деле это не какой-либо паттерн - он более или менее случайный.
Однако разделение данных и объектов рассматривается как хороший способ сохранить ваш код в чистоте *. Иногда вам понадобится только плоский «анемичный» DTO и все. В других случаях вам нужны методы для обеспечения соблюдения способа хранения данных. Только не связывайте функциональность вашей программы с данными.
* Это «Чисто» с заглавной буквой C, как название книги, потому что - помните первый абзац - речь идет об одной школе мысли, в то время как есть и другие.
источник