Объектно-ориентированный дизайн классов

12

Меня интересует хороший объектно-ориентированный дизайн классов. В частности, мне трудно выбирать между этими вариантами:

  1. статический метод против экземпляра
  2. метод без параметров или возвращаемого значения против метода с параметрами и возвращаемым значением
  3. перекрывающиеся и отличные функциональные возможности метода
  4. приват против публичного метода

Пример 1:

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

XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();

Пример 2:

В этой реализации используются статические методы с возвращаемыми значениями и параметрами, с перекрывающимися функциями и закрытыми методами.

Document result = XmlReader.readXml(url); 

В первом примере все методы являются публичными экземплярами, что упрощает их модульное тестирование. Хотя все методы различны, readXml () зависит от openUrl (), в этом openUrl () должен быть вызван первым. Все данные объявляются в полях экземпляра, поэтому нет никаких возвращаемых значений или параметров ни в одном методе, кроме как в конструкторе и методах доступа.

Во втором примере только один метод является общедоступным, остальные являются закрытыми статическими, что затрудняет их модульное тестирование. Методы перекрываются в том, что readXml () вызывает openUrl (). Там нет полей, все данные передаются как параметры в методах, и результат возвращается немедленно.

Каким принципам я должен следовать, чтобы делать правильное объектно-ориентированное программирование?

siamii
источник
3
Статические вещи плохи, когда вы делаете многопоточность. На днях у меня был статический XMLWriter, такой как XMLWriter.write (data, fileurl). Однако, поскольку у него был частный статический FileStream, использующий этот класс из нескольких потоков одновременно, он заставил второй поток перезаписать первые потоки FileStream, что вызвало ошибку, которую было бы очень трудно найти. Статические классы со статическими членами + многопоточность - рецепт катастрофы.
За Александерссон
1
@Paxinum. Проблема, которую вы описываете, является проблемой состояния, а не «статической» проблемой. Если бы вы использовали синглтон с нестатическими элементами, у вас возникла бы та же проблема с многопоточностью.
mike30
2
@Per Alexandersson Статические методы неплохие по отношению к параллелизму. Статическое состояние плохое. Вот почему функциональное программирование, в котором все методы являются статическими, очень хорошо работает в параллельных ситуациях.
Юлий Боннер

Ответы:

11

Пример 2 довольно плох для тестирования ... и я не имею в виду, что вы не можете проверить внутренности. Вы также не можете заменить свой XmlReaderобъект на фиктивный объект, так как у вас его вообще нет.

Пример 1 излишне сложен в использовании. Как насчет

XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();

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

Такие вещи, как открытие URL, чтение XML, преобразование байтов в строки, синтаксический анализ, закрытие сокетов и т. Д., Неинтересны. Создание объекта и его использование важно.

Так что ИМХО правильный ОО-Дизайн должен сделать общедоступными только две вещи (если вам не нужны промежуточные шаги по какой-то причине). Статика это зло.

maaartinus
источник
-1. Вы действительно МОЖЕТЕ заменить XmlReader на фиктивный объект. Не с мозговым фреймворком с открытым исходным кодом, а с хорошим промышленным классом, который вы можете;) Это стоит пару сотен долларов на одного разработчика, но творит чудеса, чтобы протестировать закрытую функциональность в публикуемых вами API.
TomTom
2
+1 за то, что не слышал о продажах TomTom. Когда я хочу один лайнер, я бы предпочел написать что-то вроде Document result = new XmlReader(url).getDocument();Почему? Так что я могу обновить его Document result = whateverReader.getDocument();и whateverReaderвручил мне что-то еще.
candied_orange
6

В том-то и дело, что нет единственно правильного ответа и нет абсолютного определения «правильного объектно-ориентированного проектирования» (некоторые люди предложат вам один, но они наивны ... дают им время).

Все сводится к вашим целям.

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

Итак, что ПРАВДА для проблемы, которую вы решаете? Каковы жалобы людей, которым нужно использовать ваши классы для работы с XML? Что сложного в их работе? Какой код они пытаются написать, который окружает вызовы вашей библиотеки, и как вы можете помочь этому потоку лучше для них?

Хотели бы они больше краткости? Хотели бы они, чтобы он был очень умен при вычислении значений по умолчанию для параметров, чтобы им не приходилось указывать много (или что-то еще), и он угадывает правильно? Можете ли вы автоматизировать задачи настройки и очистки, которые требуются вашей библиотеке, чтобы они не могли забыть эти шаги? Что еще вы можете сделать для них?

Черт, то, что вам, вероятно, нужно сделать, это написать 4 или 5 различных способов, затем надеть шляпу потребителя и написать код, который использует все 5, и посмотреть, что лучше. Если вы не можете сделать это для всей вашей библиотеки, то сделайте это для подмножества. И вам нужно добавить некоторые дополнительные альтернативы в свой список - как насчет свободного интерфейса, или более функционального подхода, или именованных параметров, или чего-то, основанного на DynamicObject, чтобы вы могли составить осмысленные «псевдо-методы», которые помогут им вне?

Почему jQuery король прямо сейчас? Потому что Resig и команда следовали этому процессу, пока не столкнулись с синтаксическим принципом, который невероятно уменьшил количество кода JS, необходимого для работы с dom и событиями. Этот синтаксический принцип не был понятен им или кому-либо еще, когда они начали. Они НАШЛИ это.

Как программист, это то, что ваше высшее призвание. Ты нащупываешься в темноте, пытаясь что-то делать, пока не найдешь. Когда вы это сделаете, вы будете знать. И вы дадите своим пользователям огромный скачок производительности. И это то, что дизайн (в области программного обеспечения) это все о.

Чарли Флауэрс
источник
1
Это подразумевает, что нет никаких различий между лучшими практиками и другими практиками, кроме того, как это «чувствует». Вот как вы попадаете с неуправляемыми шариками грязи - потому что многим разработчикам, выходящим за границы классов и т. Д., «Кажется» просто чертовски замечательно.
Эми Бланкеншип
@ Эми Бланкеншип, я бы однозначно сказал, что нет единственного «лучшего способа» сделать выбор, о котором спрашивает ОП. Это зависит от миллиона вещей и миллионов степеней свободы. Тем не менее, я думаю, что есть место для «передового опыта», и это в командной среде, где определенные выборы уже сделаны, и нам нужно, чтобы остальная часть команды оставалась в согласии с этими предыдущими решениями. Другими словами, в конкретном контексте могут быть причины обозначать определенные вещи как «лучшие практики». Но ОП не дал никакого контекста. Он что-то строит и ...
Чарли Флауэрс
... он сталкивается со всеми этими возможными вариантами. Нет единого «правильного ответа» на этот выбор. Это обусловлено целями и болевыми точками системы. Я гарантирую вам, что программисты на Haskell не считают, что все методы должны быть методами экземпляра. И программисты ядра Linux не думают, что сделать вещи доступными для TDD вообще очень важно. И программисты игр на C ++ часто предпочитают связывать свои данные в плотную структуру данных в памяти, а не инкапсулировать все в объекты. Каждая «Лучшая практика» является только «наилучшей практикой» в данном контексте и является анти-паттерном в некотором другом контексте.
Чарли Флауэрс
@AmyBlankenship Еще одна вещь: я не согласен с тем, что выход за границы класса "кажется просто чудовищным". Это приводит к тому, что шарики грязи не поддаются ремонту, которые ощущаются ужасно . Я думаю, что вы пытаетесь решить проблему, связанную с тем, что некоторые работники неряшливы / не мотивированы / очень неопытны. В этом случае кто-то, кто является осторожным, мотивированным и опытным, должен сделать ключевые выборы и назвать их "Лучшими методами". Но человек, выбирающий эти «лучшие практики», все еще делает выбор, основываясь на том, что «чувствует себя хорошо», и нет правильных ответов. Вы просто контролируете, кто делает выбор.
Чарли Флауэрс
Я работал с несколькими программистами, которые считали себя руководителями высшего звена, и об этом так думали менеджеры, которые твердо верили, что статика и синглтоны - это абсолютно правильный способ решения проблем общения. Статическая часть этого вопроса даже не была бы задана, если бы охватывание границ Класса таким образом «чувствовало бы» разработчиков, а также ответ, который поддерживает статическую альтернативу, не набрал бы голосов.
Эми Бланкеншип
3

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

Для модульного тестирования я бы просто протестировал интерфейс, а не внутренние компоненты, если вы действительно хотите затем переместить внутренние компоненты из частного в защищенный.


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

1. Статика против экземпляра

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

И худшая практика, о которой я могу подумать, - это Global State, включая упомянутую вами статику и любимый всеми синглтон. Некоторые выдержки из классической статьи Миско Хевери на эту тему .

Чтобы действительно понять зависимости, разработчики должны прочитать каждую строку кода. Это вызывает Spooky Action на расстоянии: при запуске наборов тестов глобальное состояние, измененное в одном тесте, может вызвать неожиданный сбой последующего или параллельного теста. Прервите статическую зависимость, используя ручное или Guice внедрение зависимости.

Spooky Action на расстоянии - это когда мы запускаем одну вещь, которая, по нашему мнению, изолирована (так как мы не передали никаких ссылок), но неожиданные взаимодействия и изменения состояния происходят в удаленных местах системы, о которых мы не говорили объекту. Это может произойти только через глобальное состояние.

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

Это сводится к тому, что вы не должны предоставлять статические ссылки на все, что имеет какое-то сохраненное состояние. Единственное место, где я использую статику - это перечисляемые константы, и у меня есть опасения даже по этому поводу.

2. Методы с входными параметрами и возвращаемыми значениями по сравнению с методами без

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

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

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

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

3. Перекрывающиеся против Различных

Я не понимаю вопроса. Каким будет преимущество в двух перекрывающихся методах?

4. Частное против публичного

Не выставляйте ничего, что вам не нужно подвергать. Тем не менее, я не большой поклонник частного, либо. Я не разработчик C #, но разработчик ActionScript. Я потратил много времени на код Adobe Flex Framework, который был написан около 2007 года. И они сделали очень плохой выбор того, что делать приватным, что превращает их в кошмар, пытаясь расширить свои классы.

Таким образом, если вы не считаете себя лучшим архитектором, чем разработчики Adobe примерно в 2007 году (из вашего вопроса я бы сказал, что у вас есть еще несколько лет, прежде чем у вас появится возможность заявить об этом), вы, вероятно, захотите просто по умолчанию использовать защищенный ,


Есть некоторые проблемы с вашими примерами кода, которые означают, что они плохо спроектированы, поэтому невозможно выбрать A или B.

Во-первых, вы, вероятно, должны отделить создание вашего объекта от его использования . Таким образом, вы обычно не имеете new XMLReader()права рядом с тем, где оно используется.

Кроме того, как говорит @djna, вы должны инкапсулировать методы, используемые в вашем XML Reader, так что ваш API (пример экземпляра) может быть упрощен до:

_document Document = reader.read(info);

Я не знаю, как работает C #, но, поскольку я работал с рядом веб-технологий, я подозреваю, что вы не всегда сможете сразу вернуть XML-документ (за исключением, возможно, в качестве обещания или будущего типа). объект), но я не могу дать вам совет о том, как обрабатывать асинхронную загрузку в C #.

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

Эми Бланкеншип
источник
2

Сосредоточьтесь на перспективе клиента.

IReader reader = new XmlReader.readXml(url);  // or injection, or factory or ...
Document document = reader.read();

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

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

Основной метод для проверки - read (). Внутренние методы можно сделать видимыми для тестовых программ, сделав их ни общедоступными, ни закрытыми, и поместив тесты в один и тот же пакет - тесты по-прежнему можно хранить отдельно от выпущенного кода.

DJNA
источник
Методы с видимостью по умолчанию все еще видны, если набор тестов находится в другом проекте?
Сиами
Ява не знает о проектах. Проекты представляют собой конструкцию IDE. Компилятор и JVM смотрят на пакеты, в которых находятся тестируемый класс и класс тестера - один и тот же пакет, видимость по умолчанию разрешена. В Eclipse я использую один проект с двумя разными исходными каталогами. Я только что попробовал с двумя проектами, это работает.
2

статический метод против экземпляра

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

метод без параметров или возвращаемого значения против метода с параметрами и возвращаемым значением

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

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

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

приват против публичного метода

Обычно геттеры, сеттеры и конструкторы должны быть публичными. Все остальное, что вы хотите попытаться сохранить в тайне, если нет случая, когда другой класс должен выполнить его. Сохранение методов по умолчанию для private поможет поддерживать Encapsulation . То же самое касается полей, по умолчанию привыкайте к приватным

Erich
источник
1

Я не буду отвечать на ваш вопрос, но думаю, что проблема возникает из-за используемых вами терминов. Например

XmlReader.read => twice "read"

Я думаю, что вам нужен XML, поэтому я создам объект XML, который может быть создан из текстового типа (я не знаю C # ... в Java это называется String). Например

class XML {
    XML(String text) { [...] }
}

Вы можете проверить это, и это ясно. Затем, если вам нужна фабрика, вы можете добавить фабричный метод (и он может быть статическим, как во втором примере). Например

class XML {
    XML(String text) { [...] }

    static XML fromUrl(url) { [...] }

}
sixro
источник
0

Вы можете следовать некоторым простым правилам. Это помогает, если вы понимаете причины для правил.

статический метод против экземпляра

Для методов вам не нужно принимать это решение осознанно. Если кажется, что ваш метод не использует никаких элементов поля (ваш любимый анализатор должен вам об этом сказать), вам нужно добавить ключевое слово static.

метод без параметров или возвращаемого значения против метода с параметрами и возвращаемым значением

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

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

Я не думаю, что это проблема. Методы, вызывающие другие методы, хороши, если это разбивает задачи на более мелкие отчетливо функциональные блоки.

приват против публичного метода

Сделайте все частным, если вам не нужно, чтобы это было публично. Пользователь вашего класса может обходиться без шума, он хочет видеть только то, что для него важно.

Мартин Маат
источник