Страницы

Поиск по вопросам

Показаны сообщения с ярлыком проектирование. Показать все сообщения
Показаны сообщения с ярлыком проектирование. Показать все сообщения

понедельник, 13 апреля 2020 г.

Как пишется дополнение к ТЗ?

#проектирование

                    
Допустим у меня есть раздел 2, и пункт 2.3. Требования к СУБД и там будут пункты:

а) СУБД должна быть быстрой;
б) СУБД должна иметь возможность резервного копирования и восстановления данных;
в) СУБД должна ставиться на Linux;

Прошел год. И заказчик захотел модернизировать систему. И написал еще требования.
Одно из них пусть будет "СУБД должна быть бесплатной". И потребовал все требования
и дополнения оформить в виде дополнения к старом ТЗ.
В ГОСТ сказано:

3.6. Титульный лист дополнения к ТЗ на АС оформляют аналогично титульному 
листу технического задания. Вместо наименования «Техническое задание» пишут 
«Дополнение № ... к ТЗ на АС … ». 
3.7. На последующих листах дополнения к ТЗ на АС помещают основание для 
изменения, содержание изменения и ссылки на документы, в соответствии с которыми 
вносятся эти изменения. 
3.8. При изложении текста дополнения к ТЗ следует указывать номера 
соответствующих пунктов, подпунктов, таблиц основного ТЗ на АС и т.п. и применять 
слова: «заменить», «дополнить», «исключить», «изложить в новой редакции».

Но тут всё не понятно.
Я пишу дополнение так:
Раздел 2. Пункт 2.3.
Дополнить: г) СУБД должна быть бесплатной
Или я переписываю весь (под)пункт полностью? Т.е:
2.3. Требования к СУБД

а) СУБД должна быть быстрой;
б) СУБД должна иметь возможность резервного копирования и восстановления данных;
в) СУБД должна ставиться на Linux;
г) СУБД должна быть бесплатной
    


Ответы

Ответ 1



Исходя из цитаты из ГОСТа, правильнее Раздел 2. Пункт 2.3. Дополнить: г) СУБД должна быть бесплатной Да и исходя из здравого смысла тоже, если вы пишете дополнение, то должно быть сразу ясно, что именно изменилось. Как вариант можно (наверное) написать так: Раздел 2. Пункт 2.3. Дополнить: г) СУБД должна быть бесплатной Таким образом: 2.3. Требования к СУБД а) СУБД должна быть быстрой; б) СУБД должна иметь возможность резервного копирования и восстановления данных; в) СУБД должна ставиться на Linux; г) СУБД должна быть бесплатной

суббота, 11 апреля 2020 г.

Схема БД для учета проведенных конференций

#mysql #база_данных #проектирование

                    
Проектирую бд для системы учета проведения НИРС в ВУЗах.

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

К примеру у меня есть 1 человек, который создает приказ (Подтверждающий) и 2 человека
которые утверждают (Удтверждающие), тогда в таблице Приказы будет 2 записи, в которых
будут повторяться поля Тема приказа, Номер приказа, Дата приказа, Код подтверждения,
и будут отличаться в 2 записях только поле Код утдверждения.

Как можно перестроить схему, чтобы избавиться от этого изьяна, или есть возможность
хранить в поле таблицы массив данных(что врядли)?


    


Ответы

Ответ 1



будет происходить избыточность данных да, при описанной схеме работы факт и момент подписания (а в идеале — и отзыва) подписи следует фиксировать в отдельной таблице, связанной с таблицей приказов и таблицей(-ами) полномочий. но если все приказы соответствуют шаблону «три подписи», то, вероятно, проще будет добавить в таблицу приказов три соответствующих поля, содержащих либо null, либо id подписавшего/утвердившего. а в принципе, вопросы «оцифровывания» документооборота вообще, и фиксации подписаний-утверждений-согласований-отклонений в частности — тема достаточно обширная. в наиболее полном варианте, например, обязательно следует учитывать тот факт, что полномочия подписания предоставляются лишь на время. т.е. начинать надо с грамотной постановки задачи, в которой чётко определить, насколько «оцифрованный» документооборот будет упрощён относительно реального.

среда, 18 марта 2020 г.

Создание хорошего описания к коду

#yii #структуры #проектирование #проекты


Как возник данный вопрос, я, наверное, уже перейду к сути. Есть запрос на сервак,
у него 26 параметров. (мило правда?) запросов к серваку примерно может быть около 350!
в каждом от 5 до кучи передаваемых параметров.
Смысл в следующем, вот и вопрос.
У меня каждый запрос разбит на свой Action каждый action лежит в отдельном файле
//upd_start
Почему выбрано именно разбиение actions на файлы. Проект достаточно большой. Чтобы
все разработчики не расширяли сам контроллер, а просто дописывали в него 1-2 строчки
для подключения нового экшена + удобно редактировать проект такими кусками, а не целым
файлом, который бы терпел изменения постоянно. По мне так это правильно.
//upd_end
пример:
public function actions()
     {
     /* All actions in this controller 
      * are located in folder 
      * application.controllers.frontend.requests
      */
     return array(
                  'gf' => 'application.controllers.frontend.requests.gf',
                   // и т.д.
                );
      }

Первый вопрос:

На каком языке писать описание ко всему проекту?

В данный момент пытаюсь описывать на ENG, как может быть заметно из вышеописанной
функции, описание на ENG.
В gf.php описание тоже на ENG
@NumSeats          - The total number of passengers for which availability is being
requested
 @StartDt           - Date of Departure or Arrival. 
 @StartPt           - Airport or city code of the customer embarkation.
 @EndPt             - Airport or city code of the customer Destination.
 @StartTm           - Requested departure in 24-hour clock

В принципе, считаю что это правильно! В плане разработки проекта другими участниками,
как русско, так и англо говорящими.
Но есть одно но. Есть люди в компании не особо понимающие ENG язык и тем самым просят
комментировать код на русском языке.
В чем прикол? Ну на русском читать ведь проще! Соглашусь, переводить технический
ENG это пипец как "весело", схожу потихонечку с ума + ко всему не всегда получается
правильно перевести ENG на RUS в связи с разными обстоятельствами (незнание каких-то
оборотов и т.д.) В общем хватает веселых вещей по языкам.
Есть варианты решения, забить на ENG писать только на RUS второе решение писать только
на ENG, третье писать в 2х вариантах и в ENG/RUS что решит траблы обоих случаев, но
увеличит кол-во описания к коду, что на мой взгляд не есть хорошо.
Вот на стадии проектирования проекта и хочу выяснить как лучше делать.
С одной стороны хочется доставить всем удовольствие от разработки и писать описание
для всех, все равно приходится переводить мануал системы на RUS, с другой стороны писать
описалово на 2х языках муторно и как-то по кол-ву кода в файле слишком много.
Будет ли большое описание влиять на производительность кода?
последнее редактирование - 11.06.2013 15:32
Будут дополнения озвучу, пока все.
Жду ваших интересных отзывов по теме. 
Естественно буду продолжать открывать новые вопросы по теме разработки.    


Ответы

Ответ 1



@Shrek, не очень понял, что это за люди, которые хотят на русском. Если они будут сопровождать проект, то лучше делать комментарии на RUS. Если же это кто-то просто из любопытных заказчиков, то как Вам удобней (видимо, оставьте ENG). А писать комментарии в 2-х вариантах, это ни к чему. Тогда уж лучше их вообще не писать, т.к. через полгода один из вариантов точно уже не будет соответствовать коду.

воскресенье, 8 марта 2020 г.

Структура классов в проекте java

#java #ооп #проектирование


В Desktop Swing приложении есть класс Item в пакете Data, класс ItemFrame (графическое
заполнение и редактирование класса Item) в пакете GUI и класс NetWorker в пакете NetDB
для работы с DB и сетью. Имена классов и пакетов - условные.

По логике программы, при создании (и редактировании) экземпляра класса Item вызывается
графический интерфейс ItemFrame в который заносятся данные, при нажатии кнопки "Сохранить"
создается объект Item, который должен быть записан в DB и, при определенных условиях,
должны вызываться методы для его передачи по сети и записи в XML.

Получается, что при нажатии кнопки вызывается конструктор Item из другого пакета,
и методы по работе с DB и сетью из третьего. Выглядит немного запутанно.
Как в таком случае правильно организовать структуру, в каком классе какие методы
лучше реализовать?

Интересует именно правильное решение с точки зрения ООП.
    


Ответы

Ответ 1



Вы на верном пути. Разделение пакетов по обязанностям -- это правильно. Тот факт, что при этом будет какое-то место, которые вызывает код из нескольких пакетов -- неизбежен. Возможно вам стоит придерживаться классической трехслойной архитектуры UI->BLL->DAL (стрелками обозначено направление взаимодействия). Первый и последний слои у вас уже есть, осталось ввести слой бизнес-логики. UI будет передавать ему данные (или уже заполненный Item), BLL будет создавать/брать переданный Item и образаться к NetDB для сохранения.

вторник, 25 февраля 2020 г.

Как контроллеры MVC вызывают несколько разных сущностей (компонентов, элементов, модулей)?

#веб_программирование #mvc #проектирование #php


Не первый год программирую, но не могу понять одну простую вещь: как контроллеры
MVC формируют многофункциональные страницы. Например, на странице на этом сайте есть
верхний тулбар, меню справа, меню футреа, основной контент и т.д.
Мне гораздо легче дается понимание HMVC, то есть компонентного подхода. Там все просто:
в шаблоне вызываем нужный компонент, и он уже работает как замкнутая система со своим
внутренним MVC.
Буду признателен, если кто-то сможет втолковать идею. Сколько статей не прочитал,
так и не понял этого.    


Ответы

Ответ 1



Класическое mvc это когда представление по событию модели берет из неё положенное интерфейсом, диспатчит контроллеру, а контроллер меняет модель. Стоит заострить внимание, что контроллер не меняет представление вообще ( хотя в классике это ДОПУСКАЕТСЯ ), а модель меняет так, что решение меняться или нет остается за моделью. И такая модель называется "активной" и при ней контроллер "тонким", что является единственно верным вариантом. ВСЁ! Остальное это неправильно и ложно. Но чья-то глупость распространилась так, что даже гуру не верят в первоисточник, хотя wiki и "gof" об это говорят прямо (и причиной такой mvc-ериси, как я считаю, стало опускание из контекста статьи контроллера). Обновление Контроллер служит связующим звеном между представлением и модель. Но дело в том, что в wiki и всяких статейках приводятся (если и правильные реализации) минимальные примеры, которые в жизни не существуют. И ещё уясните одну маленькую и ускользающую от всех деталь - представление не имеет ссылку на модель, представление имеет ссылку на реализацию интерфейса. То есть, по ооп, структура строится на интерфейсах, модель это абстракция, а не живой объект. И когда все слюнями плюются и говорят, что представление имея ссылку на модель, нарушает концепцию о разделении логики от отображения, то просто говорящие не углублялись в mvc сильно, а используют навязываемые фраймворки, которые делают люди, но их концепцию обожествляют и выставляют эталоном.

Ответ 2



MVC была разработана в 1979 году, когда дела шли немного по-другому (меня тогда еще не было на этом свете, но, подозреваю, каждое окно ограничивалось ровно одной функцией), и предназначалась она не для веба. MVC используют просто потому, что она позволяет избавиться от загаживания кода, следуя простому паттерну, и многие вещи делаются в обход парадигмы, например, каждый виджет (например, меню страниц) должен был бы получить данные от контроллера, но наверняка подтягивает их сам из модели. Скажем так, это нестрогое MVC. HMVC и мне импонирует больше MVC. "предназначалась она не для веба" = не то чтобы она плохо подходит вебу, просто когда концепция была озвучена, никто и не думал, подойдет ли это для php-фреймворка.

Ответ 3



Ну, насколько я понимаю: при заходе на страничку контроллер получает набор инструкций, (вывести меню, вывести контент и т.д.), он посылает эти инструкции по своим модулям, те, в свою очередь, обрабатывают инстукции, из вьювок берут инфу о том, КАК нужно вывести, и выводят каждый по отдельности. Но чаще всего все пишут так: контроллер получает функции, и контроллер САМ работает с модулями и вьюхами, т.е. модуль выступает в роли класса определенной сущности, где с помощью методов контроллер получает необходимую инфу, сам загружает вьюху и сам их соединяет.

понедельник, 24 февраля 2020 г.

Шаблонизация в веб приложениях: сервер, клиент, смешанная?

#angularjs #сервер #клиент_сервер #проектирование #веб_программирование


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

1. Клиентская шаблонизация с json rest api

Достаем из базы данные => отдаем их на клиент в json/xml => разбираем данные на клиенте,
создавая объекты по клиентским моделям => добавляем каждую полученную модель в DOM. 

плюсы:  


пользователь ждет только нужные ему данные  
в процессе загрузки данных можем показать красивый прелоадер


минусы:


дублируем модели
лишний раз напрягаем клиентский браузер шаблонизацией


2. Тоже rest api, только шаблонизация, в целом, серверная

Достаем из базы данные => создаем из них html код => отдаем на клиент html => на
клиенте просто пихаем полученный html в DOM не думая.
Этот способ мне кажется самым практичным, но о нем почему-то практически не пишут.
Я просто что-то не то читаю, или есть серьезные недостатки, которых я просто не вижу?

плюсы:


первых два из пункта выше  
не дублируем модели


минусы:


выглядит так, будто их нет


3. Классическая серверная шаблонизация... только сервер

Выбираем данные из базы => на сервере это все дело делаем в html, но не кусочек страницы,
а всю страницу целиком => отдаем на клиент заново всю страницу.

плюсы:


не дублируем модели


минусы:


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


Вопросы


Какие еще есть вариации?
Кто что использует в своих проектах (личных, рабочих, как делают крупные компании...)? 
Почему? 
Какие плюсы и минусы я пропустил/не понял? 
В каком случае что использовать лучше? 

    


Ответы

Ответ 1



Кто что использует в своих проектах (личных, рабочих, как делают крупные компании...)? Крупная компания, более 20 разработчиков на проекте бывает. Либо полный server-side, либо полностью client-side, смешивание только в очень специфичных случаях (возможно бывают такие, сам не встречал). Server-side для проектов, где можно обеспечить full-stack разработчиков, чтобы и сверстать по мелочи, и скрипт на JS написать и запрос к базе сформировать смогли и т.д. Мобильные приложения не требуются, предоставления API для третьих лиц тоже. Чаще всего проекты, где веб-интерфейс минимален перспектив развития интерфейса нет. Client-side если команда большая, разделить можно на фронтэндщиков, бекендщиков, мобильщиков. Минусы - расходы на поддержку линии API. Чаще всего такой подход применяем, причем API first (у нас RAML). Была пара проектов в практике, где ответственность за отображение поделили между сервером и клиентом, очень неприятные впечатления: постоянные вопросы о том, кто за что должен отвечать, вопросы согласованности (например динамически формируемая форма на клиенте и ее перезаполнение в случае ошибочного сабмита). Требования к разработчикам очень высокие, нужно знать и client-side и server-side технологии.

Ответ 2



Во втором случае на самом деле есть куча минусов (кстати, слова "REST API" к нему неприменимы - потому что это не REST и не API). во-первых, возможности разметки в таком режиме получаются "обрезанными" - в частности, многие способы вставки HTML-кода в документ не запускают скрипты; во-вторых, страница, изготовленная из кучи надерганных кусков обычно является кошмаром верстальщика - совершенно не понятно в каком вообще файле надо искать нужный кусок верстки; в-третьих, постоянное дерганье innerHTML негативно сказывается на производительности; в-четвертых, такое решение обычно требует больше трафика чем оба альтернативных. Из плюсов же у него - поддержка большинством серверных фреймворков. По первому варианту - если вы не пишите ничего сложного, то "шаблонизация" клиентский браузер не напрягает. Особенно если использовать не шаблонизаторы, а библиотеки для двунаправленной привязки данных. Дублирование моделей тоже не является особенностью первого варианта - зачастую клиентская модель, серверная модель и модель передачи являются совершенно разными - и это совершенно нормально. Фактически, при переходе от тертьего варианта к первому модель не дублируется, а разделяется на две.

четверг, 13 февраля 2020 г.

Поясните за проектирование и оформление кода [закрыт]

#php #архитектура #проектирование #code_style


        
             
                
                    
                        
                            Закрыт. На этот вопрос невозможно дать объективный ответ.
Ответы на него в данный момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы на него можно было дать ответ, основанный на фактах и цитатах, отредактировав его.
                        
                        Закрыт 4 года назад.
                                                                                
           
                
        
О себе: кодер на пхп, делаю разные парсилки, чекалки, в т.ч. с капчей, обходом разного
рода защит.

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

Раньше я делал такие конвееры многократными структурами if/elseif/else, switch/case,
goto, чтобы выполнение кода циклически перебрасывалось из одного блока в другой. Столкнулся
с большой сложностью - читабельность и понимаемость кода из-за многократной вложенности
конструкций, сложность отладки, избыточное количество переменных, в которых запутываешься.
В итоге кодинг превращается в настоящую головоломку, и каждый новый проект вызывает
FFFFUUUUU-реакцию, когда приходится это снова повторять.

Сейчас занялся очередным таким проектом, и понимаю, что я неправильно делаю, и можно
проще, независимо от уровня сложности, просто я не знаю как. Подскажите, что я не так
делаю, и как делать правильно?
    


Ответы

Ответ 1



Мини-ремарка: Лол, что не так с вопросом? Хоть бы уточнили Вы не поверите, но описанные в вопросе проблемы, с которыми вы столкнулись, и есть та огромная разница между хорошим опытным программистом и новичком. Каким вы себе представляете ответ на такой "не маленький" вопрос? :) Теперь попытка краткого ответа: Правильно ставьте задачу. Это действительно половина ответа. Производите декомпозицию, т.е. разбиение задачи на небольшие подзадачи. Меньше задача - меньше переменных в голове, яснее цель, проще и короче решение, меньше число вариантов решения, проще сосредоточиться и/или переключить контекст в голове, если что-то вас отвлекло. Думайте не в терминах языка программирования (if+else, do+while и т.д.), а в терминах той области, в которой вы решаете задачу. Не в терминах типов хранения/представления данных (int, string и т.д.), а в терминах объектов, фигурирующих в текущей области знания. Не вы "перемещаете" и "изменяете" данные, а объекты как бы сами живут и меняются, исполняя некоторые обязательства, взаимодействуют друг с другом. Отнеситесь серьезно к форматированию и организации кода. Более того, правильно примененная декомпозиция задачи не оставит вам шансов писать процедуры/функции/методы, которые не умещаются по высоте на одной странице монитора. Но позаботьтесь так же об адекватном именовании абсолютно всего. Код должен быть таким, чтобы практически любые комментарии были излишни (кроме достаточно глобальных, являющихся документацией к классу/модулю, или очень специфических). Найдите середину между одно-двухбуквенными названиями переменных и оченьДлиннымиНазваниямиКоторыеЧитатьУжеНуСовсемНеудобно. Помните, что многопоточку дебажить в принципе сложно. Поэтому очень важно максимально устранить в коде остальной "дискомфорт". Upd. Шаблонов и концепций придумано много - по формулировке задачи не очевидно, что лучше подойдет (да и я не эксперт, если честно). Я бы старался максимально избегать многопоточки (не асинхронности) - очень часто профит от ускорения не перекрывает затрат на сложность разработки и поддержки. В простейшем случае, вы разбиваете вашу задачу на независимые этапы, для каждого из которых реализуется свой обработчик. Позаботьтесь о том, чтобы обращение к общим данным было потокобезопасным и его было как можно меньше - чем более независимым будет обработчик каждого этапа, тем меньшей головной болью будет одновременный запуск нескольких обработчиков. Обозначьте зависимости и требуемые входные данные для каждого типа обработчиков, а также данные на выходе. После этого достаточно лишь организовать механизм сохранения состояния и общения между обработчиками соседних этапов (т.е. переход от этапа к следующему) - это могут быть очереди, шины, сокеты, обычные колбэки событий и т.п. Подумайте над тем, кто будет инициировать переход между состояниями (push-based, pull-based, centralized). На русском гуглить наверное будет очень сложно (у меня сходу довольно скудные результаты получились). Теги: multithreading pattern, asynchronous pattern, event-driven programming, task-based asynchronous pattern, немного про разницу между асинхронностью и многопоточностью.

Какой паттерн проектирования выбрать?

#проектирование #шаблоны_проектирования


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

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

Поправьте меня, если я чего-то недопонимаю, или вопрос задан некорректно. Спасибо.
    


Ответы

Ответ 1



В сервисном слое (слой бизнес-логики) можно использовать многие паттерны, как ОО паттерны, так и другие паттерны более узкого или широкого профиля. Все зависит от того, какие у вас задачи в этом слое встречаются. Невозможно дать общий готовый список шаблонов, вот, мол, используй вот это и будет тебе счастье. Не-воз-мож-но. И не нужно. Забудьте на время о паттернах. Они не самоцель. Сперва делайте так, как вам кажется правильным. И в какой-то момент некоторые задачи покажутся знакомыми и уже их при желании можно решить, применив тот или иной паттерн. Еще лучше, забудьте о паттернах на годик. И вернитесь к ним после того, как получите чуть больше опыта. Тогда, я уверяю вас, вы совершенно по-другому на них взглянете, и более того, даже поймете, что использовали некоторые из них, не осознавая этого. Проанализировав свой опыт, поймете, где могли бы применить тот или иной шаблон. Еще раз повторюсь, вы неправильно понимаете для чего нужны паттерны, если отталкиваетесь от них. Шаблоны -- не самоцель, а лишь средство. Это как спрашивать "я хочу построить дом, какой кирпич мне взять?". Вы не с того начинаете, строитель!

Ответ 2



Если в бизнес-логике находится неизменяемая часть программы, а реализация сервисов может меняться, то в такой ситуации сервисы надо определить как интерфейсы, и реализовать их в классах. При этом в бизнес-логику передается только интерфейс сервиса, а сами классы (т.е. реализация интерфейса) должна быть недоступной из бизнес-логики. Это позволяет менять реализацию сервисов, не меняя бизнес-логику. Например, использовали сервис логирования, который выводил данные в файл, а позже решили выводить данные в облачный сервис. Для этого надо заменить только реализацию сервиса, а бизнес-логика остается без изменений. Такой паттерн называется "Cтратегия" -- это изолирование изменяемой части посредством интерфейса. Для передачи интерфейсов сервисов в бизнес-логику используется инъекция зависимостей (Dependency Injection, DI). Для внедрения зависимостей используются разные способы и целые фреймворки, например, MEF.

суббота, 8 февраля 2020 г.

Научиться проектировать архитектуру приложения [закрыт]

#c_sharp #книги #архитектура #проектирование


        
             
                
                    
                        
                            Закрыт. На этот вопрос невозможно дать объективный ответ.
Ответы на него в данный момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы на него можно было дать ответ, основанный на фактах и цитатах, отредактировав его.
                        
                        Закрыт 4 года назад.
                                                                                
           
                
        
Стал замечать, что по мере освоения C#, я все больше думаю о том, как архитектурно
реализовать ту или иную часть приложения.
Например, разрабатывая приложение для взаимодействия с USB-устройством, задумался
над тем, а как именно будут взаимодействовать его компоненты. 
Например, можно реализовать единственный класс, который будет заниматься всем: работать
на уровне WinAPI с USB, ожидать прихода пакетов, понимать протокол общения, реализовывать
последовательности команд и обработку ответов на них и т.д.
Но это неудобное решение. Лучше разделить по слоям и, например, вынести общение по
USB в отдельный модуль и тогда, если USB поменяется на Ethernet, то можно будет просто
написать новый модуль, не трогая остальное. 
Окей, пишем модуль общения по USB. Для этого пишем сначала интерфейс взаимодействия
с верхним уровнем. Написали. Так, что-то тут все заточено под синхронный обмен, а вдруг
понадобится асинхронный. Дополняем. И еще несколько параметров. 
Начинаем писать сам модуль. Оказывается, заложенная функциональность избыточна. А
еще вот тут что-то не так и там и т.д. Опять начинаем думать, исправлять/дополнять.
В итоге я зарываюсь в вопросы из серии "а как лучше сделать сейчас, чтобы в будущем
было удобно поддерживать?", производительность падает, а сложность придуманных конструкций
резко растет и в итоге получается какая-то каша, а не идеальный код, который я и хотел
получить.
Что я делаю не так? Как поступают опытные программисты? Как они избегают этих бесконечных
вопросов и при этом пишут вполне поддерживаемый и понятный код? 
Какую литературу можно почитать, чтобы прокачаться в этих вопросах? Шаблоны проектирования?
А не слишком ли они заточены под крупные проекты и сложны для небольших проектов? 
И есть ли у кого ссылки на проекты на C#, где можно посмотреть архитектурные решения?     


Ответы

Ответ 1



@yabloko, умение проектировать и находить баланс между текущими потребностями проекта и созданием задела на будущее приходит только с опытом. Нельзя просто прочитать книжку и обрести просветление. Поэтому пишите код, обдумывайте, изучайте как устроены чужие проекты, моделируйте какие-нибудь примеры взятые с потолка. Проектируйте для разминки небольшие "взятые с потолка" фрагменты функциональности. Просто на бумажке. Например: простенький UI из текстовых полей и кнопок, но такой, чтобы при необходимости можно было расширить количество виджетов; список контактов, с возможностью импорта из разных источников (почта, twitter, соцсети); текстовый редактор с простыми операциями над текстом (например, сделать выделенный фрагмент КАПСОМ) и возможностью их отмены. и т. п. В общем фантазируйте и тренируйтесь. Кстати про проектирование полноценного текстового редактора подробно разжевано в "Банде четырех".

Ответ 2



Попробуйте думать в терминах сущностей и их ответственности. Какие сущности есть в вашей программе, кто за что отвечает, кто о чём знает? Если вы не можете описать одним коротким предложением ответственность модуля/класса/функции/переменной, возможно, её надо переделать. Вы не должны думать о том, нужно ли будет подменить кусок с USB на кусок с Ethernet. Это может оказаться преждевременным дизайнерским решением, overengineering. Но вот разделение на отдельные простые слои — правильная мысль, она поможет вам бороться со сложностью проекта, сосредотачиваться каждый раз только на нужной части. Точно так же, вы выделяете код в процедуру, если он имеет самостоятельный смысл, а не для повторного использования. Даже если процедура используется всего один раз. Стоит почитать какую-нибудь книгу по паттернам, но не увлекайтесь этим сильно: свои мозги всяко лучше, чем консервированные идеи. Тем более, то, что в одном языке — паттерн, в другом часть синтаксиса (пример: Observer в C# реализуется в одно объявление, а в C++ требует уйму boilerplate-кода).

Ответ 3



Читайте про паттерны программирования. Например, есть такая книжечка "Паттерны проектирования" авторы Эрик Фримен, Элизабет Фримен, там на джаве все показывается, но суть понятна и для других языков. Еще есть куча литературы по паттернам. используйте их, практикуйтесь, и там постепенно придет понимание что и как делать.

воскресенье, 2 февраля 2020 г.

Проектирование базы данных с таблицами без первичного ключа

#база_данных #sql_server #проектирование


Прежде всего вопрос к опытным коллегам : а допускаются ли таблицы без ПК(первичный
ключ) в профессиональной БД? Ситуация такая - есть таблица клиент с разными полями
в том числе ID(которое и есть пк для таблицы) но есть и таблица контактные данные (без
пк) там столбцы состоят из (ID, phone, email, приоритетный способ связи и тд. другие
способы) стоит ли устанавливать связь между этими таблицами и как если стоит? Ведь
номеров может быть много и они могут добавляться.
    


Ответы

Ответ 1



tl;dr; Да, добавляейте FK. Если у вас одна запись в таблице контактов для каждого кастомера - делаейте Id (CustomerID) в таблице контактов первичным ключем + кластерным индексом. Если больше одной для одного заказчика - добавляйте отдельную колонку CustomerDetailsID, и и делайте PK+CI ее + добавьте некластерный индекс по CustomerID. Если email и предпочтительный способ контакта для клиента один, а телефонов - много - выносите телефоны в отдельную таблицу. Длинная версия Ок, чтобы понять, допустимы ли таблицы без Primary Key, нужно сначала понять что такое PK и какое отношение он имеет к индексам. Primary Key и Foreign Key - это, прежде всего, логические концепции. PK - это такая колонка (или несколько колонок), которая однозначно идентифицирует запись. Т.е. одному значению PK соответствует одна запись в текущей таблице. Например, значение ID в таблице клиентов однозначно идентифицирует запись о клиенте в этой таблице. FK - это колонка, каждому значению которой однозначно соответствует какая-то запись в другой таблице. Например, для каждого СustomerID в таблице контактных данных есть ровно одна запись в таблице Customers. PK и FK - это свойства самой структуры данных. Чисто теоретически - неважно, поставили ли вы отметку PK на Customer.ID, и создали ли вы FK ContactDetails.CustomerID -> Customer.ID - колонки от этого не перестанут идентифицировать записи. Например, в базе данных Team Foundation Server вообще не проставлены FK - что не мешает ему вполне нормально работать :) Зачем тогда ставить отметки PK и FK при создании базы в SQL Server? Это позволяет SQL Server жестко поддерживать уникальность, защищая вас от ошибок в данных. Т.е. он просто не даст вам вставить еще одну запись Customer с тем же ID. И не даст вписать в таблицу ContactDetails запись для несуществующего заказчика. Это позволяет SQL Server строить запросы более эффективно. Например, при поиске заказчика по ID он точно будет знать, что найдет не больше одной строки. А не, например, 100500 заказчиков с ID=1. И он выберет соответствующий план запроса, выделит соответствующее количество памяти под запрос и т.д. Какое отношение это все имеет к индексам? Дело в том, что для поддержания целостности PK и FK SQL Server-у необходимы определенные физические структуры в базе данных. В SQL Server есть два формата хранения таблицы Куча. Собственно, название говорит само за себя - это просто все строки таблицы, лежащие на диске в виде (гм) таблицы. Чтобы найти что-то в куче - вам придется перебрать всю кучу. Эта операция называется Table Scan, и она жутко неэффективна при большом количестве данных (она реально перебирает все данные, ставит на них локи, вобщем, в реальной системе обычно ничего хорошего она не несет) Кластерный индекс. Это дерево, построенное по какой-то колонке с уникальным значением (или нескольким колонкам), в листьях которого лежат сами строки таблицы. Кластерный индекс позволяет очень быстро искать данные по значению самой колонки. Кроме кластерных индексов есть еще и некластерные - это точно такие же деревья поиска, но в листьях у них лежит значение кластерного индекса (или rowid из кучу). Т.е. они позволяют найти по какой-то колонке значение (например, дате регистрации) значение из кластерного индекса (по которому потом можно выбрать уже сами данные строки). Некластерный индекс может накладывать дополнительные ограничения - например, уникальность данных. Но тем не менее - сами данные он (по умолчанию) в себе не хранит. Ок, как эти физические структуры соответствуют PK и FK? Для PK нужна возможность быстро проверить существование и уникальность записи. Поэтому PK создается или на основе кластерного индекса, или на основе уникального некластерного индекса. Просто так висеть в воздухе он не может. Типичным кандидатом на кластерный индекс является Primary Key - т.к. и значение кластерного индекса и значение PK должны быть уникальным, должны однозначно идентифицировать строку и т.д. - и в реальных схемах редко возникает ситуация, когда под эти требования попадает сразу две разных колонки. Тот же Management Studio по одной кнопке создает одновременно и PK и Clustered Index. Поэтому кластерный индекс и Primary Key считаются чуть ли не синонимами. Хотя на самом деле есть техническая возможность создать кластерный индекс по одной колонке, а PK - по другой. Для FK не нужна поддерживающая структура в той таблице, на которой он задан. Но ему нужна поддерживающая структура в той таблице, на которую он ссылается. Т.к. он должен проверять существование и уникальность, но только в другой таблице - то требования к этой стуктуре совпадают к требованиями к структуре PK в той таблице, на которую ссылается FK. Например, при вставке в ContactDetails SQL Server должен проверять, что для вставляемого значения CustomerID есть соответствующая (и ровно одна!) запись в Customers. Поэтому для FK со стороны Customer нужен или кластерный индекс по той же колонке, или хотя бы уникальный ключ. В таблице ContactDetails при этом никаких структур данных ради этого FK не требуется.

Ответ 2



допускаются ли таблицы без ПК(первичный ключ) в профессиональной БД? Первичный ключ в таблице нужен для того, чтобы можно было однозначно идентифицировать запись - например, при наличии в таблице полных дубликатов по остальным полям, или при использовании внешних ключей (связей) к этой таблице. Если для данной конкретной таблицы ничего подобного не требуется, то наличие в ней первичного ключа необязательно. Некошерно, конечно, но и ничего особенно плохого в этом нет. Хотя системы зачастую развиваются, и в будущем такая необходимость может появиться - так что порой имеет смысл ввести в структуру синтетический первичный ключ просто "на всякий пожарный". К слову, если Вы даже не создаёте первичного ключа, не факт, что его нет. СУБД вполне может ввести (и скорее всего введёт) в структуру скрытое и недоступное Вам поле, которое будет выполнять роль синтетического первичного ключа, идентифицирующего запись. Например, СУБД же надо как-то идентифицировать, какой записи таблицы соответствует запись в индексе... стоит ли устанавливать связь между этими таблицами При создании такой связи средствами СУБД (созданием внешнего ключа) Вы переложите заботу по контролю целостности и непротиворечивости информации на плечи специально предназначенной для этого службы SQL-сервера. Это убережёт Вас от потенциального нарушения логической целостности данных, что в общем полезно - если не создавать такую связь и возложить эту задачу на клиентское ПО, которое гораздо хуже приспособлено для подобных функций, то можно поиметь ненужные проблемы, особенно в случае нештатных ситуаций. Так что если логика требует такой связи - её необходимо создать.

Ответ 3



Если правильно понял, то тут имеется ввиду таблица без кластеризованного индекса. Как правило такие таблицы называются кучами. Их стоит использовать, например, в следующих случаях: 1) В таблице не будет много записей и нам, при обращении к таблице, нужно делать скан. 2) Если созданы некластеризованные индексы и доступ к данным осуществляется через них. 3) Когда мы знаем что в таблицу будет производиться массовая запись данных. Есть и другие варианты использования куч. Все зависит от архитектуры проекта.В крупных проектах такое встречается довольно часто. Более подробно можно почитать на msdn.

пятница, 31 января 2020 г.

Как выделить шаблонность из функции?

#cpp #проектирование #шаблоны_с++ #рефакторинг


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

template
void terribleFunction(Itor it, type1 param1, type2 param2, ...)
{
    // Всякие дела
    for(....
    {
        // И еще дела

        // Первое использование it
        if(...) terribleFunctiom(it,p1,p2,...);
        // И еще...

        // Второе использование it
        if (...) *it++ = что-то;  

        // И еще...
    }
}


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

Какой бы тут хитрый метод применить, намекните?...
    


Ответы

Ответ 1



Если единственное использование - это X x = expr; *it++ = x;, то эту операцию можно обернуть в std::function f, передавать не итератор а эту функцию, использовать как f(x). Либо можно передавать класс с виртуальными функциями.

Ответ 2



По сути, все что вам нужно это полиморфное поведение вашего итератора. Сейчас вы достигаете полиморфизма за счет шаблонов. Можно достичь того же эффекта при помощи наследования и виртуальных методов. Для начала объявим интерфейс итератора: class IIterator{ public: virtual void next() = 0; virtual void setValue(int value) = 0; virtual int value() const = 0; virtual ~IIterator(){} }; Думаю вы знаете тип значения, которое хотите писать в итератор. Для примера я взял int. Теперь нам нужна конкретная реализация, причем для всех возможных итераторов. Да, снова шаблоны: template class Iterator : public IIterator{ It _iterator; public: explicit Iterator(const It &iterator): _iterator(iterator) {} void next(){ ++_iterator; } void setValue(int value){ *_iterator = value; } int value() const{ return *_iterator; } }; Теперь возьмем весь код из void terribleFunction(Itor it), перенесем его в функцию void terribleFunctionHelper(IIterator &it), и внесем некоторые изменения в использование итераторов: void terribleFunctionHelper(IIterator &it){ //... it.next(); //Раньше тут было ++it; it.setValue(42); //Раньше тут было *it = 42; //... } Код void terribleFunction(Itor it) теперь станет таким: template void terribleFunction(It it){ Iterator iterator(it); terribleFunctionHelper(iterator); } В результате этих нехитрых манипуляций, все зависимости оказались в нешаблонной terribleFunctionHelper. Можно смело переносить ее код в cpp файл. Полный пример

среда, 29 января 2020 г.

Выбор между свойством, методом, extension методом, ToString для User.FullName

#c_sharp #net #ооп #классы #проектирование


Предположим есть следующий класс:

public class User
{
    public string FirstName {get;set;}
    public string LastName {get;set;}
}


Мне необходимо получить полное имя, я знаю следующие способы как это сделать:

1.Создадим необходимое свойство в классе:

public string FullName
{
    get { return string.Format("{0} {1}", FirstName, LastName); }
}


2.Переопределим ToString() для данного класса:

public override string ToString()
{
    return string.Format("{0} {1}", FirstName, LastName);
}


3.Создадим метод расширения:

public static UserHelpers
{
    public static string FullName(this User obj)
    {
        return string.Format("{0} {1}", obj.FirstName, obj.LastName);
    }
}


4.Создадим функцию

public string GetFullName()
{
    return string.Format("{0} {1}", FirstName, LastName);
}


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

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


Ответы

Ответ 1



Полное имя - свойство пользователя, а то что оно вычислимое, а не вводится вручную - всего лишь удобство\неудобство в использовании. Так что, в плане класса - это его свойство. Если свойство всегда должно быть только вычислимым (с учетом наследования, ага), то можно сделать из свойства метод. ToString для сложных объектов не сильно полезен, хоть в данном случае и похоже на что-то полезное. Экстеншены - только для чужих классов. Иначе за ними потом становится слишком тяжело следить и поддерживать. UPD: для вычислимых свойств можно делать как свойства, так и методы. Я тут больше опираюсь на то, как оно себя ведет в абстрактном случае - может быть вообще в теории установка вручную или это всегда вычисление из свойств сущности. Стоит учитывать, что в свойства обычно больше пары строчек никто не пишет, т.е. как только у вас способ построения увеличивается до 5 строк - это уже явный кандитат на метод. Ну и, стоит учитывать, что свойство должно считаться быстро, иначе это тоже явный метод. Никаких запросов на сервер за информацией свойство явно делать не должно.

Ответ 2



С моей точки зрения вычислимое поле, за редким исключением, это внешняя, по отношению к классу, сущность. Поэтому, нахождение такого поля в классе считаю в общем случае не верным. Поясню на примере: Пусть у нас есть класс из вопроса: public class User { public string FirstName {get;set;} public string LastName {get;set;} } Мы добавляем свойство(или метод — не важно): public string FullName => LastName + FirstName; Всё, хорошо, мы молодцы. Выпускаем продукт и забываем об этом. Приходит в проект Иван Петров, и дают ему задание написать некую фичу, где требуется вывод полного имени пользователя. Только в его случае, ему нужно вывести только имя и первую часть фамилию. Он идёт в класс User, смотрит как там сделано, и решает повторить существующее решение(он в чём-то прав,— последовательность очень важна в коде). Поэтому он добавляет ещё одно свойство: public string FullNameWithShortSecond => FirstName + LastName[0]; Думаете на этом проблемы наши исчерпались? О нет, они только начались, ведь продажники продавили продажи нашего чудесного софта в США. Теперь-то деньги польются рекой! Вот только одна незадача — в США порядок First-Second[Name] обратен принятому в РФ. Поэтому мы создаём ещё одно поле, теперь для США: public string FullNameUsa => FirstName + LastName; Стоит ли упоминать, что обычно в классах не 2 свойства, и вариаций их сопоставлений можгут быть десятки и даже сотни. Всё будем свойствами прописывать? Конечно же нет. Мы оставим минимально необходимый интерфейс класса, а вот вычисляемые поля пусть вычисляют те, кому они нужны. Как они будут реализовывать вычисляемые поля? Это не важно, в C#, как мне кажется, самый удобный способ это использование extension methods. В C++ это будут свободные функции. Ну или можно будет создавать декоратор, который будет добавлять вычисляемые свойства. Но это сложнее и не так часто необходимо. Так что, никогда не добавлять вычисляемых полей в изначальный класс? Нет, к этому я не призываю. Я призываю лишь к тому, чтобы сначала рассмотреть насколько это свойство необходимо данному классу, и если у Вас нет чёткого понимания, что это поле должно быть именно здесь, то и не стоит его сюда пихать. Интерфейс класса нужно содержать в чистоте и порядке и не давать ему разрастаться, просто потому, что кажется, что тут поле будет воткнуть проще.

Ответ 3



Я бы предложил четвёртый вариант: Создадим метод GetFullName в классе: public string GetFullName() { return string.Format("{0} {1}", FirstName, LastName); } Первый вариант (создать необходимое свойство FullName в классе) не плохой вариант, хотя не так очевидно пользователям вашего класса, что FullName изменяется когда другие свойства изменяются. Как правило, если надо что-то вычислить, лучше сделать метод. Второй вариант (переопределить ToString()) подходит если вы хотите сделать метод, который показывает важную информацию человека, вроде Console.Write("Кто я? Я " + me); Но если хотите создать способ получить именно имя и фамилию, это не подходит: string fullName = person.ToString(); Откуда знаем, что ToString() возвращает то, что хотим, то есть, "Иван Прокофьев", а не "Иван, 28 лет", или "Прокофьев Иван Григоревич, человек №541"? Следующий вариант более выразителен: string fullName = person.GetFullName(); Лучше использовать третий вариант только при необходимости. Если возможно изменять исходный класс, будет проще для всех положить методы в одном классе.

воскресенье, 26 января 2020 г.

Проблемы с проектированием базы данных на основе ORM

#ооп #orm #проектирование #kohana #php


Добрый день.
Сначала перечисляю сущности.
sуstems - некие системы.
preferences - некоторые настройки, сокращенно pref
pref имеет разные типы, например настройки кеша (cache) и тамаута (timeout).
Таким образом pref можно записывать в одну таблицу с полями id, value, type или в
две разные таблицы - prefcache и preftimeout с той же структурой. Идем дальше.
Поля таблицы system - id, name.
Осуществляется связь многие ко многим через таблицу prefs_systems с полями: id_system,
id_pref, type (можно вынести сюда в случае отдельных таблиц prefcache и preftimeout.
И вот зачем две таблицы pref, я хочу на каждую делать свой ORM, т.е. есть базовый
ORM_Pref extends ORM и от него наследуются ORM_Prefcache и ORM_Preftimeot. Каждый
работает со своей таблицей, а общие настройки в базовом классе.
В этом случае в каждой ORM прописываются связи $_has_many. В случае ORM pref все
очевидно, по одной записи связей $_has_many к ORM_System. В случае с описанием ORM_System
получается такая конструкция (додумался пока писал).
 protected $_has_many = array(
    'prefcache' => array(
        'model'       => 'orm_prefcache',
        'foreign_key' => 'id_system',
        'through'     => 'prefs_systems',
        'far_key'     => 'id_pref',
    ),
    'preftimeot'  => array(
        'model'       => 'orm_preftimeout',
        'foreign_key' => 'id_system',
        'through'     => 'prefs_systems',
        'far_key'     => 'id_pref',
    ),
);

Вопросы:

Почему редактор съел почти все символы нижнего подчеркивания?
Нормально ли я придумал структуру?
Могу ли я базовый класс ORM_Pref объявить абстрактным (т.к. у него нет привязанной
таблицы и его не надо создавать) ?
Можно ли как-то улучшить мою структуру? Если все выше правильно, то при добавления
нового типа настроек я должен буду прописать связь в ORM_System, это не удобно.
    


Ответы

Ответ 1



хорошо в таком случае использовать orm-библиотеки (напр. Doctrine). Можно создать схему в mysql workbench, форвард-инжинирингом создать БД и при помощи комманды doctrine сгенерировать сущности

пятница, 24 января 2020 г.

Принцип открытости/закрытости при динамическом определении типа файла

#c_sharp #шаблоны_проектирования #проектирование #инспекция_кода #solid


Есть код приложения в котором необходимо динамически определить тип файла (HTML или
TXT) по содержимому и в зависимости от полученного типа файла вызвать соответствующий
алгоритм обработки.

interface IFileType
{
    void Process();
}
class HtmlFile : IFileType
{
    public void Process()
    {
        Console.WriteLine("HTML");
    }
}
class TxtFile : IFileType
{
    public void Process()
    {
        Console.WriteLine("TXT");
    }
}
class FileTypeHandler
{
    public static IFileType Define(string fileContent)
    {
        var file = fileContent.IndexOf("


Ответы

Ответ 1



Я вижу следующие проблемы в вашем коде. Метод FileTypeHandler содержит логику определения, для всех форматов. На данном этапе это не страшно, т.к. их не много, но если их количество будет увеличиваться, метод станет запутанный и тяжел в расширении. Рекомендую, создать сущности, для каждого формата, и в них реализовывать определение. Это позволит избежать запутанности и облегчит добавление новых. Приведу код, как я бы в данной ситуации реализовал. В нем есть небольшие упрощения. Написал, на java, но думаю вы разберетесь. enum FormatTypes { TXT("txt", new TxtDetector()), HTML("html", new HtmlDetector()); public final String name; private final Detector detector; FormatTypes(String name, Detector detector) { this.name = name; this.detector = detector; } } interface Detector { boolean isCorrectType(String fileName, String content); } class TxtDetector implements Detector { } class HtmlDetector implements Detector { } class DetectorHandler { public static String getType(String fileName) { String content = //чтение содержимого for (FormatTypes types : FormatTypes.values()) if (types.detector.isCorrectType(fileName, content)) //просто возвращает название формата, //можно при необходимости в FormatTypes положить какую то логику return types.name; throw new IllegalArgumentException("type is not supported"); } }

четверг, 23 января 2020 г.

Использование Strategy Pattern в случае, если конкретные стратегии зависят от типа использующего их объекта

#c_sharp #разработка_игр #проектирование #шаблон_стратегия


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

public interface iWeapon {
    void Use();
}

public interface iReloadable {
    void Reload();
}

public interface iWeaponUseStrategy {
    void Perform();
}

public interface iWeaponReloadStrategy {
    void Perform();
}

public class BaseWeapon: iWeapon, iReloadable {

    public iWeaponUseStrategy UseStrategy;
    public iWeaponReloadStrategy ReloadStrategy;

    public void Use() {
         UseStrategy.Perform();
    }

    public void Reload() {
        ReloadStrategy.Perform();
    }
}


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

public interface iWeaponWithAmmoUseStrategy {
    // Теперь стратегия меняет состояние внешнего по отношению к ней объекта
    void Perform(WeaponWithAmmo weapon); 
}

public class SimpleAmmoUseStrategy : iWeaponWithAmmoUseStrategy {

    public void Perform(WeaponWithAmmo weapon) {
        // Изменение состояния внешнего объекта
        weapon.AmmoCurrent -= 1;
        // код выстрела
    }
}

...

public class WeaponWithAmmo: iWeapon, iReloadable {
    public int AmmoMax {get; set;}
    public int AmmoCurrent {get; set;}

    public iWeaponWithAmmoUseStrategy UseStrategy;

    public void Use() {
        UseStrategy.Perform(this);
    }

    ...
}


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

Единственное решение, которое приходит в голову, это вынести зависимость от оружия
в отдельный интерфейс, и для каждого нового класса оружия (и соответствующих ему стратегий)
менять интерфейс iWeaponUseStrategy:

// Теперь перед использованием оружие надо инициализировать его стратегии
public iWeaponWithAmmoInit {
    void Init(WeaponWithAmmo weapon);
}

// Но затем можно вызывать методы стратегий без ссылки на внешний объект
public iWeaponWithAmmoUseStrategy {
    void Perform();
}

// Кроме того, нужно чтобы базовая стратегия являлась подтипом специфичных для оружия
стратегий
// Меняем этот код каждый раз при добавлении класса оружия
public iWeaponUseStrategy : iWeaponWithAmmoUseStrategy, iSomeNewWeaponTypeUseStrategy {
    new void Perform();
}


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

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


Ответы

Ответ 1



Мне кажется, ошибка в том, что вы пытаетесь сложную систему правил закодировать в терминах иерархии объектов. Иерархии объектов просто не предназначены для этого. Хуже того, ваша логика правил использования оружия оказывается размазанной по всем иерархии наследования. Подобную проблему недавно обсуждал Эрик Липперт в цикле статей Wizards and warriors: [1], [2], [3], [4], [5]. (Вы можете просмотреть сразу выводы в 5-ой статье или прочитать всю промежуточную логику с начала цикла.) Присоединюсь к идее этих статей: просто создайте отдельную сущность «набор правил», в которой закодируйте все правила.

воскресенье, 12 января 2020 г.

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

#c_sharp #проектирование #шаблоны_проектирования


Добрый день! Пусть есть несколько классов, например:

public class ProductA 
{
   public string FieldFirst { get; set; }
   public string FieldSecond { get; set; }
   public string FieldThird { get; set; }
   public string FieldFourth { get; set; }
}

public class ProductB 
{
   public string FieldFirst { get; set; }
   public string FieldSecond { get; set; }
   public string FieldThird { get; set; }
   public List FieldFourth { get; set; }
}

public class ProductC 
{
   public string FieldFirst { get; set; }
   public string FieldSecond { get; set; }
   public List FieldThird { get; set; }
}

public class ProductD 
{
   public string FieldFirst { get; set; }
   public List FieldSecond  { get; set; }
}


Собственно, хотел узнать, возможно ли сделать общий интерфейс для выбора класса и
создания объекта. Нельзя ли найти какой-нибудь единый  интерфейс для них, и использовать,
например  Абстрактную Фабрику. Или это нельзя сделать в данном случае. Заранее огромное
спасибо! 
    


Ответы

Ответ 1



Простое решение Если типы аргументов известны на стадии компиляции и наборы типов параметров уникальны, то можно сделать банальную фабрику: public static class ProductFactory { public static ProductA Create (string fieldFirst, string fieldSecond, string fieldThird, string fieldFourth) { return new ProductA(fieldFirst, fieldSecond, fieldThird, fieldFourth); } public static ProductB Create (string fieldFirst, string fieldSecond, string fieldThird, List fieldFourth) { return new ProductB(fieldFirst, fieldSecond, fieldThird, fieldFourth); } public static ProductC Create (string fieldFirst, string fieldSecond, List fieldThird) { return new ProductC(fieldFirst, fieldSecond, fieldThird); } public static ProductD Create (string fieldFirst, List fieldSecond) { return new ProductD(fieldFirst, fieldSecond); } } Тогда создание объектов будет выглядеть следующим образом: ProductA productA = ProductFactory.Create("1", "2", "3", "4"); ProductD productD = ProductFactory.Create("1", new List()); Здесь я предположил следующие классы продуктов: public class ProductA { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public string FieldFourth { get; set; } public ProductA (string fieldFirst, string fieldSecond, string fieldThird, string fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductB { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public List FieldFourth { get; set; } public ProductB (string fieldFirst, string fieldSecond, string fieldThird, List fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductC { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public List FieldThird { get; set; } public ProductC (string fieldFirst, string fieldSecond, List fieldThird) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; } } public class ProductD { public string FieldFirst { get; set; } public List FieldSecond { get; set; } public ProductD (string fieldFirst, List fieldSecond) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; } } Сложное решение Если на стадии компиляции ничего не известно, а аргументы собираются вручную во время выполнения программы, то можно воспользоваться Autofac, например. Вот пример использования, когда выбор делается на основе имён и типов параметров, и имена параметров не совпадают с именами аргументов конструктора: var builder = new ContainerBuilder(); builder .Register((c, p) => { switch (p.Count()) { case 4: if (p.OfType().Any(a => a.Name == "str4")) return new ProductA( p.Named("str1"), p.Named("str2"), p.Named("str3"), p.Named("str4")); else return new ProductB( p.Named("str1"), p.Named("str2"), p.Named("str3"), p.Named>("ints")); case 3: return new ProductC( p.Named("str1"), p.Named("str2"), p.Named>("doubles")); case 2: return new ProductD( p.Named("str1"), p.Named>("floats")); } throw new DependencyResolutionException("Could not resolve product."); }) .As(); IContainer container = builder.Build(); var productA = container.Resolve( new NamedParameter("str1", "1"), new NamedParameter("str2", "2"), new NamedParameter("str3", "3"), new NamedParameter("str4", "4")); var productD = container.Resolve( new NamedParameter("str1", "1"), new NamedParameter("floats", new List())); Предполагается, что продукты реализованы следующим образом: public interface IProduct {} public class ProductA : IProduct { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public string FieldFourth { get; set; } public ProductA (string fieldFirst, string fieldSecond, string fieldThird, string fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductB : IProduct { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public List FieldFourth { get; set; } public ProductB (string fieldFirst, string fieldSecond, string fieldThird, List fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductC : IProduct { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public List FieldThird { get; set; } public ProductC (string fieldFirst, string fieldSecond, List fieldThird) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; } } public class ProductD : IProduct { public string FieldFirst { get; set; } public List FieldSecond { get; set; } public ProductD (string fieldFirst, List fieldSecond) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; } }

Проектирование базы данных при DDD (Domain-Driven Design)

#база_данных #архитектура #проектирование #ddd


Постараюсь кратко описать мой теоретический и не очень актуальный, но интересующий
вопрос. 

В подходе DDD при проектировании приложения отталкиваются от проектирования доменной
логики. Если есть у кого-то опыт, посоветуйте, как тогда проектировать базу данных.
А то подход DDD мне теоретически понятен когда незыблемая БД уже есть. А если ее нет
и ведется командная разработка и когда одну часть системы делает одна команда разработчиков,
а другую - другая, то уже совсем не понятно. Если сначала проектировать БД для всей
системы, то название domain-dd не совсем себя оправдывает, так как отталкиваемся уже
не от проектирования домена, а от проектирования БД...



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

В DDD есть два термина:


Ubiquitous language - повсеместный язык
Bounded context - ограниченный контекст


То есть одна и та же сущность должна быть по-разному спроектирована в зависимости
от того, в каком контексте она используется.

Например, если рассмотреть какую-нибудь систему ERP для планирования ресурсов предприятия,
в котором сотрудники сидят и с помощью приборов делают работы. В этой системе могут
быть следующие сущности: 


Сотрудники 
Приборы 
Работы 


У системы могут быть две функции: 


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


На сколько я понимаю, понятие ограниченного контекста (bounded context) введено,
чтобы разграничить проектирование приложения на части, чтобы отдать эти части для разработки
разным командам программистов. На сколько я понимаю, в данном примере ограниченные
контексты это и есть две функции системы: учет пользователей и учет приборов.

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

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

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

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

Так же интересуют любые советы и если кто поделится своим опытом, о том, как проектировать
БД когда над системой работают несколько разных групп людей с разным видением... 
    


Ответы

Ответ 1



На сколько я понимаю, понятие ограниченного контекста (bounded context) введено, чтобы разграничить проектирование приложения на части, чтобы отдать эти части для разработки разным командам программистов. Мне кажется, что в первую очередь здесь имеет смысль разделение на модули, а возможность разработки различными командами это уже следствие такого разделения. Так как у сущности может быть множество атрибутов которые, зачастую, используются небольшими группами в различных функциях проще сразу разделить такую сущность на эти группы атрибутов и использовать их независимо, чем везде таскать одну и туже сущность большая часть которой, в заданном контексте, не нужна и образует только лишнюю связанность кода. У Вас есть сервер приложений где описана модель Ваших данных и бизнес-логика которая умеет менять состояние этой модели. Модели должно быть все равно где и как хранится ее состояние. В Вашей ситуации главное то, как выглядит модель данных на сервере приложений и то, как реализована ее бизнес-логика. Ведь описание сущностей и их атрибуты ровно как и методы этих сущносетй должны быть понятны всем участникам процесса и соответсвовать тому как они все себе это представляют опираясь на общий язык. На сколько я понимаю, DDD подразумевает длительный процесс выработки модели предметной области в течении которого она будет достаточно часто меняться пока более менее не устаканится. В таком случае сразу скооперироваться и сделать общую базу данных так просто не получится, так как модель еще толком не ясна. Поэтому, в первую очередь, Вы будете создавать и менять модель данных на сервере приложений и уже во вторую очередь придумывать как она будет хранится. Но это не означает то, что в процессе изменения модели данных так же сильно должно меняться и хранилище данных (может меняться только слой абстракции в виде представлений). Что касается БД - то тут можно пойти по разному. У вас есть база данных которая хранит состояние Вашей модели и осуществляет проверку целостности данных. На БД часто налагают различные технические требования так, что вопрос распределения данных по таблицам может, в большей степени, относится к решению этих технических проблем, а не к соответствию 1 в 1 с Вашей моделью данных. То, что у сотруника в разных контекстах есть как общие атрибуты так и частные напоминает наследование. А для наследования придумали различные способы хранения данных в таблицах, такие как Single Table Inheritance, Class Table Inheritance и Concrete Table Inheritance. В Вашем случае вполне может быть подходящим второй способ. Тогда Вы сможете определить и выделить общие черты нужных сущностей и положить их в одну таблицу позволив разным командам создать таблицы для атрибутов этой сущности относящихся только к их задачам. Или даже третий способ для начала, чтобы быть полностью независимыми друг от друга. У каждого подхода будут как свои плюсы так и минусы как в плане добавления\удаления атрибутов так и в плане взаимопересечения команд и дублирования данных. Независимо от варианта со структурой таблиц стоит использовать дополнительный слой абстракции над этими таблицами в виде представлений. Такой подход позволит склеить разные или разделить одну таблицы. В дальнейшем, когда сама модель данных перестанет сильно меняться, можно будет рассмотреть первый вариант. Это должно будет упростить логику приложения и, возможно, снизить размер сохраненного состояния. Ну и когда над одной БД работают различные группы людей особенно с разным видением я думаю, что стоит в первую очередь выработать какие-то общие правила (стандарты) как минимум в области именования обьектов.

пятница, 10 января 2020 г.

MVC для игры морской бой

#cpp #mvc #проектирование


Разделил логику от интерфейса на составляющие MVC

Model:

Класс Game в котором реализованы игроки, у каждого игрока есть поле, корабли и так далее

View:

2 объекта класса TField (визуальный компонент)

Controller:

Класс GameController, который должен связывать логику игры и ходы с визуальным представлением
на форме.

Именно на контроллере стал задумываться, как связать их. Определить в классе GameController
объекты классов модели и визуального представления? Но как их связать? Обработку нажатия
на клетку в поле, отправку координат X и Y в класс Game и обратно получить ответ, обработав
попадание/не попадание?

Я частично понял, как реализовать все это. У нас есть цепочка:


  Представление -> Контроллер -> Модель


Представление вызывает клик и отправляет контроллеру событие о выстреле. Контроллер
передает информацию об это модели на обработку. Но как сделать обновление Представления
после изменений в Модели я так и не понял.

Я так понимаю, что нужно создать слушателя/-ей, которые будут вызываться из Модели
при обновлении данных. Но это значит, что Модель должна содержать ссылку на Представление,
что по сути перечит MVC, если Модель должна не знать о Представлении. Есть мысли?
    


Ответы

Ответ 1



Смотрите. Вам нужен по идее event или Listener, или как там этот паттерн называется. Суть такова. Модель выставляет метод Subscribe(), в который можно передать callback, который будет вызван при изменении свойства. Представление знает о модели, и подписывается на её изменения. Таким образом, модель ничего не знает о представлении, но может дёрнуть это самое представление, когда что-то поменялось. Пример на коленке: class Model { public: typedef int Token; Token subscribe(std::function callback) { max_token++; callbacks[max_token] = callback; return max_token; } void unsubscribe(Token token) { callbacks.erase(token); } private: std::map> callbacks; Token max_token = -1; // это дёрнет все callback'и void notifyall() { for (auto& kv : callbacks) kv.second(); } }; class View { Model* model; Model::Token token; //... View(Model* model) : model(model) { token = model->subscribe([this] { OnModelUpdate(); }); } ~View() { model->unsubscribe(token); } };

Подскажите, как лучше оформить класс

#c_sharp #net #проектирование


В общем, хочу сделать класс, который будет импортировать DataTable в Access или MS SQL.

Класс будет иметь 1 метод: Append, которому на вход будет подаваться DataTable, а
метод в свою очередь будет загружать DataTable в нужный пункт назначения.

Разумеется, внутренняя логика Append у Access может сильно отличаться, например:


В Access нужно следить за тем, что бы файл не был выше 2гб, иначе бд поломается
В MS SQL можно воспользоваться BulkCopy, а в Access потребуется какой-то самопал.


Соответственно и конструкторы могут сильно отличаться по параметрам, например:


Строки подключения между двумя типами будут отличаться
В MS SQL можно использовать или не использовать транзакцию
В Access можно восстанавливать или не восстанавливать БД после каждого Append.


Как лучше всего все это оформить?

UPD:

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

Сделать интерфейс:

 public interface IApend
    {
         void Append(DataTable dt);
    }


Реализовать его в 2 ух классах, каждый из которых будет содержать в себе собственную
логику импорта:

       public class ImporterToSQL: IApend
    {
        //Какие-то поля
        public void Append(DataTable dt)
        {
            //Какая-то логика
        }
    }
    public class ImporterAccess : IApend
    {
        //Какие-то поля
        public void Append(DataTable dt)
        {
            //Какая-то логика
        }
    }


И создать такой класс:

   public class Importer
    {
        IApend Concrete;

        public Importer(IApend concrete)
        {
            Concrete = concrete;
        }
        static Importer ImporterToSQL(object v,object v2,object v3)
        {
            return new Importer(new ImporterToSQL(object v, object v2, object v3));
        }
        static Importer ImporterToSQL(object v, object v2)
        {
            return new Importer(new ImporterToSQL(object v, object v2));
        }
        static Importer ImporterToAccess(object v, object v2, object v3)
        {
            return new Importer(new ImporterAccess(object v, object v2, object v3)));
        }
        static Importer ImporterToAccess(object v, object v2)
        {
            return new Importer(new ImporterAccess(object v, object v2));
        }
        public void Append(DataTable dt)
        {
            Concrete.Append(dt);
        }
    }


На сколько это верно и на сколько хорошо этот подход будет работать в плане добавления
нового функционала(Например, мне приспичило добавить импорт из SQL в Access)? Это ,
вроде, зовется фабрикой классов?
    


Ответы

Ответ 1



Вы на правильном пути. Общий интерфейс для конвертации -- это правильно. Только я бы назвал его чуть поконкретнее, хотя б IImporter какой-нибудь. Что касается создания конкретных экземпляров и вашего класса Importer. Для создания действительно нужна фабрика. Однако она у вас получилась не в чистом виде. Смысла оборачивать экземпляр в ваш Importer нет -- у вас уже есть интерфейс. Просто создавайте нужные экземпляры и возвращайте их. А клиент уже будет вызывать напрямую методы: public static class ImporterFactory { static IImporter CreateSQLImporter(object v, object v2, object v3) { return new ImporterToSQL(v, v2, v3); } static IImporter CreateAccessImporter(object v, object v2, object v3) { return new ImporterAccess(v, v2, v3); } // и так далее } В качестве параметров для конструкторов у вас наверняка выступают специфичные для каждой реализации данные: например, строка подключения для SQL и путь к mdb файлу для Access. В идеале клиент вообще не должен знать что-то о деталях реализации, а фабрика должна содержать один метод на тип. Все детали о реализации прячутся в фабрике, которая подсовывает нужные параметры напрямую из конфигурации (например, из app.config). При этом клиент вообще ничего не будет знать о реализации: public static class ImporterFactory { static IImporter CreateImporter(object v) { var generalConfig = GetGeneralConfig(); if (generalConfig.Type == "sql") { var config = GetSqlImporterConfig(); return new ImporterToSQL(config.ConnectionString, v); } else if (generalConfig.Type == "access") { var config = GetAccessImporterConfig(); return new ImporterToAccess(config.ConnectionString, v); } } } Такой подход будет очень хорошо работать, если вам потребуется импорт в новое место. Вы просто напишете еще одну реализацию IImporter, внести изменения в ImporterFactory и все. Все клиентs, которые используют IImporter, менять не придется. В случае же отдельных методов вам придется менять вызывающий код, который управляет тем, какой метод вызвать. Что касается импорта из SQL в Access, то такую задачу описанным выше способом не решить. Выше мы рассмотрели только задачу "как данные, представленные в универсальном виде (DataTable), записать в конкретное место". Задачу импорта из SQL в Access можно переформулировать как "как извлечь данные из одного места и записать их в другое место". Такую задачу полезно разбить на два шага и ввести промежуточный этап. Какой? Правильно, получение данных в универсальном виде. Таким образом, шага будет два: Импорт из источника с преобразованием в универсальный вид Экспорт в другое место Соответственно, два основных контракта будут выглядеть как-то так: interface IImporter { DataTable Import(); } interface IExporter { void Export(DataTable data); } Реализация второго шага у вас уже есть, правда сейчас она называется импортом. При наличии двух шагов будет логичнее назвать их с т.з. того, как данные движутся относительно вашего приложения. Вы импортируете (загружаете) данные из одного источника и экспортируете (выгружаете) их в другое место. Реализация первого шага будет аналогична -- различные реализации интерфейсов, фабрика. Для осуществления процесса конвертации вам понадобится некоторая общая сущность, которая будет соединять шаги. Она может принимать в качестве зависимостей как фабрики, так и экземпляры импортера/экспортера: public class DataConverter // придумайте имя поудачнее, пожалуйста :) { private readonly IImporter _importer; private readonly IExporter _exporter; public DataConverter(IImporter importer, IExporter exporter) { _importer = importer; _exporter = exporter; } public void Convert() { var data = _importer.Import(); _exporter.Export(data); } }

суббота, 4 января 2020 г.

Чистый код: Непонятный закон Деметры

#ооп #проектирование


Сейчас читаю книгу ЧИстый код на небольшой главе про закон Деметры. До этого с ним
сталкивалась в паттернах от O'reilly (HeadFirst) и там было немного и понятно. После
чистого кода - в голове кавардак. Есть пара вопросов:

1) Мартин делит объекты на объекты и структуры данных. Что тут подразумевается под
структурой? Struct (в c# к примеру) или в целом объект класса в котором кроме открытых
полей (или свойств) нету больше ничего (методов, например)?

2) Из первого вопроса вытекает непонимание того, что подразумевается под гибридом
(наполовину объект, наполовина структура данных), который лучше не писать. Считается
ли гибридом, например, объект класса "Машина", где можно и цвет ее посмотреть, и колеса
даже поменять, и поведение у нее тоже есть?

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

4) Нарушается ли закон при разделении методов "плохих" на много маленьких?

Было

public class Class1
{
    public String DoSmth()
    {
        //Вроде как закон нарушен, потому что вызываем метод у вернувшегося значения
        return MethodClass1().DoSmth();
    }

    public Class2 MethodClass1()
    {
        return new Class2();
    }
}

public class Class2
{
    public String DoSmth()
    {
        String res2 = this.MethodClass2();
        return res2;
    }

    public String MethodClass2()
    {
        return "test";
    }
}


Стало

public class Class1
{
    public String DoSmth()
    {
        //теперь тут вызываются только методы того же класса
        Class2 res1 = MethodClass1();
        return this.MethodClass1_2(res1);
    }

    public Class2 MethodClass1()
    {
        return new Class2();
    }

    public String MethodClass1_2(Class2 val)
    {
        return val.DoSmth();
    }
}


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


Ответы

Ответ 1



В книге "Программист-прагматик" (Хант, Томас) закон Деметера (такой кривой перевод) сведён к короткому высказыванию: Минимизируйте связывание между модулями Пример из книги на C++ class Demeter { private: A* a; int func(); public: void example(B& b); } void Demeter::example(B& b) { int f = func(); // 1 b.invert(); // 2 a = new A(); a->setActive(); // 3 C c; c.print(); // 4 } Закон Деметры для функций гласит, что любой метод некоторого объекта может обращаться только к методам принадлежащим: самим себе любым параметрам, переданным в метод любым создаваемым им объектам любым непосредственно содержащимся объектам компонентов Исходя из этого можно дать ответ на ваш вопрос №3: уменьшение связанности облегчает модификацию и сопровождение кода, облегчает написание тестов. На практике это означает, что придётся создавать большое количество методов-оболочек, которые просто направляют запрос далее к делегату. Эти методы-оболочки влекут за собой расходы, ухудшая производительность и занимая память. Обращая закон Деметры и плотно связывая несколько модулей, можно получить выигрыш в производительности.

Ответ 2



1) Про структуру он там явно пишет: С другой стороны, если ctxt, Options и ScratchDir представляют собой обычные структуры данных, не обладающие поведением, то они естественным образом раскрывают свою внутреннюю структуру, а закон Деметры на них не распространяется. Поведение никак не скрыто, если вызывающий код обращается напрямую к данным, а не через get/set (это и есть простая структура, как в Си, например). В то же время, если обращение идёт через get/set ещё не значит, что там скрыто какое-то поведение (оно может быть там скрыто, но не факт) и поэтому: Применение функций доступа затрудняет ситуацию. 2) И про гибриды, вроде доступно: Гибриды содержат как функции для выполнения важных операций, так и открытые переменные или открытые методы чтения/записи, которые во всех отношениях делают приватные переменные открытыми. По-моему, сокрытие поведения и является ключевым фактором. Если есть какое-то поведение и оно скрыто, то это объект, если поведения нету, а просто открытые данные, то это структура, а если есть и скрытое поведение и открытые данные - вот вам и гибрид.

Ответ 3



Отвечу на 4-ый вопрос. Если "Было" и "Стало" оставить такими, в каком виде они у вас, то закон нарушается в обоих случаях. Какие тут есть "нарушения": Class1 по сути является оберткой для Class2 (по крайней мере из того кода, что вы предоставили) - такое поведение должно достигаться наследованием; Class1 очень сильно зависит от Class2 (Почему Class1, который не является фабрикой, занимается созданием экземпляра Class2?). Должно "Стать" так: public class Class2 { public RETURN_TYPE DoSmth() { // Что-то там делается } } public class Class1 { public RETURN_TYPE DoSmth(Class2 val) { /* Тут должна быть какая-то логика, но не тупой возврат, вроде: return val.DoSmth(); */ } } А ещё лучше будет, если экземпляр класса Class2 передать в конструктор класса Class1 и обращаться в методах к объекту класса Class2 через свойство класса Class1 - кстати, этого и требует в данном контексте "закон Деметры". При этом как зависимость надо передавать не сам класс, а интерфейс, который будет реализовывать Class2. На 3-ий вопрос вы сами же отчасти и ответили - код становится легче в сопровождении, поддержке, написании тестов, код становится менее сложным ля понимания, больше возможностей при повторном использовании кода (иначе код будет дублироваться), снижается зависимость классов друг от друга.