Принцип разделения интерфейса (ISP)
Продолжаем прививать вам хорошие манеры и учить жизни по всем канонам ООП.
S — принцип единой ответственности (Single responsibility Principle)
O — принцип открытости / закрытости (Open-closed Principle)
L — принцип подстановки Барбары Лисков (Liskov Substitution Principle)
I — принцип разделения интерфейса (Interface Segregation Principle)
D — принцип инверсии зависимостей (Dependency Inversion Principle)
Поговорим о четвёртом принципе — принципе разделения интерфейса.
Интерфейс — это условный перечень описаний поведения некоего объекта. Например, вы нажимаете кнопку на пульте телевизора, «ящик» включается, и вам особо не важно, как.
В объектно-ориентированном программировании интерфейс — это набор методов, которыми должен обладать объект. Цель интерфейсов — позволить пользователям запрашивать через него нужные методы объекта.
В качестве интерфейсов в Python используются абстрактные классы благодаря так называемому «принципу утиной типизации». Он гласит:
«Если оно ходит как утка и крякает как утка, то оно должно быть уткой»
Другими словами, методы класса определяют, какими будут его объекты, а не тип класса.
Принцип разделения интерфейсов гласит: интерфейс должен быть как можно более лаконичным с точки зрения связности. Другими словами, он должен выполнять всего ОДНУ какую-то задачу.
Это не означает, что должен быть только один метод. Интерфейс может иметь несколько связных методов.
Например, у интерфейса класса Database могут быть методы connect() и disconnect(), потому что они должны работать вместе. С одним методом конкретно этот класс будет неполным.
Дядя Боб, который является родоначальником термина SOLID, объясняет принцип разделения интерфейсов так:
«Создавайте мелкодисперсные интерфейсы, специфичные для клиента. Клиенты не должны быть вынуждены использовать то, что им не нужно»
Как НЕ НАДО делать:
Определяем абстрактный класс Vehicle с двумя абстрактными методами go() и fly():
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def go(self):
pass
@abstractmethod
def fly(self):
passОпределяем класс Aircraft, который наследуется от класса Vehicle и реализует методы go() и fly():
class Aircraft(Vehicle):
def go(self):
print("Taxiing")
def fly(self):
print("Flying")Определяем класс Car, который наследуется от класса Vehicle. Поскольку автомобиль не может летать (если, конечно, это не старенький Форд Англия ;)), мы вызываем исключение в методе fly():
class Car(Vehicle):
def go(self):
print("Going")
def fly(self):
raise Exception("The car cannot fly")В этой конструкции класс Car вынужден реализовать метод fly() из класса Vehicle, но не использует. Такая конструкция нарушает принцип разделения интерфейсов.
Как НАДО делать:
Разобьём класс Vehicle: создадим абстрактный класс Movable и наследуемый от него Flyable.
class Movable(ABC):
@abstractmethod
def go(self):
pass
class Flyable(Movable):
@abstractmethod
def fly(self):
passТогда Aircraft станет классом-потомком Flyable:
class Aircraft(Flyable):
def go(self):
print("Taxiing")
def fly(self):
print("Flying")А Car — классом-потомком Movable:
class Car(Movable):
def go(self):
print("Going")При такой композиции кода классу Car нужно реализовать только метод go() и не нужно реализовывать метод fly(), который он не использует.
Заключение
Создавайте мелкодисперсные интерфейсы, специфичные для клиента. Клиентов не следует заставлять реализовывать интерфейсы, которые они не используют.
👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻
Источник: Python Tutorial
Перевод и адаптация: Екатерина Прохорова