python 中具有抽象方法的委托设计模式

Delegation design pattern with abstract methods in python

我有以下 classes 实现一个 "Delegation Design Pattern" 和一个额外的 DelegatorParent class:

class DelegatorParent():

    def __init__(self):
        self.a = 'whatever'    

class ConcreteDelegatee():

    def myMethod(self):
        return 'myMethod'


class Delegator(DelegatorParent):

    def __init__(self):
        self.delegatee = ConcreteDelegatee()
        DelegatorParent.__init__(self)

    def __getattr__(self, attrname):
        return getattr(self.delegatee, attrname)

a = Delegator()
result = a.myMethod()

一切正常。

现在我想在 DelegatorParent 中放置一个抽象方法,以确保始终定义 "myMethod"。

from abc import ABCMeta, abstractmethod

class DelegatorParent():
    __metaclass__ = ABCMeta

    @abstractmethod
    def myMethod(self):
        pass

    def __init__(self):
        self.a = 'whatever'


class ConcreteDelegatee():

    def myMethod(self):
        return 'myMethod'


class Delegator(DelegatorParent):

    def __init__(self):
        self.delegatee = ConcreteDelegatee()
        DelegatorParent.__init__(self)

    def __getattr__(self, attrname):
        return getattr(self.delegatee, attrname)

    # This method seems unnecessary, but if I erase it an exception is
    # raised because the abstract method's restriction is violated
    def myMethod(self): 
        return self.delegatee.myMethod()


a = Delegator()
result = a.myMethod()

你能帮我找到一个 "elegant" 从 "Delegator" 中删除 "myMethod" 的方法吗...直觉告诉我它有点多余(考虑到定义了自定义 getattr 方法).

更重要的是,请注意,对于此实现,如果我忘记在 ConcreteDelegatee 中定义 myMethod,程序会编译,但如果我调用 Delegator.myMethod(),它可能会在运行时崩溃,这正是我想要的通过在 DelegatorParent 中使用抽象方法来避免。

显然,一个简单的解决方案是将@abstractmethod 移动到 Delegator class,但我想避免这样做,因为在我的程序中 DelegatorParent 是一个非常重要的 class(而 Delegator 只是辅助 class).

既然使用了ABCMeta,就必须定义抽象方法。可以从 __abstractmethods__ 集中删除您的方法,但它是 frozenset。反正就是涉及到列出所有的抽象方法。

因此,您可以使用一个简单的描述符来代替 __getattr__

例如:

class Delegated(object):
    def __init__(self, attrname=None):
        self.attrname = attrname

    def __get__(self, instance, owner):
        if instance is None:
            return self
        delegatee = instance.delegatee
        return getattr(delegatee, self.attrname)


class Delegator(DelegatorParent):
    def __init__(self):
        self.delegatee = ConcreteDelegatee()
        DelegatorParent.__init__(self)

    myMethod = Delegated('myMethod')

这里的一个优势:开发人员拥有 "myMethod" 已委托的明确信息。

如果你尝试:

a = Delegator()
result = a.myMethod()

有效!但是如果你忘记在 Delegator class 中实现 myMethod,你就会出现 classic 错误:

Traceback (most recent call last):
  File "script.py", line 40, in <module>
    a = Delegator()
TypeError: Can't instantiate abstract class Delegator with abstract methods myMethod

编辑

这个实现可以概括如下:

class DelegatorParent():
    __metaclass__ = ABCMeta

    @abstractmethod
    def myMethod1(self):
        pass

    @abstractmethod
    def myMethod2(self):
        pass

    def __init__(self):
        self.a = 'whatever'


class ConcreteDelegatee1():
    def myMethod1(self):
        return 'myMethod1'


class ConcreteDelegatee2():
    def myMethod2(self):
        return 'myMethod2'


class DelegatedTo(object):
    def __init__(self, attrname):
        self.delegatee_name, self.attrname = attrname.split('.')

    def __get__(self, instance, owner):
        if instance is None:
            return self
        delegatee = getattr(instance, self.delegatee_name)
        return getattr(delegatee, self.attrname)


class Delegator(DelegatorParent):
    def __init__(self):
        self.delegatee1 = ConcreteDelegatee1()
        self.delegatee2 = ConcreteDelegatee2()
        DelegatorParent.__init__(self)

    myMethod1 = DelegatedTo('delegatee1.myMethod1')
    myMethod2 = DelegatedTo('delegatee2.myMethod2')


a = Delegator()
result = a.myMethod2()

在这里,我们可以指定受托人姓名和受托人方法。

这是我目前的解决方案。它解决了主要问题(防止ConcreteDelegatee忘记定义myMethod),但我仍然不相信,因为我仍然需要在Delegator中定义myMethod,这似乎是多余的

from abc import ABCMeta, abstractmethod

class DelegatorParent(object):
    __metaclass__ = ABCMeta

    def __init__(self):
        self.a = 'whatever'

    @abstractmethod
    def myMethod(self):
        pass


class Delegatee(object):
    def checkExistence(self, attrname):
        if not callable(getattr(self, attrname, None)):
            error_msg = "Can't instantiate " + str(self.__class__.__name__) + " without abstract method " + attrname
            raise NotImplementedError(error_msg)


class ConcreteDelegatee(Delegatee):    
    def myMethod(self):
        return 'myMethod'

    def myMethod2(self):
        return 'myMethod2'


class Delegator(DelegatorParent):
    def __init__(self):
        self.delegatee = ConcreteDelegatee()
        DelegatorParent.__init__(self)
        for method in DelegatorParent.__abstractmethods__:
            self.delegatee.checkExistence(method)

    def myMethod(self, *args, **kw):
        return self.delegatee.myMethod(*args, **kw)

    def __getattr__(self, attrname):
        # Called only for attributes not defined by this class (or its bases).
        # Retrieve attribute from current behavior delegate class instance.
        return getattr(self.delegatee, attrname)



# if I forget to implement myMethod inside ConcreteDelegatee, 
# the following line will correctly raise an exception saying 
# that 'myMethod' is missing inside 'ConcreteDelegatee'.
a = Delegator() 

print a.myMethod() # correctly prints 'myMethod'

print a.myMethod2() #correctly prints 'myMethod2'

您可以决定自动实现委托给 ConcreteDelegatee 的抽象方法。

对于每个抽象方法,检查它的名称是否存在于 ConcreteDelegatee class 中,并将此方法实现为此 class 方法的委托。

from abc import ABCMeta, abstractmethod

class DelegatorParent(object):
    __metaclass__ = ABCMeta

    def __init__(self):
        self.a = 'whatever'

    @abstractmethod
    def myMethod(self):
        pass


class Delegatee(object):
    pass


class ConcreteDelegatee(Delegatee):    
    def myMethod(self):
        return 'myMethod'

    def myMethod2(self):
        return 'myMethod2'


class Delegator(DelegatorParent):

    def __new__(cls, *args, **kwargs):
        implemented = set()
        for name in cls.__abstractmethods__:
            if hasattr(ConcreteDelegatee, name):
                def delegated(this, *a, **kw):
                    meth = getattr(this.delegatee, name)
                    return meth(*a, **kw)
                setattr(cls, name, delegated)
                implemented.add(name)
        cls.__abstractmethods__ = frozenset(cls.__abstractmethods__ - implemented)
        obj = super(Delegator, cls).__new__(cls, *args, **kwargs)
        obj.delegatee = ConcreteDelegatee()
        return obj

    def __getattr__(self, attrname):
        # Called only for attributes not defined by this class (or its bases).
        # Retrieve attribute from current behavior delegate class instance.
        return getattr(self.delegatee, attrname)

# All abstract methods are delegared to ConcreteDelegatee
a = Delegator() 

print(a.myMethod()) # correctly prints 'myMethod'

print(a.myMethod2()) #correctly prints 'myMethod2'

这解决了主要问题(防止ConcreteDelegatee忘记定义myMethod)。如果您忘记实现其他抽象方法,仍然会检查它们。

__new__方法负责委派,让你__init__有空去做。