Как я должен рефакторинг это? - PullRequest
3 голосов
/ 30 июня 2010

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

С добавлением последнего клиента, я заметил, что будет много дублирования кода от другого клиента, но другой клиент никак не связан с ним.

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

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

Ответы [ 7 ]

5 голосов
/ 30 июня 2010

Рассмотрите возможность использования базового класса abstract с abstract или virtual членами. Абстрактные члены по существу эквивалентны элементам интерфейса (они не имеют встроенного поведения, они только гарантируют, что метод существует), тогда как virtual члены имеют реализацию по умолчанию, которая может быть переопределена производными классами.

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

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

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

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


Моя главная мысль заключается в том, что ООП имеет спектр того, сколько или мало функциональности в базовых / абстрактных / конкретных классах. Там нет ответа серебряной пули, иногда ваши базовые классы будут скелеты, а иногда они будут полностью выделены; все зависит от конкретной проблемы.

2 голосов
/ 30 июня 2010

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

1 голос
/ 30 июня 2010

Я бы порекомендовал шаблон посетителя:

http://en.m.wikipedia.org/wiki/Visitor_pattern

, а также шаблон посредника:

http://en.m.wikipedia.org/wiki/Mediator_pattern

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

1 голос
/ 30 июня 2010

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

  • Используйте Стратегию для дублированного кода. У меня был самый большой успех, когда стратегия заключалась в классе, реализующем известный интерфейс (один класс на альтернативную стратегию). Часто в таких случаях я использую некую форму структуры внедрения зависимостей (обычно StructureMap) для передачи соответствующей стратегии / стратегий классу.
  • Использовать некоторый класс шаблонов (или методы шаблонов) для общих элементов.
  • Используйте Декоратор , чтобы добавить определенные функции для некоторых основных клиентов.

STW предложил мне кое-что прояснить, что я имею в виду под «Стратегией» и чем это отличается от обычного наследования. Я предполагаю, что наследование - это то, с чем вы очень хорошо знакомы - что-то (обычно это метод - abstract или virtual) в базовом классе заменяется альтернативной реализацией в производном классе.

Стратегия (по крайней мере, как я обычно ее использую) обычно реализуется совершенно другим классом. Часто все, что будет содержать этот класс, - это реализация для одной заменяемой операции. Например, если «операция» заключается в выполнении некоторой проверки, у вас может быть NullValidationStrategy, который ничего не делает, и ParanoidValidationStrategy, который гарантирует, что каждый McGuffin имеет правильную высоту, ширину и определенный оттенок синего. Причина, по которой я обычно реализую каждую стратегию в своем собственном классе, заключается в том, что я стараюсь следовать принципу единственной ответственности , который может упростить повторное использование кода позже.

Как я упоминал выше, я обычно использую инфраструктуру Dependency Injection (DI), чтобы «внедрить» соответствующую стратегию через конструктор класса, но аналогичные результаты могут быть получены с помощью других механизмов - например, имеющий SetSomeOperationStrategy(ISomeOperation StrategyToUse) метод или свойство, которое содержит ссылку на стратегию. Если вы не используете DI, и стратегия всегда будет одинаковой для данного типа клиента, вы всегда можете установить правильный выбор при создании класса. Если стратегия не будет одинаковой для каждого экземпляра данного типа клиента, то вам, вероятно, нужен какой-то клиент фабрика (часто достаточно фабричный метод ).

1 голос
/ 30 июня 2010

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

0 голосов
/ 30 июня 2010

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

0 голосов
/ 30 июня 2010

Я бы пошёл с ответом Спинона (получил, по крайней мере, мой голос), но он был коротким, поэтому позвольте мне уточнить:

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

Я предполагаю, что вы знаете клиента на каком-то раннем этапе программы. Поэтому для ninject вам может потребоваться определить «Модуль» для каждого клиента и загрузить его в ядро, в зависимости от клиента.

Таким образом, я бы создал модуль «без настройки» и создал бы модуль «ClientX» для каждого особого случая, в котором вместо этого используется «Bind.To ()». Вы в конечном итоге с

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

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

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

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