如何正确地实现虚拟类?

2024-08-06 09:56:58 浏览数 (1)

在 Python 中,所谓的虚拟类通常是指抽象基类(Abstract Base Class,简称 ABC)。抽象基类不可实例化,其主要作用是定义一组抽象方法,子类必须实现这些抽象方法才能被实例化。

要正确实现虚拟类(抽象基类),可以按照我下文写的步骤来。

1、问题背景

在类继承、抽象基类甚至python接口的文档中,没有一种方式能够完全满足需求。当调用虚拟类时,希望它能够根据给定参数实例化一些更具体的类,并将该类返回给调用函数。

在现有的实现中,通过一种汇总方式将对虚拟类的调用重定向到基础类。思路如下:

代码语言:javascript复制
class Shape:
    def __init__(self, description):
        if   description == "It's flat":  self.underlying_class = Line(description)
        elif description == "It's spiky": self.underlying_class = Triangle(description)
        elif description == "It's big":   self.underlying_class = Rectangle(description)
    def number_of_edges(self, parameters):
        return self.underlying_class(parameters)
​
class Line:
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 1
​
class Triangle:
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 3
​
class Rectangle:
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 4
​
shape_dont_know_what_it_is = Shape("It's big")
shape_dont_know_what_it_is.number_of_edges(parameters)

这种重定向方式并不理想,因为它只适用于对number_of_edges()函数的调用,并且向Shape类添加类似以下内容似乎也无法解决问题:

代码语言:javascript复制
def __getattr__(self, *args):
    return underlying_class.__getattr__(*args)

2、解决方案

有一些方法可以解决这个问题:

方法1:使用new方法

代码语言:javascript复制
class Shape(object):
    def __new__(cls, *args, **kwargs):
        if cls is Shape:                            # <-- required because Line's
            description, args = args[0], args[1:]   #     __new__ method is the
            if description == "It's flat":          #     same as Shape's
                new_cls = Line
            else:
                raise ValueError("Invalid description: {}.".format(description))
        else:
            new_cls = cls
        return super(Shape, cls).__new__(new_cls, *args, **kwargs)
​
    def number_of_edges(self):
        return "A shape can have many edges…"
​
class Line(Shape):
    def number_of_edges(self):
        return 1
​
class SomeShape(Shape):
    pass
​
​
>>> l1 = Shape("It's flat")
>>> l1.number_of_edges()
1
>>> l2 = Line()
>>> l2.number_of_edges()
1
>>> u = SomeShape()
>>> u.number_of_edges()
'A shape can have many edges…'
>>> s = Shape("Hexagon")
ValueError: Invalid description: Hexagon.

方法2:使用工厂函数

代码语言:javascript复制
def factory(description):
    if   description == "It's flat":  return Line(description)
    elif description == "It's spiky": return Triangle(description)
    elif description == "It's big":   return Rectangle(description)
​
或:
def factory(description):
    classDict = {"It's flat":Line("It's flat"), "It's spiky":Triangle("It's spiky"), "It's big":Rectangle("It's big")}
    return classDict[description]
​
并从Shape继承类
class Line(Shape):
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 1

对于方法3,Python中没有开箱即用的虚拟类,需要自行实现(这应该是可能的,Python的反射能力足以实现这一点)。然而,如果需要使用虚拟类,建议使用具有虚拟类的编程语言,如Beta、gBeta或Newspeak。不过,在具体的示例中,并不清楚虚拟类如何简化解决方案,或者为什么需要使用虚拟类。

在这几个例子中:

  • Animal 是一个抽象基类,定义了一个抽象方法 make_sound
  • DogCatAnimal 的具体子类,它们必须实现 make_sound 方法才能被实例化。
  • 如果某个子类没有实现抽象基类中的所有抽象方法,尝试实例化该子类会引发 TypeError

注意事项:

  • 抽象基类本身不能被实例化,只能用作其他类的基类。
  • 子类必须实现抽象基类中的所有抽象方法,否则会导致运行时错误。
  • 抽象基类可以包含具体方法,这些方法可以被子类继承或重写。
  • 使用抽象基类能够帮助你设计更加规范和易于维护的类层次结构,强制执行接口和方法的一致性。

通过合理使用抽象基类,可以提高代码的可读性和可维护性,同时在设计接口时提供更好的约束和规范。

0 人点赞