#python #decorator #getter #strategy-pattern
#python #декоратор #геттер #стратегия-шаблон
Вопрос:
Я изучаю шаблон разработки стратегии, а также декоратор свойств в Python. Я наткнулся на этот пример:
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
class Context():
"""
The Context defines the interface of interest to clients.
"""
def __init__(self, strategy: Strategy) -> None:
"""
Usually, the Context accepts a strategy through the constructor, but
also provides a setter to change it at runtime.
"""
self._strategy = strategy
@property
def strategy(self) -> Strategy:
"""
The Context maintains a reference to one of the Strategy objects. The
Context does not know the concrete class of a strategy. It should work
with all strategies via the Strategy interface.
"""
return self._strategy
@strategy.setter
def strategy(self, strategy: Strategy) -> None:
"""
Usually, the Context allows replacing a Strategy object at runtime.
"""
self._strategy = strategy
def do_some_business_logic(self) -> None:
"""
The Context delegates some work to the Strategy object instead of
implementing multiple versions of the algorithm on its own.
"""
# ...
print("Context: Sorting data using the strategy (not sure how it'll do it)")
result = self._strategy.do_algorithm(["a", "b", "c", "d", "e"])
print(",".join(result))
# ...
class Strategy(ABC):
"""
The Strategy interface declares operations common to all supported versions
of some algorithm.
The Context uses this interface to call the algorithm defined by Concrete
Strategies.
"""
@abstractmethod
def do_algorithm(self, data: List):
pass
"""
Concrete Strategies implement the algorithm while following the base Strategy
interface. The interface makes them interchangeable in the Context.
"""
class ConcreteStrategyA(Strategy):
def do_algorithm(self, data: List) -> List:
return sorted(data)
class ConcreteStrategyB(Strategy):
def do_algorithm(self, data: List) -> List:
return reversed(sorted(data))
if __name__ == "__main__":
# The client code picks a concrete strategy and passes it to the context.
# The client should be aware of the differences between strategies in order
# to make the right choice.
context = Context(ConcreteStrategyA())
print("Client: Strategy is set to normal sorting.")
context.do_some_business_logic()
print()
print("Client: Strategy is set to reverse sorting.")
context.strategy = ConcreteStrategyB()
context.do_some_business_logic()
Насколько я понимаю, метод / декоратор свойств @property
предоставляет интерфейс для настройки свойства (температуры) в этом случае. Присвоение имени свойству _strategy
в __init__
методе подразумевает, что оно должно быть закрытой переменной. Это неправильно или избыточно? Я бы подумал, что эта переменная должна быть названа strategy
, но ее интерфейс должен быть реализован с использованием закрытых переменных (т. Е. В getter / setter)
взято из https://refactoring.guru/design-patterns/strategy/python/example
Редактировать:
Чтобы прояснить мои рассуждения: разве нельзя изменить стратегию во время выполнения следующим образом:
a = Context()
a.strategy = somestrategy
Комментарии:
1. В комментарии конкретно указано иное
2. @MadPhysicist Я понимаю, что это можно изменить во время выполнения
_strategy=...
. Мой вопрос в том, не должно ли изменение среды выполнения предполагать, что пользователь знает, что оно реализовано с помощью закрытой переменной? Что, если устаревший код написан для использованияstrategy=
?
Ответ №1:
Свойство не названо _strategy
, оно названо strategy
, но внутренняя переменная, которая содержит его значение, названа _strategy
.
Это:
def __init__(self, strategy: Strategy) -> None:
self._strategy = strategy
Позволяет:
c = Context(some_strategy)
Но при попытке присвоить ей имя _strategy
вызывает предупреждения c._strategy
.
И это:
@property
def strategy(self) -> Strategy:
return self._strategy
Затем позволяет:
my_strategy = c.strategy
Средство получения возвращает значение self._strategy
при обращении к свойству.
И, наконец, это:
@strategy.setter
def strategy(self, strategy: Strategy) -> None:
self._strategy = strategy
Позволяет:
c.strategy = another_strategy
Создание strategy
не просто свойства только для чтения, но и свойства чтения / записи.
Примечание: приведенный ниже код не является неправильным, но он делает что-то еще:
class Complex:
def __init__(self, strategy: int) -> None:
self.strategy = strategy
@property
def strategy(self) -> int:
return self._strategy
@strategy.setter
def strategy(self, strategy: int) -> None:
self._strategy = strategy
c = Complex(1)
print(c.strategy)
Разница в том, что теперь конструктор ( __init__
) не устанавливает скрытый атрибут напрямую, а сам вызывает установщик для свойства.
Если кто-то сейчас переопределяет класс, он все равно использует это, сравните:
class MyClass:
def __init__(self, a, b: int) -> None:
self.a = a
self._b = b
@property
def a(self) -> int:
return self._a
@a.setter
def a(self, a: int) -> None:
self._a = a
@property
def b(self) -> int:
return self._b
@b.setter
def b(self, b: int) -> None:
self._b = b
class MySubClass(MyClass):
@MyClass.a.setter
def a(self, a: int) -> None:
self._a = a 10
@MyClass.b.setter
def b(self, b: int) -> None:
self._b = b 10
c = MyClass(1, 2)
print(c.a)
print(c.b)
s = MySubClass(1, 2)
print(s.a)
print(s.b)
Результат:
1
2
11
2
Итак, это зависит от того, хотите ли вы, чтобы кто-то, наследующий ваш класс, мог изменить это поведение.
Комментарии:
1. Я вижу, это ясно. Будет ли код вести себя так же, если
__init__
метод объявит стратегию какself.strategy = ...
? (вместо использования подчеркивания)2. Нет, потому что тогда возникнет конфликт имен между свойством
strategy
и атрибутомproperty
. Вы могли бы просто определитьself.strategy = ...
и вообще не беспокоиться о свойстве — но тогда вы не получите ни одного из конкретных преимуществ, которые делают свойства хорошим выбором в некоторых ситуациях. Скрытие способа сохранения значения может быть очень полезным — если реализация будет изменена позже, средства получения и установки свойств могут использоваться для создания правильногоStrategy
объекта из нового содержимого.3.Эта статья, похоже, ввела меня в заблуждение programiz.com/python-programming/property
class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) 32 @property def temperature(self): print("Getting value...") return self._temperature @temperature.setter def temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273 is not possible") self._temperature = value
4. На самом деле этот фрагмент кода вызывает свое собственное свойство изнутри конструктора и устанавливает скрытый атрибут
_strategy
таким образом — это технически правильно (и на самом деле может быть предпочтительнее, если вы хотите, чтобы кто-то мог переопределить установщик и заставить конструктор использовать это переопределение). Но это не основной случай, поэтому немного странно, что они представляют его таким образом.