Solid принципы программирования python

novikov-nsa / solid_in_python.md

SOLID — это мнемоническая аббревиатура для набора принципов проектирования, созданных для разработки программного обеспечения при помощи объектно-ориентированных языков. Принципы SOLID направленны на содействие разработки более простого, надежного и обновляемого кода. Каждая буква в аббревиатуре SOLID соответствует одному принципу разработки.

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

Для понимания SOLID принципов, вы должны хорошо понимать как, используются интерфейсы.

Я попытаюсь объяснить принципы SOLID на примере Python в как можно более простой форме, чтобы даже новички смогли разобраться. Чтобы было очень легко взять представленные примеры и применить их на Python.

Рассмотрим каждый принцип один за другим:

1. Single Responsibility Principle
(Принцип единственной обязанности)

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

# Below is Given a class which has two responsibilities class User: def __init__(self, name: str): self.name = name def get_name(self) -> str: pass def save(self, user: User): pass

Мы имеем класс User, который ответственен за две работы — свойства пользователя и управление базой данных. Если в приложении будет изменен функционал управления базой данных для пользователя, тогда классы использующие свойства класса User тоже придется доработать и перекомпилировать, чтобы компенсировать новые изменения. Это как домино эффект, уроните одну кость, и она уронит все за ней следом.

Мы же просто разделим класс. Мы создадим ещё один класс, который возьмет на себя одну ответственность — управление базой данных пользователя.

class User: def __init__(self, name: str): self.name = name def get_name(self): pass class UserDB: def get_user(self, id) -> User: pass def save(self, user: User): pass

Распространённым решением этой проблемы является применение шаблона проектирования Фасад. Ознакомиться с паттерном Фасад вы можете здесь. User класс был бы фасадом для управления базой данных пользователя и управления свойствами пользователя.

Читайте также:  Нисходящее программирование плюсы и минусы

2. Open-Closed Principle
(Принцип открытости/закрытости)

Программные сущности (классы, модули, функции) должно быть открыты для расширения, но не модификации.

Давайте представим, что у вас есть магазин, и вы даете скидку в 20% для ваших любимых покупателей используя класс Discount. Если бы вы решаете удвоить 20-ти процентную скидку для VIP клиентов, вы могли бы изменить класс следующим образом:

class Discount: def __init__(self, customer, price): self.customer = customer self.price = price def give_discount(self): if self.customer == 'fav': return self.price * 0.2 if self.customer == 'vip': return self.price * 0.4

Но нет, это нарушает OCP. OCP запрещает это. Например, если мы хотим дать новую скидку для другого типа покупателей, то это требует добавления новой логики. Чтобы следовать OCP, мы добавим новый класс, который будет расширять Discount. И в этом новом классе реализуем требуемую логику:

class Discount: def __init__(self, customer, price): self.customer = customer self.price = price def get_discount(self): return self.price * 0.2 class VIPDiscount(Discount): def get_discount(self): return super().get_discount() * 2

Если вы решите дать скидку супер VIP пользователям, то это будет выглядеть так:

class SuperVIPDiscount(VIPDiscount): def get_discount(self): return super().get_discount() * 2

Расширяйте, но не модифицируйте.

3. Liskov Substitution Principle
(Принцип подстановки Лисков)

Главная идея, стоящая за Liskov Substitution Principle в том, что для любого класса клиент должен иметь возможность использовать любой подкласс базового класса, не замечая разницы между ними, и следовательно, без каких-либо изменений поведения программы при выполнении. Это означает, что клиент полностью изолирован и не подозревает об изменениях в иерархии классов.

Читайте также:  Симплекс методом решить следующие задачи линейного программирования

Более формально: Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

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

class User(): def __init__(self, color, board): create_pieces() self.color = color self.board = board def move(self, piece:Piece, position:int): piece.move(position) chessmate_check() board = ChessBoard() user_white = User("white", board) user_black = User("black", board) pieces = user_white.pieces horse = helper.getHorse(user_white, 1) user.move(horse)

LSP это основа хорошего объектно-ориентированного проектирования программного обеспечения, потому что он следует одному из базовых принципов ООП — полиморфизму. Речь о том, чтобы создавать правильные иерархии, такие, что классы, производные от базового являлись полиморфными для их родителя по отношению к методам их интерфейсов. Ещё интересно отметить, как этот принцип относится к примеру предыдущего принципа. Если мы пытаемся расширить класс новым несовместимым классом, то все сломается. Взаимодействие с клиентом будет нарушено, и как результат, такое расширение будет невозможно (или, для того чтобы сделать это возможным, нам пришлось бы нарушить другой принцип и модифицировать код клиента, который должен быть закрыт для модификации, такое крайне нежелательно и неприемлемо).

Тщательное обдумывание новых классов в соответствии с LSP помогает нам расширять иерархию классов правильно. Также, LSP способствует OCP.

4. Interface Segregation Principle
(Принцип разделения интерфейсов)

Создавайте тонкие интерфейсы, которые ориентированы на клиента. Клиенты не должны зависеть от интерфейсов, которые они не используют. Этот принцип устраняет недостатки реализации больших интерфейсов.

Чтобы полностью проиллюстрировать это, мы возьмем классический пример, потому что он очень показательный и легок для понимания. Классический пример:

class IShape: def draw(self): raise NotImplementedError class Circle(IShape): def draw(self): pass class Square(IShape): def draw(self): pass class Rectangle(IShape): def draw(self): pass

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

Читайте также:  Средства разработки язык программирования php

5. Dependecy Inversion Principle
(Принцип инверсии зависимостей)

Зависимость должна быть от абстракций, а не от конкретики. Модули верхних уровней не должны зависеть от модулей нижних уровней. Классы и верхних, и нижних уровней должны зависеть от одних и тех же абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

class AuthenticationForUser(): def __init__(self, connector:Connector): self.connection = connector.connect() def authenticate(self, credentials): pass def is_authenticated(self): pass def last_login(self): pass class AnonymousAuth(AuthenticationForUser): pass class GithubAuth(AuthenticationForUser): def last_login(self): pass class FacebookAuth(AuthenticationForUser): pass class Permissions() def __init__(self, auth: AuthenticationForUser) self.auth = auth def has_permissions(): pass class IsLoggedInPermissions (Permissions): def last_login(): return auth.last_log

Источник

Оцените статью