python中的策略模式

时间:2019-06-08 13:44:34

标签: python strategy-pattern

我来自C#背景,为了实现策略模式,我们将始终使用接口,例如:ILoggger。现在,据我了解,在Python等鸭类语言中,我们可以避免这种基类/合同。

我的问题是,这是通过利用鸭子输入来实现策略模式的最佳方法吗?并且,这种鸭子式的输入方式是否使我的代码的下一个用户清楚这是“扩展点”?另外,我认为最好使用类型提示来帮助下一个查看您的代码的人以了解策略的类型,但是在没有基类/合同的情况下使用鸭子类型时,您使用哪种类型?已经具体的课程之一?

以下是一些代码:

class FileSystemLogger():
    def log(self, msg):
        pass

class ElasticSearchLogger():
    def log(self, msg):
        pass

# if i wanted to use type hints, which type should logger be here?
class ComponentThatNeedsLogger():
    def __init__(self, logger):
        self._logger = logger

# should it be this?
class ComponentThatNeedsLogger():
    def __init__(self, logger : FileSystemLogger):
        self._logger = logger

有人可以建议解决此问题的最标准/ Pythonic /可读方式是什么?

我不是在寻找“两行代码中的答案”。

3 个答案:

答案 0 :(得分:2)

如果您真的想一直学习 classes 并强制您使用基类,请创建一个ABC: abstract base class / method及其一些实现:

归因:使用Alex Vasses answer here进行查找

from abc import ABC, abstractmethod

class BaseLogger(ABC):
    """ Base class specifying one abstractmethod log - tbd by subclasses."""
    @abstractmethod
    def log(self, message):
        pass

class ConsoleLogger(BaseLogger):
    """ Console logger implementation."""
    def log(self, message):
        print(message)

class FileLogger(BaseLogger):
    """ Appending FileLogger (date based file names) implementation."""
    def __init__(self):
        import datetime 
        self.fn = datetime.datetime.now().strftime("%Y_%m_%d.log")

    def log(self,message):
        with open(self.fn,"a") as f:
            f.write(f"file: {message}\n")

class NotALogger():
    """ Not a logger implementation."""
    pass

然后使用它们:

# import typing # for other type things

class DoIt:
    def __init__(self, logger: BaseLogger):
        # enforce usage of BaseLogger implementation
        if isinstance(logger, BaseLogger):
            self.logger = logger
        else:
            raise ValueError("logger needs to inherit from " + BaseLogger.__name__)

    def log(self, message):
        # use the assigned logger
        self.logger.log(message)

# provide different logger classes
d1 = DoIt(ConsoleLogger())
d2 = DoIt(FileLogger())

for k in range(5):
    d1.log(str(k))
    d2.log(str(k))

with open(d2.logger.fn) as f:
    print(f.read())

try:
    d3 = DoIt( NotALogger())
except Exception as e:
    print(e)

输出:

0
1
2
3
4 
file: 0
file: 1
file: 2
file: 3
file: 4

logger needs to inherit from BaseLogger

作为旁注:python已经具有相当复杂的日志记录功能。如果这只是您查询的唯一目的,请查看Logging

答案 1 :(得分:1)

在Python中,由于运行时的安全性和优雅的终止,通常不需要具有编译时类型强制的全面策略模式。

如果您希望自定义现有代码的某些部分,通常的做法是:

  • 替换适当的方法-是否通过子类化,包括带有混合或仅赋值(方法是活动对象的属性,就像其他任何属性一样,可以按相同的方式重新赋值;替换甚至不是函数,而是带有__call__的对象);
    • 请注意,在Python中,您可以通过将其定义放置在函数中来动态创建包含代码(包括函数和类)的对象。然后在封闭函数执行时对定义进行评估,您可以将可访问变量(也称为闭包)用作临时参数。或
  • 在某个时候接受回调(或在适当的时候接受其方法将有效地充当一组回调的对象);或
  • 接受一个字符串参数,该参数是某个集合中的常量,然后代码在if / else中进行测试,或者在某个注册表中查找(无论是模块全局还是类本地或宾语);
    • 自3.4起就有enum,但在简单情况下,这样做的好处被认为是太多缺点(调试时不可读,需要样板),因为与C#相比,Python在灵活性方面更具灵活性-与可扩展性量表。

答案 2 :(得分:1)

据我所知,在Python中实现策略模式的最常见方法是传递一个函数(或可调用函数)。函数是Python中的一流对象,因此,如果所有消费者需要的只是一个函数,则您不需要提供更多的功能。当然,您可以根据需要添加注释。假设您只想记录字符串:

class ComponentThatNeedsLogger:
    def __init__(self, log_func: Callable[[str], None]):
        self._log_func = log_func

这允许您即时创建一个简单的记录器:

ComponentThatNeedsLogger(
    log_func=print
)

但是您还可以利用类的所有功能来创建复杂的记录器并仅传递相关方法。

class ComplexLogger:
    def __init__(self, lots_of_dependencies):
        # initialize the logger here

    def log(self, msg: str): None
        # call private methods and the dependencies as you need.

    def _private_method(self, whatever):
        # as many as you need.

ComponentThatNeedsLogger(
    log_func= ComplexLogger(lots_of_dependencies).log
)