Как стать автором
Обновить

Комментарии 20

Спасибо за перевод!
Действительно, данный перевод не будет лишним для изучающих чистую архитектуру проектирования систем.
Добавлю свои 5 копеек из опыта.
Как бы круто не описать интерфейсный класс, но со временем из-за меняющихся требований в процессе разработки по независящим от нас причинам, приходится немного модифицировать интерфейс, нарушив принцип закрытости.

Но это не нарушает закрытость) Вы подготавливаете некий интерфейс и закладываете как может расширяться система. Но если «модель мира» уже не позволяет это делать, то это не нарушение. В велосипед нельзя заложить самолёт)

А надо закладывать, иначе очень больно из тысячи уже катающихся велосипедов делать самолёты на ходу и очень быстро :)

Как было сказано в самой статье и на чём я сакцентировал внимание в предисловии: ни одно программа не может быть на 100% закрыта. Это нужно принять. Какую бы продуманную абстракцию мы не построили, в любом случае могут появиться требования, которые заставить нас её изменить. Потому что правильность абстракций в каждой программе определяется прежде всего бизнес-требованиями. Следование же принципу открытости-закрытости просто позволяет свести изменения к минимуму и производить правильный рефакторинг. В примере из статьи самолётов не закладывали, но постепенно, стратегически вводили закрытость. Задача изначально была: рисовать фигуры. Потом требования дополнились: рисуй фигуры в правильной последовательности. Вот дополнили интерфейс соответствующим методом. Пришлось и DrawAllShapes поменять. Но теперь она закрыта от изменений требований в порядке рисования фигур. Но при этом всё ещё не закрыта от того, что допустим линии фигур надо рисовать в форме котёнка. Ну тут опять же надо изменять. Например введением аргумента в функцию Draw класса Shape типа DrawStrategy, которая воздействует на способ рисования линий. Либо же не изменять эту функцию, а просто ввести для фигур декораторы. Но тут нужно будет менять код, который эту функцию вызывает, чтобы он эти самые декораторы инициализировал и закинул в множество. Зависит от конкретной ситуации. Самое главное — держать в голове сам принцип и понимать, когда он нарушается, а когда нет и какие могут быть последствия.
В ООП мы ожидаем, что методы класса не закрыты от изменений переменных — членов этого класса. Однако мы ожидаем, что любой другой класс, включая подклассы, закрыты от изменений этих переменных. Это называется инкапсуляцией.

Это называется сокрытием, а не инкапсуляцией. В реализациях C# или C++ эти два понятия отождествляются, но если рассматривать ООП в общем, это не одно и то же. В Python развита инкапсуляция, но отсутствует сокрытие. В языке C с помощью Pimpl достигается полное сокрытие, но принципиально отсутствует инкапсуляция.

В языке C с помощью Pimpl достигается полное сокрытие, но принципиально отсутствует инкапсуляция.


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

В Python развита инкапсуляция,

Вообще, развита или не развита инкапсуляция зависит от конкретных разработчиков в конкретном проекте, если писать геттеры/сеттеры ко всем приватным полям в классах, никакой инкапсуляции не будет.
Можно ли измерить «инкапсуляцию»? Ну в идеале вот чтобы с графиками и отчетами по каждому коммиту: Вася молодец, повысил инкапсуляцию на пять единиц, давайте дадим ему премию. Или может наоборот, понять что инкапсуляция зашла слишком далеко и надо бы немного декапсулировать, а то чего они.
Можно ли измерить «инкапсуляцию»? Ну в идеале вот чтобы с графиками и отчетами по каждому коммиту: Вася молодец, повысил инкапсуляцию на пять единиц, давайте дадим ему премию. Или может наоборот, понять что инкапсуляция зашла слишком далеко и надо бы немного декапсулировать, а то чего они.

Стат. анализаторов умеющих делать оценки качества кода и всякие метрики по нему полно, но конкретно попытки измерять инкапсуляцию бессмысленны.
В этом плане полезнее смотреть на количество входящих/исходящих зависимостей(afferent/efferent coupling) среди модулей, в совокупности с цикломатической сложностью, то есть чтобы там где логика было минимум зависимостей, и Stable Dependencies Principle.
Если в проекте плохо с инкапсуляцией, это явно отразится на количестве зависимостей.


Но пытаться без технической экспертизы оценивать качество работы разработчиков очень плохая затея. Метрики не самоцель.

Если в проекте плохо с инкапсуляцией, это явно отразится на количестве зависимостей.

Высокая связность в коде вообще не зависит от инкапсуляции, это проблема архитектуры в целом.

Скотт Майерс высказывал интересную идею, что инкапсуляцию типа можно измерить количеством кода, который может сломаться при изменении реализации этого типа.

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

Язык С не поддерживает явно модули, методы и инкапсуляцию. Ограничение области видимости статических функций в пределах одной единицы трансляции является сокрытием. Данные по-прежнему отделены от функций, и вам нужно вручную их совмещать. Более того, передача функции указателя на другой, несовместимый тип не является ошибкой.


Вообще, развита или не развита инкапсуляция зависит от конкретных разработчиков в конкретном проекте

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

Дословно из оригинала:
We have a name for this expectation, we call it: encapsulation.

Но возможно я не правильно понял термин в контексте. Но я всегда полагал, что инкапсуляция в частности предполагает именно это. В чём конкретно разница между сокрытием и инкапсуляцией?

В принципе, если загуглить "difference between data hiding and encapsulation", все станет на свои места. Инкапсуляция это объединение кода и данных, а сокрытие — защита от доступа извне. В С++ сокрытие и инкапсуляция эквивалентны, но для других языков это не так. В целом, это разные вещи, которые могут использоваться как вместе, так и независимо.

Разговаривают два бизнес-коуча:
— Как повысить продажи?
— Могу рассказать.
— Рассказать я и сам могу, как повысить?


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

Это нужно делать итеративно. Ещё юнит-тесты очень хорошо помогают проектировать логику, но тут все-равно важно уметь писать юнит тесты, понимать когда они становятся слишком сложными/хрупкими, и представлять, почему плохо, например, возвращать мок из пока

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

Если бы была возможность формализовать требования к хорошему коду, его уже писал бы компьютер, но хороший код не может существовать в отрыве от бизнеса/задач
До середины статьи не покидало чувство, что я читаю про Dependency Inversion а не про Open-Closed. Потому как по сути там предлагают использовать абстракции, чтобы скрывать имплементацию методов от функций, которые принимают в качестве аргументов те или иные объекты. А вот «Эвристики и конвенции» уже к DI не относятся. Значит ли это, что OC включает в себя DI?
Да, DI — это один из инструментов, который обеспечивает OC.
я думаю DI больше о том как организовать зависимости между модулями в то время как OC про то что все зависимости должны внедряться через интерфейсы.
OC это более глобальный принцип. И достигается он в том числе и за счёт того, что зависимости внедряются через интерфейсы или говоря более общим словом абстракции. Об этом как раз говорит DI. Но не только. Принцип Подстановки Лисков также обепечивает выполнение OC за счёт того, что мы правильно реализуем иерархии наследования и клиентам базовых классов не нужно подстраиваться под детали реализации наследников, а значит не не надо изменятся, когда появляется новый наследник.

Мне кажется, что финальная версия функция рисования противоречит Single Responsible Principle. Здесь и сортировка и отрисовка. Хотя порядок определяется не в этой функции, но, тем не менее, он завязан на operator<, что не очень гибко. Например, нам может понадобится распечатывать объекты с другим порядком, тогда нужно передавать предикат. Лучше передавать в функцию уже упорядоченный список.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий