【问题标题】:pick a subclass based on a parameter根据参数选择子类
【发布时间】:2011-09-01 16:44:08
【问题描述】:

我有一个模块(db.py),它从不同的数据库类型(sqlite、mysql 等)加载数据。该模块包含一个类 db_loader 和继承自它的子类(sqlite_loader、mysql_loader)。

正在使用的数据库类型在单独的 params 文件中,

用户如何取回正确的对象?

即我该怎么做:

loader = db.loader()

我是在 db.py 模块中使用一种称为 loader 的方法,还是有一种更优雅的方法,一个类可以根据参数选择自己的子类?有没有标准的方法来做这种事情?

【问题讨论】:

    标签: python oop


    【解决方案1】:

    听起来你想要Factory Pattern。您定义了一个工厂方法(在您的模块中,或者可能在它可以生成的所有对象的公共父类中),您将参数传递给它,它将返回正确类的实例。在 python 中,问题可能比维基百科文章中的一些细节要简单一些,因为您的类型是动态的。

    class Animal(object):
    
        @staticmethod
        def get_animal_which_makes_noise(noise):
            if noise == 'meow':
                return Cat()
            elif noise == 'woof':
                return Dog()
    
    class Cat(Animal):
        ...
    
    class Dog(Animal):
        ...
    

    【讨论】:

    • 谢谢!这似乎是最优雅的解决方案!
    • 是的,但这需要我每次有新动物时都扩展工厂方法。有什么方法可以很好地“自动检测”子类,其中noise 是子类中的某个属性值?
    • 您可以通过该属性查找子类:return [subclass for subclass in cls.__subclasses__() if subclass.noise == noise][0]
    【解决方案2】:

    有没有更优雅的方式让类可以根据参数选择自己的子类?

    您可以通过覆盖基类的__new__ 方法来做到这一点。这将允许您简单地转到loader = db_loader(db_type)loader神奇地 成为数据库类型的正确子类。这个解决方案比其他答案稍微复杂一些,但恕我直言,它肯定是最优雅的。

    最简单的形式:

    class Parent():
        def __new__(cls, feature):
            subclass_map = {subclass.feature: subclass for subclass in cls.__subclasses__()}
            subclass = subclass_map[feature]
            instance = super(Parent, subclass).__new__(subclass)
            return instance
    
    class Child1(Parent):
        feature = 1
    
    class Child2(Parent):
        feature = 2
    
    type(Parent(1))  # <class '__main__.Child1'>
    type(Parent(2))  # <class '__main__.Child2'>
    

    (注意只要__new__返回一个cls的实例,就会自动为你调用实例的__init__方法。)

    这个简单的版本虽然有问题,需要扩展和定制以适应您想要的行为。最值得注意的是,这是您可能想要解决的问题:

    Parent(3)  # KeyError
    Child1(1)  # KeyError
    

    所以我建议将cls 添加到subclass_map 或将其用作默认值,例如subclass_map.get(feature, cls)。如果您的基类不打算被实例化——也许它甚至有抽象方法? -- 那么我建议给Parent 元类abc.ABCMeta

    如果您也有孙类,那么我建议将子类的集合放入递归类方法中,该方法遵循每个沿袭到最后,添加所有后代。

    这个解决方案比工厂方法模式恕我直言更漂亮。与其他一些答案不同,它是自我维护的,因为子类列表是动态创建的,而不是保存在硬编码映射中。而且这只会实例化子类,不像其他答案之一,它将实例化与给定参数匹配的全局命名空间中的任何内容。

    【讨论】:

    • 我真的很喜欢这个。我试过这个,但当孩子们有一些额外的参数/属性时,我没有让它工作。你知道如何处理这种情况吗?
    • @Patricio 没有看到您的代码,我只能猜测可能有什么帮助...如果您在选择子类时需要考虑多个参数,那么subclass_map 的键可能是这些参数的元组。
    • 基本上我要做的是:stackoverflow.com/questions/61339788/…
    【解决方案3】:

    我会将子类的名称存储在 params 文件中,并有一个工厂方法来实例化给定名称的类:

    class loader(object):
      @staticmethod
      def get_loader(name):
        return globals()[name]()
    
    class sqlite_loader(loader): pass
    
    class mysql_loader(loader): pass
    
    print type(loader.get_loader('sqlite_loader'))
    print type(loader.get_loader('mysql_loader'))
    

    【讨论】:

    • 谢谢!这似乎是一个很好的解决方案,我认为与actionshrimp基本相同?
    • @Mike Vella:与actionshrimp 解决方案的主要区别在于,您不需要在一系列if-elif 语句中显式命名每个类。换句话说,如果您添加一个新的加载器类,它可以立即在 params 文件中指定:无需记住进入并更新那个大的if
    【解决方案4】:

    将类存储在dict 中,根据您的参数实例化正确的类:

    db_loaders = dict(sqlite=sqlite_loader, mysql=mysql_loader)
    loader = db_loaders.get(db_type, default_loader)()
    

    db_type 是您要打开的参数,sqlite_loadermysql_loader 是“加载器”类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-11
      • 1970-01-01
      • 2012-08-09
      • 1970-01-01
      • 2021-01-25
      • 2010-10-21
      • 1970-01-01
      相关资源
      最近更新 更多