Spa*_*ace 7 python abstract-class strategy-pattern keyword-argument python-3.x
我正在尝试实现 Strategy 设计模式,以便为以模块化方式实现的底层算法创建一个接口。
目前,根据下面的代码,我有一个顶级/父抽象类 ( ParentAbstractStrategy),它定义了该strategy方法的基本接口。
我也有一个从这个抽象类 ( ChildAbstractStrategy)向下一级。
我有两个抽象类的原因是因为它们需要保存的属性;见__init__方法。
ChildAbstractStrategy是一种特殊情况,ParentAbstractStrategy因为它存储了一个附加属性:attr2。否则它的接口是相同的,如相同的strategy方法签名所见。
有时,我希望能够直接子类化ParentAbstractStrategy并实现strategy方法(请参阅ConcreteStrategyA),但有时我希望能够子类化ChildAbstractStrategy,因为需要额外的属性(请参阅ConcreteStrategyB)。
另一个复杂问题是,在任一抽象类的某些子类中,我希望能够处理strategy方法中的其他参数。这就是为什么我添加**kwargs到该strategy方法的所有签名中的原因,以便我可以根据具体情况将我想要的任何其他参数传递给子类。
这就产生了最后一个问题:这些额外的参数在子类中不是可选的。例如,在strategy方法ConcreteStrategyB我想肯定的是,来电者在第三个参数传递。我基本上是在滥用**kwargs提供可能应该是位置参数的内容(因为我不能给它们合理的默认值并且需要强制执行它们的存在)。
这种**kwargs在子类中用于“方法重载”的当前解决方案感觉非常混乱,我不确定这是否意味着类继承方案或接口设计存在问题,或两者兼而有之。
有没有办法以更简洁的方式实现这些设计目标。感觉就像我在这里错过了一些大图,也许类/界面设计很糟糕。也许为该strategy方法创建两个具有不同签名的不相交抽象类?
import abc
class ParentAbstractStrategy(metaclass=abc.ABCMeta):
@abc.abstractmethod
def __init__(self, attr1):
self.attr1 = attr1
@abc.abstractmethod
def strategy(self, arg1, arg2, **kwargs):
raise NotImplementedError
class ChildAbstractStrategy(ParentAbstractStrategy, metaclass=abc.ABCMeta):
@abc.abstractmethod
def __init__(self, attr1, attr2):
super().__init__(attr1)
self.attr2 = attr2
@abc.abstractmethod
def strategy(self, arg1, arg2, **kwargs):
raise NotImplementedError
class ConcreteStrategyA(ParentAbstractStrategy):
def __init__(self, attr1):
super().__init__(attr1)
def strategy(self, arg1, arg2, **kwargs):
print(arg1, arg2)
class ConcreteStrategyB(ChildAbstractStrategy):
def __init__(self, attr1, attr2):
super().__init__(attr1, attr2)
def strategy(self, arg1, arg2, **kwargs):
print(arg1, arg2)
arg3 = kwargs.get("arg3", None)
if arg3 is None:
raise ValueError("Missing arg3")
else:
print(arg3)
Run Code Online (Sandbox Code Playgroud)
这是一个解释器会话,演示了它当前的工作方式:
>>> a = ConcreteStrategyA(1)
>>> a.attr1
1
>>> a.strategy("a", "b")
a b
>>> b = ConcreteStrategyB(1, 2)
>>> b.attr1
1
>>> b.attr2
2
>>> b.strategy("a", "b")
a b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/space/strategy.py", line 42, in strategy
raise ValueError("Missing arg3")
ValueError: Missing arg3
>>> b.strategy("a", "b", arg3="c")
a b
c
Run Code Online (Sandbox Code Playgroud)
回答我自己的问题。
在这种情况下我的使用**kwargs是“坏”的。为什么?据我所知,**kwargs通常用于:
**kwargs参数是可以传递到函数中的可选参数,并且它们具有合理的默认值。在函数调用中要求**kwargs它违背了它们的目的;应使用需要显式提供的位置参数。这样,调用者必须明确满足函数提供的接口。
**kwargs正如我所遇到的那样,在界面中使用还有另一个问题。它涉及LSP(里氏替换原则,参见https://en.wikipedia.org/wiki/Liskov_substitution_principle)。当前的实现正在滥用**kwargs试图为strategy子类之间的方法定义变量接口。尽管在语法上所有方法的函数签名都strategy匹配,但在语义上接口是不同的。这违反了 LSP,这要求我ParentAbstractStrategy在考虑它们的接口时可以处理相同的任何后代,例如我应该能够处理相同strategy的方法。ConcreteStrategyAConcreteStrategyB
我的解决方案是什么?我已更改该方法的接口,strategy不再包含**kwargs而是使用位置参数和带有默认值的关键字参数的混合。例如,如果ConcreteStrategyB仍然需要第三个参数arg3但ConcreteStrategyA不需要,我可以将类更改为如下所示:
class ConcreteStrategyA(ParentAbstractStrategy):
def __init__(self, attr1):
super().__init__(attr1)
def strategy(self, arg1, arg2, arg3=None):
print(arg1, arg2)
class ConcreteStrategyB(ChildAbstractStrategy):
def __init__(self, attr1, attr2):
super().__init__(attr1, attr2)
def strategy(self, arg1, arg2, arg3=None):
print(arg1, arg2)
assert arg3 is not None
print(arg3)
Run Code Online (Sandbox Code Playgroud)
两个父类的接口都更改为匹配。
| 归档时间: |
|
| 查看次数: |
1805 次 |
| 最近记录: |