[英]Checking of **kwargs in concrete implementation of abstract class method. Interface issue?
我正在嘗試實現策略設計模式,以便為要以模塊化方式實現的基礎算法創建接口。
目前,按照下面的代碼,我有一個頂級/父抽象類( ParentAbstractStrategy
),它定義了strategy
方法的基本接口。
我還從這個抽象類( ChildAbstractStrategy
)上進行了下一級的ChildAbstractStrategy
。
我有兩個抽象類的原因是因為它們需要擁有屬性。 請參見__init__
方法。 ChildAbstractStrategy
是一個特例ParentAbstractStrategy
,它存儲的附加屬性: attr2
。 否則,其接口是相同的,如相同的strategy
方法簽名所示。
有時,我希望能夠直接ParentAbstractStrategy
並實現strategy
方法(請參見ConcreteStrategyA
),但是有時我希望能夠ChildAbstractStrategy
,因為需要額外的屬性(請參見ConcreteStrategyB
)。
另一個復雜之處是,在任何一個抽象類的某些子類中,我都希望能夠處理strategy
方法中的其他參數。 這就是為什么我在strategy
方法的所有簽名中添加**kwargs
原因,以便我可以根據需要將想要添加的任何其他參數傳遞給子類。
這就產生了最后一個問題:這些額外的參數在子類中不是可選的。 例如,在ConcreteStrategyB
的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)
這是一個解釋器會話,演示其當前的工作方式:
>>> 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
回答我自己的問題。
在這種情況下,我對**kwargs
是“不好的”。 為什么? 據我所知, **kwargs
通常用於:
**kwargs
是可以傳遞給函數的可選參數,他們有理智的默認值。 在函數調用中需要 **kwargs
了它們的目的; 應該使用需要顯式提供的位置參數。 該接口提供的功能這種方式必須由主叫方明確滿足。
與我一樣,在接口中使用**kwargs
還有另一個問題。 它涉及LSP(Liskov替代原理,請參閱https://en.wikipedia.org/wiki/Liskov_substitution_principle )。 當前的實現正在濫用**kwargs
,以試圖為子場景之間的strategy
方法定義變量接口。 盡管在語法上所有strategy
方法的功能簽名都匹配,但是在語義上接口是不同的。 這違反了LSP,要求我在考慮ParentAbstractStrategy
的接口時例如可以將其同等對待,例如,我應該能夠將ConcreteStrategyA
和ConcreteStrategyB
的strategy
方法相同。
我該怎么辦? 我已將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)
兩個父類的接口都更改為匹配。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.