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

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


person Simon Featherstone    schedule 09.10.2015    source источник
comment
Я не упомянул, что это был вопрос о проекте 2.7, но меня интересуют ответы для 3.x и 2.7.   -  person Simon Featherstone    schedule 09.10.2015


Ответы (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 основан на принципе утиного ввода, поэтому вместо того, чтобы применять все это с помощью объявлений интерфейса и наследования, он обычно определяется более свободно в терминах «параметр должен быть итерируемым» и т. д., и вызывающая сторона просто должна убедиться, что аргументы являются итерируемыми. Абстрактные классы и аннотации функций в сочетании с правильными инструментами разработки могут помочь разработчикам в соблюдении таких контрактов на различных уровнях правоприменения.

person deceze♦    schedule 09.10.2015
comment
Спасибо за исчерпывающий ответ. Я забыл упомянуть, что это проект версии 2.7, но вы это тоже освещаете. Я с нетерпением жду возможности изучить более продвинутые функции Python 3 в будущем. - person Simon Featherstone; 09.10.2015
comment
Из-за знаменитой функции Python утиный тип интерфейсы не используются так широко, как в других языках программирования. Если объект ходит, как утка, и плавает, как утка, то Python будет рад обращаться с ним как с уткой, даже если это лебедь! - person Milovan Tomašević; 08.03.2021

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

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


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!
person Milovan Tomašević    schedule 07.03.2021