Рефакторинг .NET, СУХОЙ. двойное наследование, доступ к данным и разделение интересов - PullRequest
5 голосов
/ 27 августа 2010

Предыстория:

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

Код частично основан на одном из рефакторингов @ JohnMacIntyre , о которых я недавно убедил его написать в блоге на http://whileicompile.wordpress.com/2010/08/24/my-clean-code-experience-no-1/. IЯ немного изменил код, как обычно, для того, чтобы продвинуть код на один уровень дальше - обычно просто для того, чтобы увидеть, какой дополнительный пробег я могу извлечь из концепции ... в любом случае, мои причины в значительной степени не имеют значения.

Часть моего уровня доступа к данным основана на следующей архитектуре:

abstract public class AppCommandBase : IDisposable { }

Содержит базовые элементы, такие как создание объекта команды и очистка после удаления AppCommand.Все мои объекты базы команд являются производными от этого.

abstract public class ReadCommandBase<T, ResultT> : AppCommandBase

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

abstract public class ReadItemCommandBase<T, FilterT> : ReadCommandBase<T, T> { }

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

public class MyTableReadItemCommand : ReadItemCommandBase<MyTableClass, Int?> { }

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

Теперь у меня также есть эта структура для моего ReadList...

abstract public ReadListCommandBase<T> : ReadCommandBase<T, IEnumerable<T>> { }
public class MyTableReadListCommand : ReadListCommandBase<MyTableClass> { }

Разница в том, что классы List содержат свойства, относящиеся к генерации списка (т. Е. PageStart, PageSize, Sort и возвращает IEnumerable) по сравнению с возвратом одного объекта DataObject (для которого требуется только фильтр).который идентифицирует уникальную запись).

Проблема:

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

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

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

Если кому-то нужна полная база кода, чтобы увидеть, о чем я пишу, у меня есть прототип решения для моего DropBox по адресу http://dl.dropbox.com/u/3029830/Prototypes/Prototype%20-%20DAL%20Refactor.zip. Код, о котором идет речь, находится вПроект DataAccessLayer.

PS Это не часть текущего активного проекта, это скорее головоломка для моего собственного развлечения.

Заранее спасибо, ребята, я ценю это.

Ответы [ 5 ]

4 голосов
/ 27 августа 2010

Отделить обработку результатов от извлечения данных. Ваша иерархия наследования уже достаточно глубока в ReadCommandBase.

Определить интерфейс IDatabaseResultParser. Реализуйте ItemDatabaseResultParser и ListDatabaseResultParser, оба с параметром конструктора типа ReadCommandBase (и, возможно, также преобразуйте его в интерфейс).

Когда вы вызываете IDatabaseResultParser.Value (), он выполняет команду, анализирует результаты и возвращает результат типа T.

Ваши команды сосредоточены на извлечении данных из базы данных и возвращении их в виде кортежей некоторого описания (фактических кортежей или массивов массивов и т. Д. И т. Д.), А ваш анализатор фокусируется на преобразовании кортежей в объекты любого типа, который вам нужен. Посмотрите NHibernates IResultTransformer, чтобы узнать, как это может работать (и, вероятно, это лучшее имя, чем IDatabaseResultParser).

Пользуйся композицией по наследству.

Посмотрев на образец, я пойду еще дальше ...

  1. Удалите AppCommandBase - он не добавляет значения в вашу иерархию наследования, так как все, что он делает, это проверяет, что соединение не является нулевым и открытым, и создает команду.
  2. Отделение построения запроса от выполнения запроса и синтаксического анализа результата - теперь вы можете значительно упростить реализацию выполнения запроса, поскольку это либо операция чтения, которая возвращает перечисление кортежей, либо операция записи, которая возвращает количество затронутых строк.
  3. Ваш построитель запросов может быть объединен в один класс для включения разбиения на страницы / сортировки / фильтрации, однако может быть проще построить некоторую форму ограниченной структуры вокруг них, чтобы вы могли разделить разбиение на страницы и сортировку и фильтрацию. Если бы я делал это, я бы не стал создавать запросы, я просто написал бы sql внутри объекта, который позволял бы мне передавать некоторые параметры (эффективно хранимые процедуры в c #).

Итак, теперь у вас есть IDatabaseQuery / IDatabaseCommand / IResultTransformer и почти нет наследования =)

1 голос
/ 27 августа 2010

Я думаю, что короткий ответ заключается в том, что в системе, где множественное наследование было объявлено вне закона «для вашей защиты», стратегия / делегирование является прямой заменой . Да, у вас все еще есть параллельная структура, например, свойство для объекта делегата. Но это сводится к минимуму в пределах возможного в пределах языка.

Но давайте отступим от простого ответа и широко рассмотрим ....

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

Проблема, с которой вы столкнетесь при принятии этого решения, которое я рекомендую, заключается в том, что вы уже пошли на уступки в своем дизайне: вы оптимизируете для другого SQL в случаях "item" и "list". Сохранение этого как есть помешает вам, несмотря ни на что, потому что вы дали им равные счета, поэтому они обязательно должны быть братьями и сестрами. Так что я бы сказал, что вашим первым шагом в попытке выйти из этого «локального максимума» элегантности дизайна было бы откатить эту оптимизацию и рассматривать отдельный элемент таким, какой он есть на самом деле: особый случай списка, с одним элемент. Вы всегда можете попытаться повторно ввести оптимизацию для отдельных элементов позже. Но подождите, пока вы не решите проблему элегантности, которая вас сейчас раздражает.

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

0 голосов
/ 13 сентября 2010

Я не очень внимательно изучил ваш сценарий, но у меня есть некоторые соображения по поводу проблемы двойной иерархии в C #.Чтобы разделить код в двойной иерархии, нам нужна другая конструкция в языке: либо миксин, черта (pdf) ( C # исследование -pdf ) или роль (какв Perl 6 ).C # позволяет очень легко обмениваться кодом с наследованием (что не является правильным оператором для повторного использования кода) и очень трудоемким делить код с помощью композиции (вы знаете, вы должны написать весь этот код делегирования вручную).

Есть способы получить вид mixin в C #, но это не идеально.

Язык Oxygene ( скачать ) (Object Pascal для .NET) также имеет интересную функцию для делегирования интерфейса , которую можно использовать для создания всего этого делегирующего кодадля вас.

0 голосов
/ 27 августа 2010

Я думаю, что простой ответ ... Поскольку .NET не поддерживает множественное наследование, всегда будут некоторые повторения при создании объектов подобного типа. .NET просто не дает вам инструментов для повторного использования некоторых классов таким образом, чтобы облегчить совершенную СУХУ.

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

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

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

0 голосов
/ 27 августа 2010

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

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