Я совершенно новичок в принципах дизайна SOLID . Я понимаю их причину и преимущества, но все же мне не удается применить их к небольшому проекту, который я хочу реорганизовать в качестве практического упражнения для использования принципов SOLID. Я знаю, что нет необходимости менять приложение, которое работает идеально, но я все равно хочу его реорганизовать, чтобы получить опыт проектирования для будущих проектов.
Приложение имеет следующую задачу (на самом деле намного больше, чем это, но давайте будем проще): оно должно прочитать XML-файл, который содержит определения таблицы базы данных / столбца / представления и т. Д., И создать файл SQL, который можно использовать для создания схема базы данных ORACLE.
(Примечание: пожалуйста, воздержитесь от обсуждения, зачем мне это нужно или почему я не использую XSLT и т. Д., Есть причины, но они не по теме.)
Для начала я выбрал только Таблицы и Ограничения. Если вы игнорируете столбцы, вы можете указать это следующим образом:
Ограничение является частью таблицы (или, точнее, частью инструкции CREATE TABLE), и ограничение также может ссылаться на другую таблицу.
Сначала я объясню, как приложение выглядит прямо сейчас (без применения SOLID):
В настоящий момент в приложении имеется класс «Таблица», который содержит список указателей на ограничения, принадлежащие таблице, и список указателей на ограничения, ссылающиеся на эту таблицу. Всякий раз, когда соединение устанавливается, обратное соединение также будет установлено. В таблице есть метод createStatement (), который в свою очередь вызывает функцию createStatement () каждого ограничения. Указанный метод сам будет использовать соединения с таблицей владельца и ссылочной таблицей для получения их имен.
Очевидно, это не относится к SOLID вообще. Например, существуют циклические зависимости, которые раздувают код с точки зрения требуемых методов «добавления» / «удаления» и некоторых деструкторов больших объектов.
Итак, есть пара вопросов:
- Должен ли я разрешить циклические зависимости с помощью внедрения зависимостей? Если это так, я полагаю, что ограничение должно получить таблицу владельца (и, возможно, ссылку) в своем конструкторе. Но как я мог тогда запустить список ограничений для одной таблицы?
- Если класс Table хранит свое собственное состояние (например, имя таблицы, комментарий к таблице и т. Д.) И ссылки на ограничения, являются ли это одной или двумя «обязанностями», имея в виду принцип единой ответственности?
- В случае 2. правильно, я должен просто создать новый класс в логическом бизнес-уровне, который управляет ссылками? Если это так, 1. очевидно больше не будет актуальным.
- Должны ли методы «createStatement» быть частью классов Table / Constraint или я должен также удалить их? Если да, то где? Один класс Manager для каждого класса хранения данных (т. Е. Table, Constraint, ...)? Или, скорее, создать класс менеджера по ссылке (аналогично 3.)?
Всякий раз, когда я пытаюсь ответить на один из этих вопросов, я где-то бегаю кругами.
Очевидно, что проблема становится намного более сложной, если вы включите столбцы, индексы и т. Д., Но если вы, ребята, поможете мне с простой вещью Table / Constraint, я могу решить все остальное самостоятельно.
источник
Ответы:
Вы можете начать с другой точки зрения, чтобы применить «Принцип единой ответственности» здесь. Вы показали нам (более или менее) только модель данных вашего приложения. SRP здесь означает: убедитесь, что ваша модель данных отвечает только за хранение данных - ни меньше, ни больше.
Поэтому, когда вы собираетесь читать свой XML-файл, создавать из него модель данных и писать SQL, вам не следует внедрять в свой
Table
класс что-либо, специфичное для XML или SQL. Вы хотите, чтобы ваш поток данных выглядел так:Таким образом, единственное место, где должен быть размещен специфичный для XML код, это, например, класс с именем
Read_XML
. Единственное место для кода, специфичного для SQL, должно быть подобным классуWrite_SQL
. Конечно, возможно, вы собираетесь разделить эти 2 задачи на несколько подзадач (и разделить ваши классы на несколько классов менеджера), но ваша «модель данных» не должна брать на себя никакой ответственности с этого уровня. Так что не добавляйте ниcreateStatement
к одному из ваших классов моделей данных, так как это дает ответственность вашей модели данных за SQL.Я не вижу никаких проблем, когда вы описываете, что таблица отвечает за хранение всех ее частей (имя, столбцы, комментарии, ограничения ...), то есть идея, лежащая в основе модели данных. Но описанная вами «Таблица» также отвечает за управление памятью некоторых ее частей. Это специфическая проблема C ++, с которой вам не так легко столкнуться в таких языках, как Java или C #. C ++ способ избавиться от этой ответственности - использовать интеллектуальные указатели, делегируя владение другому уровню (например, библиотеке повышения или вашему собственному «интеллектуальному» уровню указателей). Но будьте осторожны, ваши циклические зависимости могут «раздражать» некоторые реализации интеллектуальных указателей.
Еще кое-что о SOLID: вот хорошая статья
http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game
объясняя SOLID небольшим примером. Давайте попробуем применить это к вашему случаю:
Вам нужны не только классы
Read_XML
иWrite_SQL
, но и третий класс , который управляет взаимодействием этих 2 -х классов. Давайте назовем этоConversionManager
.Применение принципа DI может означать здесь: ConversionManager не должен создавать экземпляры
Read_XML
иWrite_SQL
сам по себе. Вместо этого эти объекты могут быть введены через конструктор. И конструктор должен иметь такую подписьConversionManager(IDataModelReader reader, IDataModelWriter writer)
где
IDataModelReader
интерфейс, от которогоRead_XML
наследуется, иIDataModelWriter
то же самое дляWrite_SQL
. ЭтоConversionManager
открывает возможности для расширений (вы очень легко предоставляете разных читателей или писателей) без необходимости их изменения - поэтому у нас есть пример для принципа Open / Closed. Подумайте об этом, что вам придется изменить, если вы хотите поддержать другого поставщика базы данных - фактически, вам не нужно ничего менять в своей модели данных, просто вместо этого предоставьте другой SQL-Writer.источник
Ну, вы должны применить S SOLID в этом случае.
Таблица содержит все ограничения, определенные для нее. Ограничение содержит все таблицы, на которые оно ссылается. Простая и простая модель.
То, что вы придерживаетесь, - это способность выполнять обратный поиск, то есть выяснить, на какие ограничения ссылаются некоторые таблицы.
Так что вы на самом деле хотите, это служба индексирования. Это совершенно другая задача, и поэтому она должна выполняться другим объектом.
Чтобы разбить его на очень упрощенную версию:
Что касается реализации индекса, есть 3 пути:
getContraintsReferencing
метод может действительно просто сканировать всеDatabase
дляTable
экземпляров и сканировать ихConstraint
с , чтобы получить результат. В зависимости от того, насколько это дорого и как часто вам это нужно, это может быть вариант.Table
иConstraint
экземпляров, когда они меняются. Немного более простым решением было быIndex
создать «индекс моментального снимка» целого,Database
с которым вы бы потом отказались. Это, конечно, возможно только в том случае, если ваше приложение проводит большое различие между «временем моделирования» и «временем запросов». Если есть вероятность, что эти два шага одновременно, то это нежизнеспособно.источник
Лекарство от циклических зависимостей - поклясться, что вы никогда их не создадите. Я считаю, что тестирование в первую очередь является сильным сдерживающим фактором.
В любом случае, циклические зависимости всегда можно сломать, введя абстрактный базовый класс. Это типично для представлений графа. Здесь таблицы - узлы, а ограничения внешнего ключа - ребра. Поэтому создайте абстрактный класс Table и абстрактный класс Constraint и, возможно, абстрактный класс Column. Тогда все реализации могут зависеть от абстрактных классов. Это может быть не лучшим из возможных представлений, но это улучшение по сравнению со взаимно связанными классами.
Но, как вы подозреваете, лучшее решение этой проблемы может не потребовать отслеживания отношений между объектами. Если вы хотите перевести только XML в SQL, вам не нужно представление графа ограничений в памяти. График ограничений был бы хорош, если бы вы хотели запустить алгоритмы графа, но вы не упомянули об этом, поэтому я предполагаю, что это не является обязательным требованием. Вам просто нужен список таблиц, список ограничений и посетитель для каждого диалекта SQL, который вы хотите поддерживать. Создайте таблицы, затем сгенерируйте внешние ограничения для таблиц. Пока требования не изменились, у меня не было бы проблем с подключением генератора SQL к XML DOM. Сохранить завтра на завтра.
источник