Принцип разделения интерфейса (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
Перевод и адаптация: Екатерина Прохорова