创建动态子类型时摆脱`if`语句

时间:2019-03-02 12:41:18

标签: python oop design-patterns

主要是关于设计的问题。

让我们假设有一个类,该类具有通过其迭代通过的某些参数来初始化特定类型的不同子类的作用。当__init__方法为每个子类型接收不同的参数时,就会出现问题。有什么方法可以避免函数内部的if语句初始化类,以便仅知道要传入的参数?也许我不知道一些设计模式。还是设计不良的结果?

下面是我的意思的一个例子。请注意其中包含if ... else ...的manage static方法,并且如果有更多类型的worker,我们将拥有更多ifs,这就是我要避免的方法。 请记住,该示例是最少的,而if语句可能要复杂得多。

from abc import ABCMeta


class BaseWorker(metaclass=ABCMeta):
    def work(self):
        pass


class Worker1(BaseWorker):
    def __init__(self, name):
        self.name = name

    def work(self):
        pass


class Worker2(BaseWorker):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def work(self):
        pass


class Manager(object):
    @staticmethod
    def manage(attributes_list):
        for attributes in attributes_list:
            if "age" in attributes:
                w = Worker2(name=attributes["name"], age=attributes["age"])
            else:
                w = Worker1(name=attributes["name"])
            w.work()


if __name__ == '__main__':
    dynamic_attributes = [
        {"name": "davay"},
        {"age": "55", "name": "ok"},
        # and so on...
    ]
    Manager.manage(dynamic_attributes)

所需的解决方案应该是

    @staticmethod
    def desired_manage(attributes_list):
        for attributes in attributes_list:
            w = worker_factory(attributes)
            w.work()

**请注意,worker_factory只是解决此问题的方式的任意名称,并不意味着工厂模式是可行的方式。 甚至更少,如果我们尝试工厂模式,从我看到的情况来看,if语句将仅移动到那里,将无法解决任何问题。

谢谢!

2 个答案:

答案 0 :(得分:1)

一种方法是在Manager中注册每个子类,以及确定该子类是否应实例化的可调用对象。

类似的东西(未经测试):

class Manager(object):

    _registry = []
    _default = None

    @classmethod
    def register(cls, callable, klass):
        cls._registry.append((callable, klass))
        return

    @classmethod
    def register_default(cls, klass):
        cls._default = klass
        return

    @staticmethod
    def manage(attributes_list):
        for attributes in attributes_list:
            for callable, klass in Manager._registry:
                if callable(attributes):
                    w = klass(**attributes)
                    break
        else:
            w = Manager._default(**attrs)
        w.work()


class BaseWorker(metaclass=ABCMeta):
    def work(self):
        pass


class Worker1(BaseWorker):
    def __init__(self, name):
        self.name = name

Manager.register_default(Worker1)  


class Worker2(BaseWorker):
    def __init__(self, name, age):
        self.name = name
        self.age = age 

Manager.register(Worker2, lambda attrs: 'age' in attrs)

此方法有一些缺点:

  • 条件按插入顺序进行测试,因此您需要确保在诸如'name' in attrs and 'age' in attrs这样的宽松条件之前先测试更严格的条件(例如'name' in attrs)。如果在不同的模块中定义了子类,则可能具有挑战性。也许为每个(callable, klass)元组添加一个优先级,可用于对注册表进行排序
  • 如果attributes_list大(和/或有很多子类),则嵌套循环可能会很慢

答案 1 :(得分:0)

如果子类最后将共享工作方法,则您不需要此子类,并且实现它们将是错误的设计,从而不必要地增加了难度。您可以在 init 中使用默认值作为不需要的参数,然后它可以为每个子类型接收不同的参数而无需抱怨。

from abc import ABCMeta


class BaseWorker(metaclass=ABCMeta):
    def work(self):
        pass


class Worker(BaseWorker):
    def __init__(self, name, age=None, address=None, sex=None):
        self.name = name
        self.age = age
        address= address
        sex= sex

    def work(self):
        pass


class Manager(object):
    @staticmethod
    def manage(attributes_list):
        for attributes in attributes_list:
            w = Worker(**attributes)
            w.work()


if __name__ == '__main__':
    dynamic_attributes = [
        {"name": "davay"},
        {"name": "aa", "age": "55"},
        {"name": "bb", "sex": "female"},
        {"name": "cc", "address": "123 street", "age": "43"},
        # and so on...
    ]
    Manager.manage(dynamic_attributes)