#ооп #архитектура #solid
Вот пример такой композиции в коде, как я понимаю нарушает принцип минимальной информированности
(Principle of Least Knowledge) (см. Закон Деметры):
class A:
def method1(self):
pass
def method2(self):
pass
def method3(self):
pass
class B:
def __init__(self):
self.a = A
def method1(self):
pass
b = B()
b.a.method1()
Однако, я заметил, что часто используют такой подход для расширения интерфейса класса.
Пример из джавы и C#:
System.out.println()
Пример из Джанго:
MyModel.objects.create()
Вот и интересно, когда расширять класс таким образом можно считать правильным?
Ответы
Ответ 1
Ограничения накладываются согласно здравому смыслу и предметной области. Я напишу о человеке. Человек, в большинстве случаев, может двигать конечностями, думать головой и делать ещё какие то действия, которые довольно поверхностны. Если вы пишете игру и человеки там - не предмет каких то модификаций, то доступны у них должны быть те же операции, что и в реальной жизни. Стоит иметь в виду, что для мед. оборудования или игр с модификацией людей - доступ может быть намного шире. Простой пример - человек.рука.двигать(). Что бы ни случилось в реализации, такой код останется валидным и не должен превращаться в человек.ДвигатьРукой() или человек.Двигать(рука). При этом, люди не умеют контролировать конкретные мышцы в руке, к примеру, а значит, объект должен скрывать собой внутренности реализации. Т.е. рука.Двигать() но не рука.мышцы[x].двинуть(y, z). Ещё проще - когда свойство обязательно и неизменно, обосновано предметной областью - можно ссылаться на него, иначе его должен скрывать основной класс.Ответ 2
Для начала нужно разобраться в чем проблема, когда один объект выставляет свои внутренности наружу, после чего станет понятным, когда это проблемой не является. Инкапсуляция и сокрытие информации направлены на упрощение разработки. Хороший дизайн позволяет сосредоточиться на минимальном числе концепций в один момент времени и ограничивает число изменений минимальным числом модулей в случае изменения требований. Когда объект выставляет свои внутренности наружу, то это может привести к более хрупкому решению. По своей природе, открытый интерфейс класса должен быть более стабильным, а детали реализации – скрытыми от его клиентов. В этом случае, реализация может быть заменена, не нарушая работу клиентов. Когда в открытый интерфейс просачиваются детали реализации, в виде открытых полей или даже свойств, то это ограничивает возможности автора класса на изменения. Код вида A.B.C.D.E.Foo() обладает низкой стабильностью, поскольку внесение изменений в один из 5 классов наверняка его сломает. С другой стороны, не любое открытое свойство раскрывает детали реализации. Иногда, подобные свойства являются частью самой сути решаемой проблемы. Например, у квадрата есть координаты и обращение вида: square.Position.X вполне может быть оправданным. Иногда, приходится нарушать данное правило по другой причине, например, при разработке библиотек. Любая сложная система является иерархической, и нам просто необходимо группировать связанные концепции. Тот же System.out из мира Java является подобным примером. Мы хотим объединить все системные операции за некоторым фасадом, который будет удобной точкой входа для исследования. В этом плане мы не собираемся перемещать out в другой класс, и точно не будем дублировать операции из PrintStream в классе System. Итак, в качестве заключения: закон Деметры не нарушается, если отношение целое-часть является частью предметной области. Закон Деметры не нарушается в случае фасадных классов, единственная задача которых предоставить доступ к некоторым объеткам. Но закон Деметры нарушается, если выставленное свойство является деталью реализации, которая может измениться в будущем.
Комментариев нет:
Отправить комментарий