面向对象编程/抽象
如果你曾经用咖啡机冲过咖啡,你可能就体验过抽象。当你冲咖啡时,你会确保有足够的咖啡豆,你会加水,加牛奶,也许还会放一个新的咖啡过滤器。然后,你按下按钮,你想要的咖啡就做好了。这是一个很好的抽象示例,因为用户不需要关心特定类型咖啡所需的牛奶量,或需要研磨多少咖啡豆才能得到正确的量,或水温需要多少度,用户只需要关心提供必要的材料来生产最终产品。这种结构也适用于面向对象编程。对象的用户应该只关心提供最终产品所需的必要参数,而不应该真正知道或需要知道对象内部是如何工作的。
在一些语言中,可以创建一个不能实例化的类。这意味着我们不能直接使用这个类来创建对象——我们只能从这个类继承,并使用子类来创建对象。
我们为什么要这样做?有时我们想指定一个对象需要具有的属性集,以便它适合某些任务——例如,我们可能编写了一个函数,它期望它的一个参数是一个具有某些方法的对象,我们的函数需要使用这些方法。我们可以创建一个类,通过定义这些对象必须实现的方法列表,来作为适合对象的模板。这个类不打算被实例化,因为我们所有的方法定义都是空的——所有方法的内部必须在子类中实现。
抽象类因此是一个接口定义——一些语言也有一种称为接口的结构类型,它非常相似。如果一个类继承自指定该接口的类,我们就说该类实现了该接口。
在 Python 中,我们无法阻止任何人实例化一个类,但我们可以通过在方法定义中使用NotImplementedError来创建类似于抽象类的东西。例如,以下是一些可以用作形状模板的“抽象”类
class Shape2D:
def area(self):
raise NotImplementedError()
class Shape3D:
def volume(self):
raise NotImplementedError()
任何二维形状都有面积,任何三维形状都有体积。计算面积和体积的公式取决于我们拥有什么形状,不同形状的对象可能具有完全不同的属性。
如果一个对象继承自 2DShape,它将获得该类的默认面积方法——但默认方法会引发一个错误,向用户明确表明必须在子对象中定义一个自定义方法
class Square(Shape2D):
def __init__(self, width):
self.width = width
def area(self):
return self.width ** 2