Я понимаю мотив, лежащий в основе принципа наименьшего количества знаний , но я нахожу некоторые недостатки, если пытаюсь применить его в своем проекте.
Один из примеров этого принципа (на самом деле, как его не использовать), который я нашел в книге Head First Design Patterns, указывает на то, что неправильно вызывать метод для объектов, которые были возвращены из вызова других методов, в терминах этого принципа. ,
Но, похоже, иногда очень необходимо использовать такую возможность.
Например: у меня есть несколько классов: класс захвата видео, класс кодировщика, класс streamer, и все они используют какой-то другой базовый класс VideoFrame, и, поскольку они взаимодействуют друг с другом, они могут делать, например, что-то вроде этого:
streamer
код класса
...
frame = encoder->WaitEncoderFrame()
frame->DoOrGetSomething();
....
Как видите, этот принцип здесь не применяется. Может ли этот принцип применяться здесь, или же этот принцип не всегда может быть применен в такой конструкции?
источник
Ответы:
Принцип, о котором вы говорите (более известный как Закон Деметры ) для функций, можно применить, добавив еще один вспомогательный метод к вашему классу стримеров, например
Теперь каждая функция «общается только с друзьями», а не с «друзьями друзей».
ИМХО, это приблизительное руководство, которое может помочь в создании методов, которые более точно следуют принципу единой ответственности. В простом случае, подобном приведенному выше, это, вероятно, очень самоуверенно, если это действительно стоит хлопот и если полученный код действительно «чище», или если он просто формально расширит ваш код без какого-либо заметного выигрыша.
источник
Принцип наименьшего знания или Закон Деметры - это предупреждение против запутывания вашего класса деталями других классов, которые пересекают слой за слоем. Он говорит вам, что лучше разговаривать только со своими «друзьями», а не с «друзьями друзей».
Представьте, что вас попросили приварить щит к статуе рыцаря в блестящей броне. Вы осторожно размещаете щит на левой руке, чтобы он выглядел естественно. Вы замечаете, что на предплечье, локте и плече есть три небольших места, где щит касается доспехов. Вы свариваете все три места, потому что хотите быть уверенными, что связь прочная. Теперь представьте, что ваш босс злится, потому что он не может сдвинуть локоть своей брони. Вы предполагали, что броня никогда не будет двигаться, и создали неподвижную связь между предплечьем и предплечьем. Щит должен подключаться только к его другу, предплечью. Не предплечья, друзья. Даже если вам нужно добавить кусок металла, чтобы они соприкасались.
Метафоры хороши, но что мы на самом деле подразумеваем под другом? Любая вещь, которую объект знает, как создать или найти, является другом. Кроме того, объект может просто попросить передать другие объекты , о которых он знает только интерфейс. Они не считаются друзьями, потому что не ожидают, как их получить. Если объект не знает, откуда он взялся, потому что что-то еще прошло / впрыснуло это, то это не друг друга, это даже не друг. Это то, что объект знает только как использовать. Это хорошая вещь.
Когда вы пытаетесь применять такие принципы, важно понимать, что они никогда не запрещают вам что-то делать. Они являются предупреждением о том, что вы, возможно, пренебрегаете дополнительной работой для достижения лучшего дизайна, который выполняет то же самое.
Никто не хочет делать работу без причины, поэтому важно понимать, что вы получаете после этого. В этом случае он сохраняет ваш код гибким. Вы можете вносить изменения и меньше беспокоиться о других классах. Это звучит хорошо, но не поможет вам решить, что делать, если вы не примете это за какую-то религиозную доктрину.
Вместо слепого следования этому принципу возьмите простую версию этой проблемы. Напишите решение, которое не следует этому принципу и которое соответствует. Теперь, когда у вас есть два решения, вы можете сравнить, насколько каждый восприимчив к изменениям, попытавшись внести их в оба.
Если вы не можете решить проблему, следуя этому принципу, скорее всего, вам не хватает другого навыка.
Одним из решений вашей конкретной проблемы является внедрение
frame
чего-либо (класса или метода), который знает, как общаться с фреймами, чтобы вам не приходилось распространять все эти детали фрейма чата в вашем классе, который теперь знает только, как и когда получить кадр.Это на самом деле следует другому принципу: отделить использование от строительства.
Используя этот код, вы взяли на себя ответственность за то, чтобы каким-то образом приобрести
Frame
. Вы еще не взяли на себя ответственность за разговор сFrame
.Теперь вы должны знать, как разговаривать
Frame
, но замените это на:И теперь вам нужно только знать, как разговаривать с вашим другом, FrameHandler.
Есть много способов достичь этого, и это, возможно, не самый лучший, но он показывает, как следование принципу не делает проблему неразрешимой. Это просто требует больше работы от вас.
У каждого хорошего правила есть исключение. Лучшие примеры, которые я знаю, это внутренние языки, специфичные для предметной области . A DSL сек цепной методпохоже, все время нарушает закон Деметры, потому что вы постоянно вызываете методы, которые возвращают разные типы, и используете их напрямую. Почему это нормально? Потому что в DSL все, что возвращается, это тщательно разработанный друг, с которым вам нужно поговорить напрямую. По замыслу вы получили право ожидать, что цепочка методов DSL не изменится. У вас нет этого права, если вы просто случайно копаетесь в кодовой базе, объединяя все, что найдете. Лучшие DSL - это очень тонкие представления или интерфейсы с другими объектами, в которые вы, вероятно, не должны углубляться. Я упоминаю об этом только потому, что обнаружил, что понял закон деметры гораздо лучше, когда узнал, почему DSL - хороший дизайн. Некоторые заходят так далеко, что говорят, что DSL даже не нарушают настоящий закон Деметры.
Другое решение состоит в том, чтобы позволить что-то еще внедрить
frame
в вас. Если выframe
пришли от сеттера или, предпочтительно, от конструктора, вы не берете на себя никакой ответственности за создание или приобретение фрейма. Это означает, что ваша роль здесь намного больше, чемFrameHandlers
должна была быть. Вместо этого теперь вы - тот, с кем общаетесьFrame
и заставляете что-то еще выяснить, как это сделать.Frame
В каком-то смысле это то же самое решение с простой сменой перспективы.В SOLID принципов являются большим я стараюсь следовать. Здесь соблюдаются два принципа: принцип единой ответственности и принцип инверсии. Это действительно трудно уважать этих двоих и все же в конечном итоге нарушать закон Деметры.
Менталитет, который нарушает Деметру, подобен еде в буфете, где вы просто берете все, что хотите. Немного поработав заранее, вы можете предоставить себе меню и сервер, который доставит вам все, что вам понравится. Откиньтесь на спинку кресла, расслабьтесь и хорошо наклоните голову.
источник
Функциональный дизайн лучше, чем объектно-ориентированный дизайн? Это зависит.
MVVM лучше чем MVC? Это зависит.
Амос и Энди или Мартин и Льюис? Это зависит.
От чего это зависит? Выбор, который вы делаете, зависит от того, насколько хорошо каждый метод или технология соответствует функциональным и нефункциональным требованиям вашего программного обеспечения, в то же время адекватно удовлетворяя вашим целям разработки, производительности и удобства обслуживания.
Когда вы читаете это в книге или блоге, оцените претензию на основе ее достоинств; то есть спроси почему. В разработке программного обеспечения нет правильного или неправильного метода, есть только «насколько хорошо этот метод отвечает моим целям? Является ли он эффективным или неэффективным? Решает ли он одну проблему, но создает новую? Может ли она быть понятна всем команда разработчиков, или это слишком малоизвестно?
В этом конкретном случае - акте вызова метода для объекта, возвращенного другим методом - поскольку существует фактический шаблон проектирования, который кодифицирует эту практику (Фабрика), трудно представить, как можно утверждать, что он категорически неправильно.
Причина, по которой он называется «Принципом наименьшего знания», заключается в том, что «Низкая связь» является желательным качеством системы. Объекты, которые не тесно связаны друг с другом, работают более независимо, и поэтому их легче поддерживать и изменять индивидуально. Но, как показывает ваш пример, бывают ситуации, когда более высокая связь является более желательной, чтобы объекты могли более эффективно координировать свои усилия.
источник
Ответ Дока Брауна показывает классическую реализацию закона Деметры в учебнике - и раздражение / неорганизованность - добавление десятков методов таким образом, вероятно, поэтому программисты, в том числе и я, часто не беспокоятся об этом, даже если им и следует.
Существует альтернативный способ отделить иерархию объектов:
В случае оригинального плаката (OP)
encoder->WaitEncoderFrame()
будет возвращатьсяIEncoderFrame
вместо aFrame
и будет определяться, какие операции допустимы.РЕШЕНИЕ 1
В простейшем случае,
Frame
иEncoder
классы находятся под вашим контролем,IEncoderFrame
является подмножеством методов Рама уже публично разоблачает, иEncoder
класс не на самом деле все равно , что вы делаете для этого объекта. Затем реализация тривиальна ( код на c # ):РЕШЕНИЕ 2
В промежуточном случае, когда
Frame
определение не находится под вашим контролем, или было бы неуместно добавлятьIEncoderFrame
методы кFrame
, тогда хорошим решением является Адаптер . Это то , что ответ CandiedOrange в обсуждает, как иnew FrameHandler( frame )
. ВАЖНО: если вы сделаете это, будет более гибко, если вы представите его как интерфейс , а не как класс .Encoder
должен знать оclass FrameHandler
, но клиенты должны знать толькоinterface IFrameHandler
. Или, как я его назвал,interface IEncoderFrame
- чтобы указать, что это именно Frame, как видно из POV Encoder :СТОИМОСТЬ: Выделение и сборщик мусора нового объекта, EncoderFrameWrapper, каждый раз
encoder.TheFrame
вызывается. (Вы можете кэшировать эту оболочку, но это добавляет больше кода. И надежно кодировать легко, только если поле кадра кодировщика не может быть заменено новым кадром.)РЕШЕНИЕ 3
В более сложном случае новая обертка должна была бы знать и то,
Encoder
и другоеFrame
. Этот объект сам по себе будет нарушать LoD - он манипулирует отношениями между Encoder и Frame, что должно быть ответственностью Encoder - и, вероятно, будет проблемой, чтобы получить права. Вот что может произойти, если вы начнете идти по этому пути:Это стало ужасно. Существует менее запутанная реализация, когда оболочка должна коснуться деталей своего создателя / владельца (Encoder):
Конечно, если бы я знал, что окажусь здесь, я бы этого не сделал. Могли бы просто написать методы LoD и покончить с этим. Нет необходимости определять интерфейс. С другой стороны, мне нравится, что интерфейс объединяет связанные методы. Мне нравится, каково это делать «подобные фрейму операции» с тем, что похоже на фрейм.
ЗАКЛЮЧИТЕЛЬНЫЕ КОММЕНТАРИИ
Подумайте над этим: если разработчик
Encoder
полагал, что экспонированиеFrame frame
соответствовало их общей архитектуре или было «намного проще, чем реализация LoD», то было бы намного безопаснее, если бы вместо этого он выполнил первый фрагмент, который я показываю, - выставил ограниченное подмножество Рамка, как интерфейс. По моему опыту, это часто вполне работоспособное решение. Просто добавьте методы в интерфейс по мере необходимости. (Я говорю о сценарии, в котором мы «знаем», что у Frame уже есть необходимые методы, или их было бы легко и без споров добавить. Работа по «реализации» для каждого метода заключается в добавлении одной строки в определение интерфейса.) И Знайте, что даже в худшем будущем этот API можно сохранить работоспособным - здесь, удаливIEncoderFrame
из .Frame
Encoder
Также обратите внимание , что если у вас нет разрешения на добавление
IEncoderFrame
кFrame
, или необходимые методы не очень хорошо подходят к общемуFrame
классу, и решение # 2 не подходит, возможно , из-за дополнительного объекта создания-и-разрушения, Решение № 3 можно рассматривать как простой способ организации методовEncoder
выполнения LoD. Не просто пройти через десятки методов. Оберните их в интерфейс и используйте «явную реализацию интерфейса» (если вы находитесь в c #), чтобы к ним можно было получить доступ только тогда, когда объект просматривается через этот интерфейс.Еще один момент, который я хочу подчеркнуть, заключается в том, что решение выставить функциональность в качестве интерфейса , обрабатывается всеми 3 ситуациями, описанными выше. Во-первых,
IEncoderFrame
это просто наборFrame
функций России. Во вторых,IEncoderFrame
это адаптер. В третьих,IEncoderFrame
это разделение поEncoder
функциональности. Не имеет значения, изменяются ли ваши потребности между этими тремя ситуациями: API остается неизменным.источник