Здравствуйте, Codealot, Вы писали:
C>Некоторые люди делают для каждого класса по интерфейсу и фабрике чтобы создавать объекты, причем каждый интерфейс реализован ровно в одном классе. C>В этом есть какой-то тайный смысл, или они просто идиоты?
Так иногда делают чтобы отделить интерфейс от реализации. Представьте, что есть очень жирный и большой класс, и это несмотря на то, что у него всего лишь два публичных метода. А теперь задача — найти среди этих тысяч строк реализации одгого класса чего же от него могут хотеть те, кто его используют.
Это становится похоже на поиск иголок в стоге сена — и по времени и по ментальным трудозатратам. Вот тогда иногда и делают отдельный интерфейс, который строго завляет — нужно только 2 таких вот метода.
А тысячи строк реализации — это уже дело второстепенное и совершенно неинтересное, особенно если над проектом работает несколько людей со своими зонами ответственности. Им нырять в чужие тысячи строк очень больно, но беглым взглядом увидеть суть в коротком определении интерфейса — очень легко и понятно.
Это классика подхода под названием "сервисо-ориентированая архитектура".
Здравствуйте, Aquilaware, Вы писали:
A>Так иногда делают чтобы отделить интерфейс от реализации. Представьте, что есть очень жирный и большой класс, и это несмотря на то, что у него всего лишь два публичных метода. А теперь задача — найти среди этих тысяч строк реализации одгого класса чего же от него могут хотеть те, кто его используют.
Публичные методы — вверх. Всё.
И это все равно не тот случай.
Здравствуйте, Codealot, Вы писали:
C>Некоторые люди делают для каждого класса по интерфейсу и фабрике чтобы создавать объекты, причем каждый интерфейс реализован ровно в одном классе. C>В этом есть какой-то тайный смысл, или они просто идиоты?
AG>1) Если в качестве борьбы с перекрёстными ссылками? AG>2) Для представления древовидных структур, с коллекциями элементов того же типа, что и корневой элемент? AG>Когда в корневом элементе требуется иметь коллекцию child-элементов. AG>как тогда быть? AG>P.S. Подразумеваю C++ и наличие файлов *.h & *.cpp
Не вижу препятствий.
class child;
class parent
{
std::vector<std::shared_ptr<child>> childs;
};
Здравствуйте, уважаемый B0FEE664, Вы писали:
AG>Для представления древовидных структур, с коллекциями элементов того же типа, что и корневой элемент? AG>Когда в корневом элементе требуется иметь коллекцию child-элементов.
BFE>Не вижу препятствий.
BFE>
Элемент дерева, как логично я заметил выше, один и тот же.
Для каких-то других элементов — он является parent-ом.
Для других — чайлдом.
Ссылочку на этот чайлд хранит родитель (parent) в своей коллекции.
То, что Вы писали — это два разных класса.
Я бы предложил так:
class Node : public INode // Класс реализации узла (header):
{
public:
Node(const std::string& sNameOfNode); // C-tor
~Node(); // D-tor
std::string GetNameOfNode() const override;
std::vector<std::shared_ptr<INode>> GetVectOfChilds() const override;
void AddNode(std::shared_ptr<INode> p) override;
};
Здесь и у Вас, и у меня — две сушности.
Но у меня — универсальный узел и интерфейс к нему.
Именно наличие программного интерфейса — позволяет уменьшить связанность (coupling) в проекте.
За счёт этого, получим упрощение архитектуры и уменьшение количества ошибок.
Конечно же, можно было обойтись и без интерфейса, однако так красивее и понятнее.
Кроме того, имеется заложенная база — как для развития (новые типы узлов),
так и для тестирования основы нашего проекта.
Здравствуйте, Codealot, Вы писали:
C>Публичные методы — вверх. Всё. C>И это все равно не тот случай.
Различать всё-таки следует понятия: класс и модуль:
Класс — может выступать элементом внутренней структуры.
Его публичные методы — вполне возможно это внутренние шестерёнки,
оставленные открытыми только для работы с объектами этого класса его "начальниками" (клиентами).
Никто другой не работает с этим классом. Большинству окружающих даже не важно, что эти методы public.
В терминологии C++ есть понятие friend. Иногда я даже пользуюсь этим, нарушая принцыпы ООД.
То есть: публичные методы класса — не факт, что являются входами/выходами всго модуля (или даже подсистемы).
Чего уже НЕ СКАЖЕМ об интерфейсе: здесь методы именно определяющие взаимодействие модуля с внешним миром.
Они обязаны быть максимально независимыми от внутренней реализации. Простой "публичностью" здесь уже не обойтись.
P.S. Вот поэтому, реализация (даже если и единичная) нашего модуля ИМХО заслуживает наличия собственного интерфейса.
Это, казалось бы, усложнение — делает понимание кодов проекта проще и быстрее. Оно экономит силы и время (в том числе даже и
самому автору разработки, когда по воле случая он раскроет свои же коды 5-ти летней давности).
Здравствуйте, AlexGin, Вы писали:
AG>Конечно же, можно было обойтись и без интерфейса, однако так красивее и понятнее. AG>Кроме того, имеется заложенная база — как для развития (новые типы узлов), AG>так и для тестирования основы нашего проекта.
не просто "красивее", но и безопасней
1. контракт зафиксирован == клиенты могут начать разработку до того как будет закончена имплементация. клиенты могут писать юнит-тесты мокируя объект
2. имплементация спрятана от клиентов, что не позволяет особо продвинутым все сломать залазя туда своими кривыми руками
3. никто от балды не может добавить "полезный" метод в интерфейс
4. можно заменить реализацию и использовать две параллельно
5. интерфейс вообще можно отделить в отдельную либу и отправить на сторону клиента(например если мы пишем сетевые сервисы)
Поковырявшись еще в проекте, нашел немало перлов типа такого:
if (text.ToUpper().Contains(...))
doSomething();
else if (text.ToUpper().Contains(...))
doSomethingElse();
else if (text.ToUpper().Contains(...))
doSomethingDifferentElse();
Так что вопрос о качестве кода можно закрывать. Джуниор, которого покусал Гамма.
Всех, пто придумывал оправдания такому коду — поздравляю.
Здравствуйте, Codealot, Вы писали:
AG>Его публичные методы — вполне возможно это внутренние шестерёнки,
C>Значит, они не должны быть публичными. Всё.
Да, именно, внутренние — для модуля.
Они могут выходить за пределы класса. Могут быть публичными для класса.
C>Вот так, не умея использовать базовые конструкции языка, люди придумывают какую-то хрень.
Вот так, люди не писавшие ничего сложнее "Hello world!", пытаются навязать своё видиние единственно правильной архитектуры ПО.
Здравствуйте, AlexGin, Вы писали:
AG>Да, именно, внутренние — для модуля.
У тебя какое-то странное понимание модуля. Как и всего остального.
AG>Вот так, люди не писавшие ничего сложнее "Hello world!", пытаются навязать своё видиние единственно правильной архитектуры ПО.
Пальцем в небо. Очень сильно сомневаюсь, что у тебя есть хотя бы малейшие реальные основания для таких понтов.
Здравствуйте, Codealot, Вы писали:
Y>>Имелось в виду, что будут. Но потом. Но не срослось.
C>YAGNI. Если понадобится — тогда и надо добавлять.
потом не получится, правильно надо делать сразу.
пример, работал я над проектом в котором повсюду встречался код:
... // код какого-то метода
someObj.setField(SomeConfig.getInstance().getFieldValue())
... // код продолжается
и все, пока в mockito не появился mockStatic никаких зубодробительных юнит-тестов не напишешь. а если это не java, то возможно вообще не напишешь
EDITED:
тоже относится к методам которые, например вычисляют даты и время у себя внутри. ты их не протестируешь потому как значения всегда будут разные
Здравствуйте, AlexGin, Вы писали:
AG>Здесь и у Вас, и у меня — две сушности.
ну никто не мешает сделать одну:
class node
{
std::vector<std::shared_ptr<node>> childs;
};
AG>Но у меня — универсальный узел и интерфейс к нему.
Нет, у тебя узел и иузел.
AG>Именно наличие программного интерфейса — позволяет уменьшить связанность (coupling) в проекте.
А конкретно в данном случае что именно уменьшается?
AG>За счёт этого, получим упрощение архитектуры и уменьшение количества ошибок.
Введение новой сущности интерфейса не упрощает, а усложняет.
AG>Конечно же, можно было обойтись и без интерфейса, однако так красивее и понятнее.
Эмоции. "Я так всегда делал и мне нравится"
AG>Кроме того, имеется заложенная база — как для развития (новые типы узлов),
yagni. Вот _если_ появятся новые узлы — вот тогда и добавим интерфейс. Впрочем, тут тоже могу условно согласиться. Если средство разработки не умеет в рефакторинг "выделить интерфейс", то приходится прыгать... впрочем и без авто-тулзов ввести интерфейс _при необходимости_ относительно просто. Проще, чем постоянно копаться в сотнях интерфейсов, созданных на всякий случай.
AG>так и для тестирования основы нашего проекта.
Вот тут могу согласиться для некоторых ЯП. Если ЯП не позволяет тестировать без интерфейсов, то их приходится вводить на каждый чих.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, AlexGin, Вы писали:
AG>Чего уже НЕ СКАЖЕМ об интерфейсе: здесь методы именно определяющие взаимодействие модуля с внешним миром.
Это прост какие-то соглашения. Раз интерфейс, давайте его считать чем-то особым и назовём "интерфейсом модуля". Врочем, это опять всё от конкретного ЯП зависит. В плюсах там с модульностью вообще плохо. Вот и приходится опираться на определённые соглашения.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
AG>>Но у меня — универсальный узел и интерфейс к нему. ·>Нет, у тебя узел и иузел.
Напомню, что в терминологии C++ (в отличие от C# и Java) понятие интерфейс отсутствует.
Вместо него обычно применяется: абстрактный_класс: https://www.tutorialspoint.com/cplusplus/cpp_interfaces.htm
AG>>Именно наличие программного интерфейса — позволяет уменьшить связанность (coupling) в проекте. ·>А конкретно в данном случае что именно уменьшается?
Показана идея применения абстрактного_класса C++ в качестве интерфейса.
Здесь (в приведенном примере) всё просто, поэтому избыточной связанности нет.
AG>>За счёт этого, получим упрощение архитектуры и уменьшение количества ошибок. ·>Введение новой сущности интерфейса не упрощает, а усложняет.
Конкретно здесь — упрощает понимание (даже если незначительно и увеличивает объём кода).
AG>>Конечно же, можно было обойтись и без интерфейса, однако так красивее и понятнее. ·>Эмоции. "Я так всегда делал и мне нравится"
Если быть точным, то делал я так не всегда.
Первые лет пять/шесть разработки на C++ я практически не применял абстрактные классы.
Но примерно с 2015-го понял, что такой подход упрощает понимание проекта.
Даже мне самому легче понять то, что я разработал несколько лет назад.
В литературе это именуют: best practices.
P.S. Мы не ждём, когда спроектируются новые узлы поезда/вагоны, а подготавливаем проект "рельсового пути" для существующих и новых поездов.
Вы же предлагаете проектировать новые рельсы, как только появятся новые вагоны.
YAGNI — если бы я предлагал проектировать поезд на магнитной подушке, для поездки по линии длиной 5 км, где вполне хватит обычного трамвая.
Здравствуйте, baxton_ulf, Вы писали:
_>пример, работал я над проектом в котором повсюду встречался код: _>
_> ... // код какого-то метода
_> someObj.setField(SomeConfig.getInstance().getFieldValue())
_> ... // код продолжается
_>
_>и все, пока в mockito не появился mockStatic никаких зубодробительных юнит-тестов не напишешь. а если это не java, то возможно вообще не напишешь
Элементарно рефакторится.
Начнем с того, что SomeConfig.getInstance().getFieldValue() — это лютый копипаст и так делать нельзя и от такого нужно избавляться мгновенно на автомате как только увидел такое.
Ну и переписывается это на что то вроде
someObj.setField(ConfigHelper.getFieldValue())
,
причем можно еще и static import сделать для читаемости.
ConfigHelper уже пишется таким образом, что уже достаточно легко можно определять с каким конфигом работать, тестовым или нет, причем переопределять все в рантайме. Непосредственно в тестах быстро пишется утилита, которая позволяет работать с разными конфигами.
И все — все дополнительные сложности локализованы в одном месте, очень быстрым легким и безопасным рефакторингом, весь остальной код именно что жестко перелопачивать не требуется. Уже утилитные Helper классы пишутся с нуля, там могут быть любые навороты.
А далее уже можно подумать над использованием фреймворков, где уже есть всякие DI в зависимости от контекста, где уже задача нормального тестирования, мокирования, подмены конфигов и т.д работает из коробки и т.д. Если основной код требуется сделать независим от таких фреймворков — ок, хорошо, просто используем такие фреймворки только в тестах, делая мостик между тем, как положено там тестировать и нашими костылями с подменой конфига.
Здравствуйте, elmal, Вы писали:
E>Начнем с того, что SomeConfig.getInstance().getFieldValue() — это лютый копипаст и так делать нельзя и от такого нужно избавляться мгновенно на автомате как только увидел такое. E>Ну и переписывается это на что то вроде E>
, E>причем можно еще и static import сделать для читаемости. E>ConfigHelper уже пишется таким образом, что уже достаточно легко можно определять с каким конфигом работать, тестовым или нет, причем переопределять все в рантайме. Непосредственно в тестах быстро пишется утилита, которая позволяет работать с разными конфигами.
А что, getInstance() не даст переопределить конфиг на любой тестовый?
Кроме чуть более длинной записи (и то можно результат getInstance() закэшировать в пределах метода) — проблемы не видно.
А делать на каждое поле отдельный метод ещё и в ConfigHelper — задолбёшься.
Хотя если это ConfigHelper().getFooValue() — то ещё более-менее норм.
E>А далее уже можно подумать над использованием фреймворков, где уже есть всякие DI в зависимости от контекста, где уже задача нормального тестирования, мокирования, подмены конфигов и т.д работает из коробки и т.д. Если основной код требуется сделать независим от таких фреймворков — ок, хорошо, просто используем такие фреймворки только в тестах, делая мостик между тем, как положено там тестировать и нашими костылями с подменой конфига.
Ну так подменять объект конфигурации для теста — очевидность.
Здравствуйте, B0FEE664, Вы писали:
BFE>В конторе, где я сейчас работаю, прямо в правилах кодирования есть пункт запрещающий создавать интерфейс если его реализация будет ровно в одном классе. Причин не знаю.
Причина проста. Кода должно быть ровно столько, сколько необходимо. Минимальное количество кода — имеем одну реализацию. Потребовалась другая — выделяем интерфейс. Выделяем когда потребуется, а не потому, что так "положено".
Меньше кода — это легче читается, легче читается — проще понимание, проще понимание — делаешь меньше ошибок, делаешь меньше ошибок — задача быстрее будет сдана. Меньше кода — это более легкие и более быстрые изменения, это меньше проблемами с мержем с другими и т.д. При развитии можно все сложности во всякие библиотеки и утилиты добавлять, а основной код должен быть минимальным по объему. А библиотеки и утилиты — там вообще никаких классов, там тупо набор легко тестируемых функций.