《设计模式解析(第2版•修订版)》—第1章 1.6节面向对象范型

简介: 面向对象范型以对象概念为中心,一切都集中在对象上。编写代码时是围绕对象而非函数进行组织的。

本节书摘来自异步社区《设计模式解析(第2版•修订版)》一书中的第1章,第1.6节面向对象范型,作者【美】Alan Shalloway(艾伦•沙洛维) , James R.Trott(詹姆斯•R.特罗特),更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.6 面向对象范型
设计模式解析(第2版•修订版)
使用对象将责任转移到更局部的层次

面向对象范型以对象概念为中心,一切都集中在对象上。编写代码时是围绕对象而非函数进行组织的。

对象是什么?对象传统上被定义为带有方法(面向对象领域称呼函数的术语)的数据。糟糕的是,这是一种非常有局限性的对象观。稍后我会给出一个更好的对象定义(在第8章中还会谈到)。我说到对象的数据时,可能指数值和字符串这样的简单事物,也可能指其他对象。

使用对象的优点在于,可以定义自己负责自己的事物(参见表1-2)。对象天生就知道自己的类型。对象中的数据能够告诉它自己的状态如何,而对象中的代码能够使它正确工作(也就是说,做要求它做的事情)。


11fc6131de2a48d78b23d9b22635a61e3eb18f30

在这种情况下,对象是通过寻找在问题领域中的实体而被发现的。然后再通过查看这些实体需要做些什么,为每个对象确定责任(或者称方法)。这与通过在需求中寻找名词发现对象和通过寻找动词发现方法的技术是一致的。随着所遇到问题更加复杂,我们将看到,这种技术存在很大局限性,本书中我会给出一种更好的方式。但是现在我们还要从此方式开始入手。

怎么理解对象?

理解对象的最佳方式,是将其看成“具有责任的东西”。有一条好的设计规则:对象应该自己负责自己,而且应该清楚地定义责任。这就是我之所以说“Student对象的责任之一是知道怎样从一个教室去下一个教室”的原因。

或者,使用Fowler的视角

还可以用Martin Fowler的视角框架来观察对象:

在概念层次上,对象是一组责任;1
在规约层次上,对象是一组可以被其他对象或对象自己调用的方法(也称行为); 
在实现层次上,对象是代码和数据,以及它们之间的计算交互。
糟糕的是,人们对面向对象设计的教学和讨论更多的是停留在实现层次上——也就是只考虑代码和数据,对概念层次和规约层次重视很不够。然而,从后两种层次去思考对象其实也具有巨大的效力。

对象具有供其他对象使用的接口

因为对象具有责任而且自己负责自己,所以必须有办法告诉对象要做什么。还记得吗?对象含有说明自己状态的数据,还有实现必要功能的方法。对象的很多方法都将标识为可被其他对象调用。这些方法的集合就称为对象的公开接口(public interface)。

例如,在教室的例子中,我可以编写含有一个gotoNextClassroom()方法的Student对象。我不需要向这个方法传递任何参数,因为每个Student对象都自己负责自己。也就是说,Student对象知道:

为了能够找到下一个教室,它需要什么;
怎样为完成这个任务获取所需的其他信息。
围绕类组织对象

刚开始,只有一种学生——普通学生需要从一个教室到另一个教室去。请注意,在我的教室(我的系统)中可能有很多这样的“普通学生”。当然可以每个学生都有一个对象对应,从而能够容易地和分别地跟踪每个学生的状态。但是,要求每个Student对象都有自己的一组方法,告诉它能做什么和怎样做,显然效率很低,尤其是在对所有学生而言任务都一样的时候。

一种效率更高的办法是,让所有学生与一组方法关联起来,每个学生都可以根据自己的需要使用或修改这些方法。我希望定义一个“一般学生”来包含这些公共方法的定义。然后,可以有各种各样特殊的学生,每个特殊学生都必须掌握自己的私有信息。

在面向对象术语中,这种“一般学生”被称为类(class)。类就是对对象行为的定义,它包含以下内容的完整描述:

对象所包含的数据元素;
对象能够操作的方法;
访问这些数据元素和方法的方式。
因为对象所包含的数据元素可以不同,所以同一类型的对象可以含有不同数据,但它们都具有相同的功能(如方法所定义)。

对象是类的实例

要获得一个对象时,我告诉程序需要某个类型(type,也就是对象所属的类)的一个新对象,这个新对象称为类的一个实例(instance)。创建类实例的过程称为实例化(instantiation)。

在例子中使用对象

使用面向对象方法为“去下堂课教室”的例子编写代码比以前的方法简单多了。步骤如下所示。

1.开始控制程序。

2.实例化室中学生的集合。

3.告诉此集合,让学生去自己下堂课的教室。

4.集合让每个学生去自己下堂课的教室。

5.每个学生都:

  a.找到自己下堂课的教室在哪里;

  b.决定怎么去;

  c.去那里。

  6.完成。

抽象类型的需要

在需要加入另一个学生类型比如研究生之前,这一方式能够很好地工作。

遇到难题了。看起来我必须允许任何类型的学生(普通学生或者研究生)加入这个集合。但我面临的问题是,怎样让集合引用其元素呢?因为我是在讨论如何用代码实现,这时集合实际上将是包含某个类型对象的数组或其他容器。如果集合命名为RegularStudent(普通学生)之类,我就不能将GraduateStudent(研究生)类型的对象放入集合。如果我说集合仅仅是一组对象,我又怎么确定其中不包含类型错误的对象(即不能“去下堂课的教室”)呢?

解决方案很直截了当。我需要一个能包容多种具体类型的一般类型。在本例中,我需要一个包含RegularStudent对象和GraduateStudent对象的Student类型。在面向对象的术语中,我们称Student类为抽象类(abstract class)2。

抽象类定义了一组类可以做什么

抽象类定义了其他一些相关类的行为。这些“其他”类是代表了某种特殊类型的相关行为的类。这样的类通常被称为具体类(concrete class),因为它代表着一个概念特定的、不变的实现。

在本例中,Student就是抽象类。具体类RegularStudent和GraduateStudent则代表了两种类型的Student。RegularStudent是一种Student,GraduateStudent也是一种Student。

这种关系叫做is-a(是一个/种)关系,是我们称之为继承(inheritance)关系的一种特例。于是,我们说RegularStudent类继承自Student类。其他类似的说法还有:GraduateStudent派生自Student类,Graduate- Student特化(specialize)了Student类,或者GraduateStudent是Student的子类。

而另一方面,我们说Student类是GraduateStudent类和Regular-Student的基类,Student类泛化(generalize)了二者,或者Student类是GraduateStudent类和RegularStudent的超类(superclass)。

抽象类可以充当其他类的占位符

抽象类可以充当其他类的占位符。可以使用抽象类定义其派生类必须实现的方法。抽象类还可以包含所有派生类都能够使用的公共方法。3派生类是使用抽象类的默认行为还是使用自己的有所变化的行为,由派生类自己决定(这与“对象自己负责自己”的要求一致)。

这就意味着,我可以在控制程序中编写一些对象,它们的引用类型都是Student。编译器能够检查Student引用所指向的是否真的是一种Student。这种机制使我们能够实现鱼与熊掌兼得,同时获得了以下两方面的优点。

集合只需处理Student对象(从而使Instructor对象也只需要处理Student对象)。
但是类型检查仍然存在(只有能够“去下堂课教室”的Student对象会包含进来)。
而且每一种Student都可以按自己的方式实现功能。
抽象类不只是不能实例化

抽象类经常被描述为“不能实例化的类”。这个定义本身没错——在实现层次上。但是局限性太大了。在概念层次上定义抽象类会更有帮助。在概念层次,抽象类就是(实现抽象类所代表的概念的)其他类的占位符。

也就是说,抽象类为我们提供了一种方法,能够给一组相关的类赋予一个名字。这使我们能够将这一组相关类看成一个概念。

在面向对象范型中,必须总是从概念、规约和实现所有三个视角层次来思考问题。
可见性

因为对象都自己负责自己,所以有很多东西不需要暴露给其他对象。前面我曾提到公开接口——可以被其他对象访问的方法的概念。在面向对象系统中,可访问性主要分为以下几种类型4。

公开(public)——任何对象都能够看见。
保护(protected)——只有这个类及其派生类的对象能够看见。
私有(private)——只有这个类的对象能够看见。
这就引出了封装(encapsulation)的概念。封装经常被简单地描述成“数据隐藏”。一般而言,对象不应该将内部数据成员暴露给外部世界。(也就是说,其可见性是protected或private。)

封装

但封装可不只是指数据隐藏。封装一般意味着各种隐藏。

在本例中,讲师不知道哪些是普通学生,哪些是研究生。所以学生的类型对讲师隐藏了。(也就是说,我封装了学生的类型。)在面向对象语言中,抽象类Student将隐藏从其派生的类的类型。你将在本书后面看到,这是一个非常重要的概念。

多态

另一个要理解的术语是多态(polymorphism)。

在面向对象语言中,我们经常用抽象类类型的引用来引用对象。但是,我们真正引用的是从抽象类派生的类的具体实例。

因此,当我通过抽象引用概念性地要求对象做什么时,将得到不同的行为,具体行为取决于派生对象的具体类型。“多态”这个词来源于“poly”(意为“很多”)和“morph”(意为“形态”)。因此,它的意思是“很多形态”。这个名称非常合适,因为同一个调用能够获得很多不同形态的行为。

在本例中,讲师告诉学生“去下堂课的教室”。但是,根据学生类型的不同,他们会采取不同的行为(因此出现了多态)。


9799f1c44b1d5ea5b18af938b69e4a0f32887202


25d19da5f27f94b69cd149fccbb220d265a18e2c
  • 有些面向对象分析人员说万事万物皆对象:类是对象,实例也是对象。这在技术上可能是正确的,但是却成了混淆和发生争议的地方。本书所称的对象是类的实例。

1这里比较粗略地套用了Bertrand Meyer在Object-Oriented Software Construction(Upper Saddle River,N.J.: Prentice Hall,1997,p. 331)中概述的“按约定设计”(design by contract)的概念。
2有几种语言中接口也能如此。本章中提到抽象类时,可以假定我编写的是抽象类或者接口。
3抽象类和接口之间有一点不同。接口只定义一组类能够做什么,而并不实现默认行为。
4不同的语言经常有其他类型的可访问性,但是,本质上都是这三种的变种而已。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

相关文章
|
2月前
|
设计模式 存储 前端开发
Java Web开发中MVC设计模式的实现与解析
Java Web开发中MVC设计模式的实现与解析
|
3月前
|
设计模式 Java 编译器
Java 设计模式最佳实践:一、从面向对象到函数式编程
Java 设计模式最佳实践:一、从面向对象到函数式编程
|
4月前
|
设计模式
二十三种设计模式全面解析-解放组件间的通信束缚:深入探讨中介者模式的高级应用和进阶技巧
二十三种设计模式全面解析-解放组件间的通信束缚:深入探讨中介者模式的高级应用和进阶技巧
|
4月前
|
设计模式
二十三种设计模式全面解析-解密中介者模式:构建灵活的通信桥梁
二十三种设计模式全面解析-解密中介者模式:构建灵活的通信桥梁
|
4月前
|
设计模式 存储 缓存
二十三种设计模式全面解析-探索解释器模式如何应对性能挑战
二十三种设计模式全面解析-探索解释器模式如何应对性能挑战
|
4月前
|
设计模式 存储 缓存
二十三种设计模式全面解析-探索解释器模式的高级应用和优化技巧:解锁代码解析的新境界
二十三种设计模式全面解析-探索解释器模式的高级应用和优化技巧:解锁代码解析的新境界
|
4月前
|
设计模式 自然语言处理 编译器
二十三种设计模式全面解析-解释器模式(Interpreter Pattern):用代码诠释语言的魅力
二十三种设计模式全面解析-解释器模式(Interpreter Pattern):用代码诠释语言的魅力
|
4月前
|
设计模式 存储
二十三种设计模式全面解析-深入探究备忘录模式:保留过去,预见未来
二十三种设计模式全面解析-深入探究备忘录模式:保留过去,预见未来
|
11天前
|
设计模式 SQL 算法
设计模式了解哪些,模版模式
设计模式了解哪些,模版模式
19 0
|
30天前
|
设计模式 Java uml
C++设计模式之 依赖注入模式探索
C++设计模式之 依赖注入模式探索
37 0

推荐镜像

更多