第二章 使用对象构建抽象 - 2.6 实现类和对象

优质
小牛编辑
125浏览
2023-12-01

在使用面向对象编程范式时,我们使用对象隐喻来指导程序的组织。数据表示和操作的大部分逻辑都表达在类的定义中。在这一节中,我们会看到,类和对象本身可以使用函数和字典来表示。以这种方式实现对象系统的目的是展示使用对象隐喻并不需要特殊的编程语言。即使编程语言没有面向对象系统,程序照样可以面向对象。

为了实现对象,我们需要抛弃点运算符(它需要语言的内建支持),并创建分发字典,它的行为和内建对象系统的元素差不多。我们已经看到如何通过分发字典实现消息传递行为。为了完整实现对象系统,我们需要在实例、类和基类之间发送消息,它们全部都是含有属性的字典。

我们不会实现整个 Python 对象系统,它包含这篇文章没有涉及到的特性(比如元类和静态方法)。我们会专注于用户定义的类,不带有多重继承和内省行为(比如返回实例的类)。我们的实现并不遵循 Python 类型系统的明确规定。反之,它为实现对象隐喻的核心功能而设计。

我们从实例开始。实例拥有具名属性,例如账户余额,它可以被设置或获取。我们使用分发字典来实现实例,它会响应“get”和“set”属性值消息。属性本身保存在叫做的局部字典中。

就像我们在这一章的前面看到的那样,字典本身是抽象数据类型。我们使用列表来实现字典,我们使用偶对来实现列表,并且我们使用函数来实现偶对。就像我们以字典实现对象系统那样,要注意我们能够仅仅使用函数来实现对象。

为了开始我们的实现,我们假设我们拥有一个类实现,它可以查找任何不是实例部分的名称。我们将类作为参数cls传递给make_instance

绑定方法值。make_instance中的get_value使用get寻找类中的具名属性,之后调用bind_method。方法的绑定只在函数值上调用,并且它会通过将实例插入为第一个参数,从函数值创建绑定方法的值。

  1. >>> def bind_method(value, instance):
  2. """Return a bound method if value is callable, or value otherwise."""
  3. if callable(value):
  4. def method(*args):
  5. return value(instance, *args)
  6. return method
  7. else:
  8. return value

当方法被调用时,第一个参数self通过这个定义绑定到了instance的值上。

类也是对象,在 Python 对象系统和我们这里实现的系统中都是如此。为了简化,我们假设类自己并没有类(在 Python 中,类本身也有类,几乎所有类都共享相同的类,叫做type)。类可以接受getset消息,以及new消息。

  1. >>> def make_class(attributes, base_class=None):
  2. """Return a new class, which is a dispatch dictionary."""
  3. def get_value(name):
  4. if name in attributes:
  5. return attributes[name]
  6. elif base_class is not None:
  7. def set_value(name, value):
  8. attributes[name] = value
  9. return init_instance(cls, *args)
  10. cls = {'get': get_value, 'set': set_value, 'new': new}
  11. return cls

不像实例那样,类的get函数在属性未找到的时候并不查询它的类,而是查询它的base_class。类并不需要方法绑定。

实例化。make_class中的new函数调用了init_instance,它首先创建新的实例,之后调用叫做__init__的方法。

  1. >>> def init_instance(cls, *args):
  2. """Return a new object with type cls, initialized with args."""
  3. instance = make_instance(cls)
  4. init = cls['get']('__init__')
  5. if init:
  6. init(instance, *args)
  7. return instance

最后这个函数完成了我们的对象系统。我们现在拥有了实例,它的set是局部的,但是get会回溯到它们的类中。实例在它的类中查找名称之后,它会将自己绑定到函数值上来创建方法。最后类可以创建新的(new)实例,并且在实例创建之后立即调用它们的__init__构造器。

在对象系统中,用户仅仅可以调用create_class,所有其他功能通过消息传递来使用。与之相似,Python 的对象系统由class语句来调用,它的所有其他功能都通过点表达式和对类的调用来使用。

我们现在回到上一节银行账户的例子。使用我们实现的对象系统,我们就可以创建Account类,CheckingAccount子类和它们的实例。

在这个函数中,属性名称在最后设置。不像 Python 的class语句,它强制内部函数和属性名称之间的一致性。这里我们必须手动指定属性名称和值的对应关系。

Account类最终由赋值来实例化。

  1. >>> Account = make_account_class()

之后,账户实例通过new消息来创建,它需要名称来处理新创建的账户。

  1. >>> jim_acct = Account['new']('Jim')

之后,消息传递给jim_acct,来获取属性和方法。方法可以调用来更新账户余额。

  1. >>> jim_acct['get']('holder')
  2. 'Jim'
  3. 0.02
  4. >>> jim_acct['get']('deposit')(20)
  5. 20
  6. >>> jim_acct['get']('withdraw')(5)
  7. 15

就像使用 Python 对象系统那样,设置实例的属性并不会修改类的对应属性:

继承。我们可以创建CheckingAccount子类,通过覆盖类属性的子集。在这里,我们修改withdraw方法来收取费用,并且降低了利率。

  1. >>> def make_checking_account_class():
  2. """Return the CheckingAccount class, which imposes a $1 withdrawal fee."""
  3. def withdraw(self, amount):
  4. return Account['get']('withdraw')(self, amount + 1)
  5. return make_class({'withdraw': withdraw, 'interest': 0.01}, Account)

在这个实现中,我们在子类的withdraw中调用了基类Accountwithdraw函数,就像在 Python 内建对象系统那样。我们可以创建子类本身和它的实例,就像之前那样:

  1. >>> CheckingAccount = make_checking_account_class()
  2. >>> jack_acct = CheckingAccount['new']('Jack')

它们的行为相似,构造函数也一样。每笔取款都会在特殊的withdraw函数中收费 $1,并且interest也拥有新的较低值。

  1. >>> jack_acct['get']('interest')
  2. 0.01
  3. >>> jack_acct['get']('deposit')(20)
  4. 20
  5. >>> jack_acct['get']('withdraw')(5)