#python #design-patterns #strategy-pattern
Вопрос:
Мне нужно улучшить поведение класса с помощью внешних методов, поэтому я использую шаблон стратегии.
Сначала я определяю интерфейс для подписи методов:
class ILabel(ABC):
@abstractmethod
def get_label(self, obj):
pass
и реализация этого интерфейса:
class Label(ILabel):
def __init__(self, prefix):
self.prefix = prefix
def get_label(self, merchandise, obj):
return self.prefix str(obj) merchandise.name
И класс, который я хотел бы дополнить своим алгоритмом:
class Merchandise:
def __init__(self):
self.name = "__a_name"
def get_label(self, obj):
return str(obj) self.name
def display(self, obj, get_label=None):
if get_label:
self.get_label = types.MethodType(get_label, self)
print(self.get_label(obj))
И, наконец, звонивший:
# default behavior
x = Merchandise().display("an_obj")
# augmented behavior
label = Label("a_prefix__")
y = Merchandise().display("an_obj", label)
print(f"Default output: {x}")
print(f"Augmented output: {y}")
результат должен быть:
Default output: an_obj__a_name
Augmented output: a_prefix__an_obj__a_name
Два вопроса:
- Учитывая, что вместо «сиротского» метода (за неимением лучшего слова) я отправляю метод внутри класса со ссылкой на себя; это все еще считается шаблоном стратегии или другой шаблон ближе к этому дизайну?
- Поскольку я передаю ссылку на
Merchandise
при регистрации метода (т. Е.types.MethodType(get_label, self)
),get_label
метод вLabel
классе имеет ссылку на экземплярMerchandise
. т. е.:def get_label(self, merchandise, obj):
Вопрос в том, существует ли какое-либо лучшее соглашение об именах для
merchandise
справки?
Обновить
В попытке предоставить минимально работающий пример используется приличное количество контекста, что может привести к мысли get_label
, что метод может быть без состояния (т. Е. Без ссылки на экземпляр Merchandise
). Он Label.get_label
обновлен, чтобы прояснить этот момент.
Комментарии:
1. Шаблоны проектирования не обязательно универсальны. Они часто являются обходными путями для функций, не предусмотренных целевым языком, таких как функции более высокого порядка.
Ответ №1:
Учитывая, что вместо «сиротского» метода (за неимением лучшего слова) я отправляю метод внутри класса со ссылкой на себя; это все еще считается шаблоном стратегии или другой шаблон ближе к этому дизайну?
Я бы все равно назвал это Стратегией, но продолжайте читать.
Поскольку я передаю ссылку на Товар при регистрации метода (т. Е. типов.MethodType(get_label, self)), правильное определение get_label в классе Label таково:
Из-за этой путаницы вам следует использовать другой подход.
Ваша логика реализации поведения стратегии по умолчанию обратная. Нет никаких причин, по которым «Стратегия получения метки для obj
» должна быть методом Merchandise
класса, за исключением того, что у вас там хранится реализация по умолчанию. Даже это не обязательно должен быть обычный метод, так как он ничего не делает self
.
Это означает , что код слишком сложен (потому что вы напрасно используете types.MethodType
механизм и динамически исправляете класс), а также имеет неожиданное поведение с сохранением состояния: когда вы вызываете display
с ненулевым None
значением для get_label
, эта стратегия повлияет на будущие вызовы туда display
, куда None
передается.
Если вам не нужно поведение с сохранением состояния, то вам нужна логика установки по умолчанию наоборот-установите локальный, а не изменяйте класс:
class Merchandise:
@staticmethod
def get_label(obj):
return str(obj)
def display(self, obj, get_label=None):
if get_label is None:
get_label = Merchandise.get_label
print(get_label(obj))
Хотя на самом деле нам здесь не нужен шаблон «заменить значением None
по умолчанию», так как мы не собираемся изменять параметр:
class Merchandise:
@staticmethod
def get_label(obj):
return str(obj)
def display(self, obj, get_label=Merchandise.get_label):
print(get_label(obj))
И этот игрушечный пример может быть еще проще:
class Merchandise:
def display(self, obj, get_label=str):
print(get_label(obj))
# although *this* doesn't rely on `self`, either....
Если вам действительно нужно поведение с отслеживанием состояния, то вам следует задать состояние либо при инициализации, либо явно позже, либо и то и другое:
class Merchandise:
def __init__(self, get_label=str):
self.get_label = get_label
@property
def get_label(self): return self._get_label
@get_label.setter
def get_label(self, value):
# may as well do a little verification
if not callable(value):
raise TypeError("get_label strategy must be callable")
self._get_label = value
def display(self, obj):
print(self.get_label(obj))
Обратите внимание, что здесь это self.get_label(obj)
не вызов метода; Python найдет get_label
как атрибут экземпляра, прежде чем попытается найти его в классе; найдя вызываемый объект, он затем вызовет этот объект.
Комментарии:
1. Вы можете обнаружить, что последний код очень похож на реализацию состояния в учебнике. На самом деле нет никакого различия в языках, где функции являются объектами первого класса. Кроме того, на самом деле в шаблоне на таких языках совсем немного — все, что мы действительно делаем,-это используем функции более высокого порядка, сохраняя одну из них и используя ее позже. Эквивалентно, это просто еще один пример использования композиции и делегирования, а не наследования.
2.
Merchandise
сам по себе выглядит как контейнер в стиле Java для чего-то, что в противном случае могло бы быть обычной функцией.3.Обратите внимание, что если реализация
get_label
in по умолчаниюMerchandise
использовала другиеself
атрибуты, вы все равно можете использовать ее в любом из этих вариантов — просто назначьте связанный метод (self.get_label
)get_label
в первом подходе или разрешите свойству затенять обычный метод во втором.4. @chepner Я согласен, но я предполагаю, что усилия по созданию минимального воспроизводимого примера исключают контекст, оправдывающий применение шаблона.
5. Я хотел бы повторить, что создание минимального воспроизводимого примера создает массу контекста. В моем приложении, в частности,
Label
Merchandise
в методе используются другие свойства классовget_label
и. Поэтому наличие состояния в обоих случаях имеет важное значение, поэтому я был бы наиболее признателен за проекты, которые сохраняли бы оба состояния.