4

描述

我想知道我展示的代码是否可以被视为 Python 中基于策略的设计的示例。另外,我想知道您是否见过使用类似此示例的 python 模块,以便我可以向它们学习?

我在一篇文章中写了更多关于这种方法的细节和例子。

最近,我需要为我正在研究的 python 模块进行基于策略的设计之类的东西。

我在这个论坛上发现了一个类似的问题,但它已关闭,我无法添加评论。

让我总结一下我对 Python 中这种方法的期望。

  • 模块类分为策略类和主机类。
  • 策略类通过继承实现对宿主类的行为或接口的修改。
  • 用户通过提供一组策略类从宿主类实例化一个新类。

这是我的玩具示例:

class PrintOutput:
    """Implement print output policy."""
    def _output(self, message):
        print(message)

class SaveOutput:
    """Implement save output policy."""
    def set_filename(self, filename):
        self.filename = filename
    def _output(self, message):
        with open(self.filename, 'w') as file:
            file.write(message)

def HelloWorld(Output=PrintOutput):
    """Creates a host class."""
    class _(Output):
        """Implements class affected by the policy."""
        def run(self):
            """Print message."""
            self._output('Hello world!')
    return _

PrintHelloWorld = HelloWorld()
hw = PrintHelloWorld()
hw.run() # print "Hello World!"

SaveHelloWorld = HelloWorld(
    Output=SaveOutput
)
hw = SaveHelloWorld()
hw.set_filename('output.txt')
hw.run() # save "Hello World!" in output.txt

在示例中,我希望所有类都已在模块中定义。用户只需要使用符合他或她要求的输出策略来实例化HelloWorld类。

设计成分

基本的设计成分是

  • 多重继承:存在于 C++ 和 Python 中。

  • 推迟继承:推迟主机类和策略类之间的继承定义,直到用户实例化主机类

// in C++ using templates
template<typename Policy1, typename Policy2, ...>
class HostClass: public Policy1, public Policy2, ... {
    // Implement host class.
};
# in Python with a factory with arguments
def HostClass(Policy1, Policy2=Default2, ...):
    class _(Policy1, Policy2, ...):
        """Implements class affected by the policies."""
    return _
  • 类实例化
// in C++
typedef HostClass<Policy1Class, Policy2Class, ...> NewClass;
# in Python
NewClass = HostClass(Policy1Class, Policy2Class, ...)

使用 mixins 作为替代

我刚刚从其中一条评论中了解到,可以使用Python mixins作为创建新类的替代方法。在这种方法下,模块的代码分为基础类和混合类。然后,您可以从基类创建新类,并使用 mixin 类来实现对基类提供的行为或接口的修改。

按照这个答案,我能够使用 mixins 编写我的第一个示例。

class PrintOutput:
    """Implement print output mixin."""
    def _output(self, message):
        print(message)

class SaveOutput:
    """Implement save output mixin."""
    def set_filename(self, filename):
        self.filename = filename
    def _output(self, message):
        with open(self.filename, 'w') as file:
            file.write(message)

class HelloWorld:
    """Creates a host class."""
    def run(self):
        """Print message."""
        self._output('Hello world!')

class PrintHelloWorld(PrintOutput, HelloWorld):
    pass

hw = PrintHelloWorld()
hw.run() # print "Hello World!"

class SaveHelloWorld(SaveOutput, HelloWorld):
    pass

hw = SaveHelloWorld()
hw.set_filename('output.txt')
hw.run() # save "Hello World!" in output.txt

方法之间的区别

mixin 和我之前的示例之间的主要区别在于类之间的继承层次结构。mixin 和 based classes 都不能推断出哪个是 mixin 或 based class 的角色。这是因为它们都是PrintHelloWorld或SaveHelloWorld等新类的父

在我尝试进行基于策略的设计时,宿主类总是可以知道哪些类是他们的策略,因为它们是它的父类。此功能允许我利用 Python 的方法解析顺序 (MRO) 在主机类和策略类之间创建组合。这些组合是使用实例化的主机类作为策略来实例化另一个主机类的结果,请参见下面的示例。

class InputMessage:
    """Generate the message."""
    def run(self):
        return 'hello world'

def AddPrefix(Input):
    """Add a prefix."""
    class _(Input):
        def set_prefix(self, prefix):
            self._prefix = prefix
        def run(self):
            return self._prefix + super().run()
    return _

def AddSuffix(Input):
    """Add a suffix."""
    class _(Input):
        def set_suffix(self, suffix):
            self._suffix = suffix
        def run(self):
            return super().run() + self._suffix
    return _

def PrintOutput(Input):
    """Print message."""
    class _(Input):
        def run(self):
            print(super().run())
    return _

PrintPrefixSuffixMessage = PrintOutput(
    AddSuffix(AddPrefix(InputMessage))
)
message = PrintPrefixSuffixMessage()
message.set_prefix('Victor says: ')
message.set_suffix(' and goodbye!')
message.run()

我不确定这种差异在这两种方法之间是否有任何实际意义。目前我只是想了解可以用它们表达什么。

走下递归的兔子洞

我想指出,可以在混合中添加递归。这让人想起 C++ 中使用的一些元编程技术,这些技术可以在编译时进行计算。这原则上在作为解释语言的 Python 中没有应用。请耐心等待下面的下一个不切实际的例子。

class Identity:
    def run(self, z):
        return z

def MandelbrotSeq(Base, n):
    def Mandelbrot(Base, n):
        if n == 0:
            return Base
        class _(Base):
            def run(self, z):
                z = super().run(z)
                return z**2 + self.c
        return Mandelbrot(_, n-1)
    return Mandelbrot(Base, n).__mro__

M = MandelbrotSeq(Identity, 5)

m = M[0]()
m.c = 1
# print 677 the value at 5th iteration for c=1
print(m.run(0))

m = M[1]()
m.c = 1
# print 26 the value at 4th iteration for c=1
print(m.run(0))

m = M[2]()
m.c = 1
# print 5 the value at 3th iteration for c=1
print(m.run(0))

MandelbrotSeq工厂将Mandelbrot序列递归地映射为层次连接类的列表。M的第一个元素指的是Identity类的第五代后代。这意味着调用M [0] 实例的run(.)成员函数将返回序列的第 5 个值。同样,我调用M [1] 实例的run(.) 成员函数将返回序列的第 4 个值。

这只是如何利用 Python MRO 的一个极端示例,如前面部分所示。这只是一个玩具示例,仅仅因为涉及递归,我无法想出比分形之类的更好的主意。请不要在家里这样做!

4

1 回答 1

0

答案是我根据我的研究和 StackOverflow 的一些评论学到的东西。

我写了一篇关于这些方法的详细博客,称为Mixin 和 python 中基于策略的设计。在那篇文章中,我还讨论了几个扩展,比如使用装饰器的实现、使用 mixins 添加默认类等等......

设计要求和实现

要求

设计一个 Python 模块,这样用户可以通过从一组预定义的类继承来创建具有新行为和接口的类。这种方法基于所有可能的方式为用户提供了大量可用的类,它们可以组合成新的类。

可以使用 mixin 和基于策略的实现来实现此要求。

混合实现

Mixin 实现定义如下:

  • 模块类分为 mixin 和基类。
  • Mixin 类实现对基类的行为或接口的修改。
  • 用户通过继承将一个或多个 mixin 与基类组合来创建一个新类。
class Mixin1Class:
    """Implementation of some Mixin1 class."""
class Mixin2Class:
    """Implementation of some Mixin2 class."""
...
class BasedClass:
    """Implementation of based class."""

# End user creating new class
class NewClass(Mixin1Class, [Mixin2Class, ...], BasedClass):
    pass

参考:

基于策略的实施

基于策略的实现定义如下:

  • 模块类分为策略类和主机类。
  • 策略类实现对主机行为或接口的修改。
  • 宿主类是用类工厂定义的,即返回类型对象的函数。
  • 用户使用类工厂调用新类的创建。
  • 策略类作为参数传递给类工厂。
  • 使用类工厂,可以创建宿主类,并将所有策略类作为其父类。
class Policy1Class:
    """Implementation of some Policy1 class."""
class Policy2Class:
    """Implementation of some Policy2 class."""
...
def HostClassFactory(Policy1, Policy2=Policy2Class, ...):
    """Create a HostClass and return it."""
    class HostClass(Policy1, Policy2, ...):
        """Concrete implementation of the host class."""
    return HostClass

# End user invoking new class
NewClass = HostClassFactory(Policy1Class, [Policy2Class,...])

参考:

实现之间的比较

这里列出了方法之间的差异:

  • 类之间的关系不一样会影响 Python 的 Method Resolution Order 或 MRO,见下图。
  • mixin 的基类在用户创建新类时被定义或实例化。
  • 但是,基于策略的主机类定义会延迟到用户调用工厂函数。
  • 可以提供默认策略类,但不能有 mixin 默认类。
于 2019-10-22T12:01:34.193 回答