В попытке применить принципы SOLID к проекту Python, который вырос органично и нуждается в рефакторинге, я пытаюсь понять, как работает разделение интерфейса Принцип можно применить к языку 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!