Pull to refresh

Comments 19

UFO just landed and posted this here

Для лучшего понимания я пытался сформулировать эти принципы своими словами, может быть, кому-то это поможет:

SRP

Single-responsibility principle, принцип единственной ответственности. Предполагает проектирование классов, имеющих только одну причину для изменения, позволяет вести проектирование в направлении, противоположном созданию «Божественных объектов». Класс должен отвечать за удовлетворение запросов только одной группы лиц.

OCP

Open–closed principle, принцип открытости/закрытости. Классы должны быть закрыты от изменения (чтобы код, опирающийся на эти классы, не нуждался в обновлении), но открыты для расширения (классу можно добавить новое поведение). Вкратце — хочешь изменить поведение класса — не трогай старый код (не считая рефакторинга, т. е. изменение программы без изменения внешнего поведения), добавь новый. Если расширение требований ведет к значительным изменениям в существующем коде, значит, были допущены архитектурные ошибки.

LSP

Liskov substitution principle, принцип подстановки Барбары Лисков: поведение наследующих классов должно быть ожидаемым для кода, использующего переменную базового класса. Или, другими словами, подкласс не должен требовать от вызывающего кода больше, чем базовый класс, и не должен предоставлять вызывающему коду меньше, чем базовый класс.

ISP

Interface segregation principle, принцип разделения интерфейса. Клиент интерфейса не должен зависеть от неиспользуемых методов. В соответствии с принципом ISP рекомендуется создавать минималистичные интерфейсы, содержащие минимальное количество специфичных методов. Если пользователь интерфейса не пользуется каким-либо методом интерфейса, то лучше создать новый интерфейс, без этого метода.

DIP

Dependency inversion principle, принцип инверсии зависимостей. Модули верхнего уровня не должны обращаться к модулям нижнего уровня напрямую, между ними должна быть «прокладка» из абстракций (т. е. интерфейсов). Причем абстракции не должны зависеть от реализаций, реализации должны зависеть от абстракций.

Больше — на GitHub'е.

@Technoit, сам Роберт Мартин считал SRP самым трудным для понимания и уточнял, что в первую очередь под "единой ответственностью" имеется в виду ответственность группы лиц, т. е. класс не должен закрывать потребности, например, бухгалтерии и транспортного отдела.

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

Для меня вот SRP нигде не противоречит OCP. Если у вас это происходит «зачастую» — сможете показать хотя бы три примера?

На первый принцип еще посмотреть надо. Его растолковывают не всегда так, как подразумевал Мартин.

Примеры выглядят: когда долго писал на java, но потом надо было на питоне наваять.

OpenClose принцип можно интерпретировать как открыто для использования, но реализация должна быть скрыта. Если говорить про ООП, то класс предоставляет к использованию открытые методы (и все они предназначены для использования извне) и скрывает детали реализации - всё остальное скрыто. Открыть то, что нужно и скрыть всё остальное, чтоб не дать запутаться в дебрях реализации.

На более высоком уровне - это разделение компонентов на API и реализацию. Всё, что доступно для использования вынесено в API. При этом компонент с API имеет минимум зависимостей (норма - если вообще не имеет, или имеет зависимости только от других, более высокоуровневых API). И при использовании код зависит только от API и при этом скрываются все зависимости от реализации.

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

Ещё можно понять, почему Rectangle — датакласс (хотя это тоже спорный вопрос), но Printer у меня совсем не ассоциируется с данными.

Однако даже в Rectangle и Square датаклассы используются очень необычным образом. Поля объявлены как private, а потом отдельно объявлены одноимённые свойства. Но зачем датакласс тогда нужен, если ни одно из преимуществ датаклассов не используется?

Было интересно услышать доводы того, кто поставил минус.

Я с Вами согласен, что датаклассы используются не по назначению и выглядят глупо на маленьких задачах. Я старался придерживаться общего стиля в написании. Раскройте более подробно Вашу мысль "Их используют, когда объект предназначен для хранения значений, к которым осуществляется доступ через точку, а в примерах полноценные классы с методами ". Буду Вам благодарен.
Комментарий конструктивный +

Имеется ввиду, что dataclass берет на себя заботу о бойлерплейте. В частности, об __init__. Часть классов, к которым вы применяете этот декоратор, не подразумевает инициализацию данными. Соответственно, функционал датакласса никак не используется, а потому не нужен.

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

Вот цитата из PEP 557, посвящённого датаклассам:

Although they use a very different mechanism, Data Classes
can be thought of as “mutable namedtuples with defaults”.

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

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

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

А вот если у нас есть класс для принтера, то это уже не данные. Это некоторая абстракция, с которой мы будем взаимодействовать с помощью методов. То есть, принтер — это не запись с именованными полями. Да, внутри, конечно, какие-то атрибуты есть, но они должны быть скрыты. Да и к чему классу принтера методы для работы с полями, которые добавит декоратор?

А вот можно вопрос про сохранение?

Почему добавление, удаление и вывод в классе реестра норм, а вот сохранение - уже избыточно?

Операция ведь только для реестра предназначена, и актор у нее один - реестр.

Потому что это не функционал реестра. SaveManager сохраняет данные в сторонний файл. Позже этот класс можно будет использовать для сохранения иных сущностей.

В разделе про ISP в коде верное решение не указано, а только показана проблема (что пришлось сделать заглушку с исключением)? Или я не совсем понял, как код связан с текстом.

Отличная статья, спасибо!

Если вдруг напишите подобного рода статью про многопоточность в Python (pyside/pyqt), с радостью прочитаю!

По моему в ISP в решении проблемы с принтером код как раз с проблемой, которого не хватает в проблеме, а кода решения нет.

Sign up to leave a comment.

Articles