我正在尝试实现策略设计模式,以创建一个接口,以模块化的方式实现底层算法。在
目前,根据下面的代码,我有一个顶层/父抽象类(ParentAbstractStrategy
),它定义了strategy
方法的基本接口。在
我还从这个抽象类(ChildAbstractStrategy
)向下一级。在
我之所以有两个抽象类是因为它们需要保存属性;请参见__init__
方法。
ChildAbstractStrategy
是ParentAbstractStrategy
的一个特例,因为它存储了一个附加属性:attr2
。
否则它的接口是相同的,如相同的strategy
方法签名所示。在
有时,我希望能够直接将ParentAbstractStrategy
子类化并实现strategy
方法(请参见ConcreteStrategyA
),但有时我希望能够将ChildAbstractStrategy
子类化,因为需要额外的属性(请参见ConcreteStrategyB
)。在
另一个复杂的问题是,在抽象类的某些子类中,我希望能够处理strategy
方法中的附加参数。这就是为什么我将**kwargs
添加到strategy
方法的所有签名中,这样我就可以根据具体情况将我想要的任何附加参数传递给子类。在
这就产生了最后一个问题:这些额外的参数在子类中不是可选的。E、 在strategy
的strategy
方法中,我想确定调用者传递了第三个参数。
我基本上是滥用**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)
下面是一个解释会话,演示它当前的工作方式:
^{pr2}$
回答我自己的问题。在
在这个场景中,我对
**kwargs
的使用是“糟糕的”。为什么? 据我所知,**kwargs
通常用于:**kwargs
是可选参数,可以传递到函数和中,它们具有正常的默认值。在在函数调用中将
**kwargs
设为必需的,这将破坏它们的用途;应该改用需要显式提供的位置参数。这样,函数提供的接口就可以由调用者显式地满足。在与我一样,在接口中使用}的{}方法进行相同的处理。在
**kwargs
还有另一个问题。它涉及到LSP(Liskov代换原理,见https://en.wikipedia.org/wiki/Liskov_substitution_principle)。当前的实现正在滥用**kwargs
,试图在子cases之间为strategy
方法定义一个变量接口。尽管所有strategy
方法的函数签名在语法上是匹配的,但在语义上接口是不同的。这违反了LSP,它要求我在考虑ParentAbstractStrategy
的任何子代时,对它们的接口都是相同的,例如,我应该能够对ConcreteStrategyA
和{我的解决方案是什么? 我已经将
strategy
方法的接口更改为不再包含**kwargs
,而是将位置参数和关键字参数与默认值混合使用。 E、 g.如果ConcreteStrategyB
仍然需要第三个参数arg3
,但是ConcreteStrategyA
不需要,我可以将这些类改成这样:两个父类的接口都更改为匹配。在
相关问题 更多 >
编程相关推荐