Может ли принцип разделения интерфейса применяться к объектам Python?

В попытке применить принципы SOLID к проекту Python, который органически вырос и нуждается в перефакторинге, я пытаюсь понять, как принцип разделения интерфейса можно применить к языку Python, когда интерфейсы не существуют как особенность языка?

2 ответа

Решение

Интерфейс - это то, на что вы можете напечатать подсказку, буквально в исходном коде или просто неофициально в документации. Python 3 поддерживает аннотации функций, более 3,5 реальных подсказок типов, и даже если всего этого не было, вы все равно можете неофициально вводить подсказки просто в документации. Подсказка о типе просто говорит о том, что определенный параметр должен иметь определенные характеристики.

Более конкретно:

interface Foo {
    string public function bar();
}

function baz(Foo obj) { .. }

Все, что это делает, это объявляет, что любой параметр передается в baz должен быть объектом с методом bar который не принимает аргументов и возвращает строку. Даже если Python не реализовал ничего на уровне языка для обеспечения этого, вы все равно можете объявить эти вещи различными способами.

Python поддерживает две важные вещи: абстрактные классы и множественное наследование.

Вместо interface Fooв Python вы делаете это:

import abc

class Foo(abc.ABC):
    @abc.abstractmethod
    def bar() -> str:
        pass

Вместо implements Foo, ты сделаешь:

class MyClass(Foo):
    def bar() -> str:
        return 'string'

Вместо function baz(Foo obj), ты сделаешь:

def baz(obj: Foo):
    obj.bar()

Благодаря функции множественного наследования вы можете разделять свои интерфейсы / абстрактные классы так, как вам удобно.

Python основан на принципе утилитарной типизации, поэтому вместо принудительного применения всего этого через объявления и наследование интерфейса он обычно более свободно определяется в терминах "параметр должен быть итеративным" и т. Д., И вызывающий просто должен убедиться, что аргументы повторяемы. Абстрактные классы и аннотации функций в сочетании с правильными инструментами разработки могут помочь разработчикам в соответствии с такими контрактами на разных уровнях исполнения.

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

Давайте рассмотрим простой пример:

      
from abc import abstractmethod


class Machine:
    def print(self, document):
        raise NotImplementedError()

    def fax(self, document):
        raise NotImplementedError()

    def scan(self, document):
        raise NotImplementedError()


# ok if you need a multifunction device
class MultiFunctionPrinter(Machine):
    def print(self, document):
        pass

    def fax(self, document):
        pass

    def scan(self, document):
        pass


class OldFashionedPrinter(Machine):
    def print(self, document):
        # ok - print stuff
        pass

    def fax(self, document):
        pass  # do-nothing

    def scan(self, document):
        """Not supported!"""
        raise NotImplementedError('Printer cannot scan!')


class Printer:
    @abstractmethod
    def print(self, document): pass


class Scanner:
    @abstractmethod
    def scan(self, document): pass


# same for Fax, etc.

class MyPrinter(Printer):
    def print(self, document):
        print(document)


class Photocopier(Printer, Scanner):
    def print(self, document):
        print(document)

    def scan(self, document):
        pass  # something meaningful


class MultiFunctionDevice(Printer, Scanner):  # , Fax, etc
    @abstractmethod
    def print(self, document):
        pass

    @abstractmethod
    def scan(self, document):
        pass


class MultiFunctionMachine(MultiFunctionDevice):
    def __init__(self, printer, scanner):
        self.printer = printer
        self.scanner = scanner

    def print(self, document):
        self.printer.print(document)

    def scan(self, document):
        self.scanner.scan(document)


printer = OldFashionedPrinter()
printer.fax(123)  # nothing happens
printer.scan(123)  # oops!
Другие вопросы по тегам