должна ли эта реализация шаблона стратегии на python использовать закрытую переменную в методе инициализации?

#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 таким образом — это технически правильно (и на самом деле может быть предпочтительнее, если вы хотите, чтобы кто-то мог переопределить установщик и заставить конструктор использовать это переопределение). Но это не основной случай, поэтому немного странно, что они представляют его таким образом.