Статьи
August 17, 2022

Принцип разделения интерфейса (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 👈🏻

👨🏻‍💻Чат PythonTalk в Telegram💬

🍩 Поддержать канал 🫶

Источник: Python Tutorial
Перевод и адаптация: Екатерина Прохорова