Может ли принцип разделения интерфейса применяться к объектам 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!