为什么要“面向对象”?
- 面向对象方法使构建系统更容易,因为:
- 大家发现面向对象更易理解
- 实现可以更简单
- 把数据和功能组合在一起简单而自然
- 分析和实现之间的概念跨度更小
- 设计良好的一组对象能弹性地适应重用和变化
- 可视化模型提供更有效的沟通
- 建模过程有助于创建通用词汇以及在开发者和用户/客户之间达成共识
- 非计算机编程人员也能理解对象模型
- 你能赚更多钱 :^)
这些好处可以使用面向对象方法获得,但面向对象方法不能保证这一点。
本课程不能把你变成优秀的面向对象设计者,这只有靠经验和聪明的头脑才能做到。
本课程的目标?
- 学习面向对象范例(paradigm)
- 学习UML的一个子集,用于可视化对象建模
- 学习为什么一些设计好于另一些
- 学习怎样用C++和Java实现面向对象设计
- 学习一些面向对象设计模式
- 了解一些最新、最好的商业面向对象技术
- 了解一个能最大限度利用面向对象技术的过程
- 为进一步的学习做好准备,开始着手一个面向对象项目
主要的面向对象概念
- 模型(Models)
- 对象(Objects)
- 类(Classes)
- 封装(Encapsulation)
- 抽象(Abstraction)
- 继承(Inheritance)
- 多态(Polymorphism)
- 泛化(Generalization)
过程化方法(The Procedural Approach)
- 系统由过程(procedures)组成
- 过程之间互相发送数据
- 过程和数据各自独立
- 集中于数据结构、算法和运算步骤的先后顺序
- 过程经常难以重用
- 缺乏具有较强表现力的可视化建模技术
- 分析与实现之间需要进行概念转换
- 本质上是机器/汇编语言的抽象
- 从设计模型到代码实现跨度很大
面向对象方法
- 系统由对象组成
- 对象互相发送消息(过程调用)
- 相关的数据和行为紧密地绑定在对象中
- 把问题领域建模成对象,要解决的问题自然的映射为代码的实现
- 可视模型表现力强,相对容易理解
- 集中于实现之前所确定的职责(responsibilities)和接口
- 强有力的概念:接口,抽象,封装,继承,委托(delegation)和多态
- 问题的可视模型逐渐进化成解决方案模型
- 设计模型与代码实现之间跨度较小
- 努力缩减软件的复杂度
例子:温度换算
- 过程/函数化方法
float c = getTemperature(); // 假定为摄氏度 Celsius
float f = toFarenheitFromCelcius( c );
float k = toKelvinFromCelcius( c );
float x = toKelvinFromFarenheit( f );
float y = toFarenheitFromKelvin( k );
- 面向对象方法
Temp temp = getTemperature();
float c = temp.toCelcius();
float f = temp.toFarenheit();
float k = temp.toKelvin();
- 包含有数据的Temp的内部单元是什么?
建模(Modeling)
- 成功的程序能解决真实世界的问题
- - 它们紧密对应于需要解决的问题
- - 它们对问题领域和用户活动进行建模
- 建模促进与用户更好的可视化交流。成功的面向对象设计总是一开始就由领域专家和软件设计者建立一个 反映问题领域的可视化的“对象模型”。
- 你愿意让承包人在没有设计蓝图的情况下建造你的新房子吗?
- 建模的本质展示所有精确相关的细节
对象
- 代表真实或抽象的事物,有一个名字
- 有明确的职责(well-defined responsibilities)
- 展示良好的行为(well-defined behavior)
- 接口清晰,并且尽可能简单
- 自相容,内聚,完备(self-consistent,coherent,and complete)
- (通常)不是很复杂或很大
- 只需要理解自己和一小部分其他对象的接口
- 与一小部分其它对象协同工作(team players)
- 尽可能地与其它对象松散耦合(loosely coupled)
- 很好地文档化,以便他人使用或重用
- 对象是类的实例,每一个对象都有唯一的标识
- 类定义一组对象的接口和实现,即定义了这些对象的行为
- 抽象类不能拥有实例
- 只要有抽象类(如宠物),通常就会有能够实例化的具体类(如猫,狗等)
- 一些面向对象语言(如Smalltalk)支持元类(metaclass)的概念,程序员可以随时(on-the-fly)定义一个类,然后实例化。这种情况下,类也是一个对象,即元类
- 对象一旦实例化,就不能更改它的类
对象的特征
- 有唯一标识
- 可以分成许多种类(即类)
- 可以继承或聚合
- 行为、职责明确
- 接口与实现分离
- 隐藏内部结构
- 有不同的状态
- 可以提供服务
- 可以给其它对象发送消息
- 从其它对象接收消息,并做出相应响应
- 可以把职责委托给其它对象
类
- 有公共的属性和行为的一组对象可以抽象成为类
- 对象通常根据你所感兴趣的属性而分类,
- 例如:街道,马路,高速公路...
- 不同的程序对它们分类也不同
- 交通模拟器程序
- 单行道,双通道,有分车道的,住宅区的,限制通行的
- location w/ respect to business commuters.
- 维护调度程序
- 路面材料
- 重型卡车运输
- location w/ respect to congressional district.
- 类本身也可以有属性和行为
- 例如:养老金管理程序中的“雇员”类
- 雇员总数
- 雇员编制多少
- 不同语言对类的支持略有不同:
- Smalltalk 把类当作对象(很有好处)
- C++提供最小限度的支持(有时会带来很多烦恼)
- Java位于上述两者之间
- 类也是对象
- 类可以有属性
- “雇员”类可以有一个包含其所有实例的列表(list)
- “彩票”类可以有一个种子(seed)用于产生随机票号,该种子被所有实例共享
- 类可以有行为
- “雇员”类可以有 getEmployeeBySerialNum 行为
- “彩票”类可以有 generateRandomNumber 行为
封装
- 只暴露相关的细节,即公有接口(public interface)
- 封装什么?如何封装?
- 隐藏“齿轮和控制杆”
- 只暴露客户需要的职责
- 防止对象受到外界干扰
- 防止其它对象依赖可能变化的细节
- 信息隐藏有助于对象和模块之间的松散耦合,使得设计更加灵活,更易于重用
- 减少代码之间的依赖
- “有好篱笆才有好邻居”
- 例如:汽车的气动踏板
- 最佳实践:对象之间只通过方法(函数)互相访问。切忌直接访问属性。
class Person {
public int age;
}
class BetterPerson {
private int age; // change to dateOfBirth
public int getAge() { return age; }
}
更完善的 Person 类可能是: private dateOfBirth
抽象
- 抽象使得泛化(generalizaions)成为可能
- 人类经常使用泛化。
- 当你看见约翰和简家里的那头灰德国牧羊犬时,你有没有......想到“狗”这个词?
- 抽象同样能简化计算机程序。例如,软件中有两个重要抽象:客户端和服务器(clients and servers)
抽象例子
- 在图形用户界面中,系统可能会询问用户各种问题:
- 统一处理这些问题会显得很简单,每一个问题都作为Question类的特例(specialization);程序只需维护 这些问题的实例列表,分别调用各自的askTheUser()方法。
继承
多态
- 是一种允许多个类针对同一消息有不同的反应的能力
- 对于任何实现了给定接口的对象,在不明确指定类名的情况下,就可以使用。
- 例如:question.askTheUser();
- 当然,这些不同反应都有类似的本质
- 尽可能使用接口继承和动态(运行期)绑定
-
Liskov 替换原则:
如果Y是X的子类,那么在任何使用X实例的地方都可以用Y的实例来替换。
演示多态的Java代码
// File: question/QuestionTest.java
// 下面的代码将输出什么?
// Refer to the Beginning Java link on the course web site.
package question;
abstract class Question { // Full class name is question. QuestionTest
public Question( String _text ) { // Constructor
theText = _text;
}
public abstract void askTheUser();
protected String theText;
}
class YesNoQuestion extends Question {
public YesNoQuestion( String _text ) { super( _text ); }
public void askTheUser() {
System.out.println( theText );
System.out.println( "YES or NO ...?" );
}
}
class FreeTextQuestion extends Question {
public FreeTextQuestion( String _text ) { super( _text ); }
public void askTheUser() {
System.out.println( theText );
System.out.println( "Well...? What’s the answer...?" );
}
}
public class QuestionTest {
public static void main(String[] args) {
Question[] questions = getQuestions();
for (int i = 0; i < questions.length; i++) {
questions[i].askTheUser(); // Polymorphism !!!
}
}
private static Question[] getQuestions() {
Question[] qs = new Question[2];
qs[0] = new YesNoQuestion("Do you understand polymorphism?");
qs[1] = new FreeTextQuestion("Why is polymorphism good?");
return qs;
}
}
输出:
Do you understand polymorphism?
YES or NO ...?
Why is polymorphism good?
Well...? What's the answer...?
更多的Java例子
// File: Derived.java
// What will the following Java code output to the screen?
class Base {
final void foo() {
System.out.println("Base foo");
}
void bar() {
System.out.println("Base bar");
}
}
public class Derived extends Base {
void bar() {
System.out.println("Derived bar");
}
public static void main(String[] args) {
Derived d = new Derived();
d.foo();
d.bar();
Base b = d;
b.bar();
}
}
输出:
Base foo
Derived bar
Derived bar
为什么面向对象有效:减小复杂度
- 封装:只暴露公有接口,隐藏了复杂的实现细节,避免代码之间复杂的相互依赖
- 多态:允许有相同接口的类互相替换,由此减小代码的复杂度
- 继承:使用抽象类或接口实现泛化来减小复杂度
- 委托:通过从更小、封装更好的服务来构建更完整或更高层次的服务来减小复杂度。委托还增加了运行时的灵活性。
为什么面向对象有效:语言学和辨识
- 我们主要使用名词,然后对它进行修饰和增加属性,最后,把它和动词联合在一起
- 面向对象设计遵循这个模式
- 过程化设计不遵循这个模式
- 这就是为什么人们经常发现对象更容易理解。
- 我们从对象模型中能形成构造良好的主谓宾(Subject-verb-object)句子:
- 试试用功能分解来形成主谓宾句子!
而且,人们广泛使用抽象和泛化...
面向对象是编程进化一个自然阶段
- 首先出现机器语言
- 在此基础上发展出汇编语言,提供了符号
- 高级语言出现:Fortran, Pascal, C等。它们提供了程序语句之间的“结构”关系。
- “goto”的使用日渐稀少,这有助于简化程序结构。
- 数据结构和算法提供了程序结构的可重用模式,促进更高层次上的抽象。
- 面向对象的抽象是为了关注于解决问题,而不是机器
- 通过更高层次的抽象,程序语句之间的关系转化成为相对简单的对象协作关系
- 设计模式提供可重用的对象结构...
面向对象更多的好处
- 组件非常有用...代码重用
- 设计模式很好...设计重用
- 接口不错...灵活健壮的代码
- 底层结构(infrastructure)和可重用服务同样很好
- 接口能完美分离个人和团队的职责,增加团队效率
- 松散耦合和模块化提高了扩展性、灵活性、可量测性和重用性
- 逻辑变化很自然的被隔离起来,这多亏了对象的模块化和信息隐藏(封装)。这意味着实现更快,维护更容易。
- 面向对象中间件使我们无须关注位置、平台和语言
- 一组设计良好的对象是我们可以增加新功能而不用更改设计
好的面向对象设计
- 艺术多于科学
- 可解决问题的模型本质上当然没有问题,但是一些模型就是比其它的好,这是因为它们更实用、更灵活、更容易扩展、更方便理解、更简单...
- 第一个设计几乎不可能是最好的设计。找到最好的抽象来对问题建模始终不是一件容易的事情,经验很重要。
- 经常需要尝试多次,来确定如何划分系统各部分之间的边界才是最好?每一部分应该为其它部分提供什么接口?
- 以体系结构为中心,而不是功能为中心。首先关注全面的大体的,其次才是具体的特定的。设计时首先做到这一点,就成功了一大半。
- 设计中要考虑可能发生的扩展,使得以后扩展是递增式的,不用更改设计。不同的设计可以有完全相同的功能,但是在这方面可能完全不同。
- 尽量推广可重用的面向服务的底层结构,这样,在需求不可避免的变化时,代码也能更快、更容易的更改。
职责
- 是面向对象分析中采用的最普遍的方法
- 对“客户端/服务器”有两种通用的解释:
- 用于分布式体系中,服务器提供对共享资源(如数据库)的访问,客户端提供用户界面
- 用于面向对象术语中,服务器是一个提供服务的对象;在这里我们使用这个含义
- 客户端与服务器协作(发送消息)
- 一个对象可能在一个协作中是客户端,而在另一个协作中是服务器
- 服务器负责提供某种服务
- 一般来说,对象应该以某种定义良好的方式工作
设计过程概述
- 查看领域,识别对象、类
- 确定对象之间和类之间的关系
- 赋予职责
- 迭代,迭代,迭代,迭代,迭代,迭代...
- 以领域建模作为开始,而不是以建模解决方案作为开始
CRC 卡片
- CRC方法使用3×5(英寸)索引卡片,一个类就是一张卡片,卡片上写有该类的职责以及为了完成这些职责必须与哪些类协作。类的简要描述写在卡片背面。
- 下面的例子中,类Foo必须与类X和类Y协作(给它们发送消息),以完成“do something”责任。
例子:“棍子”游戏
游戏设计两个玩家使用一台计算机来一起玩。游戏中许多棍子按行排列,当游戏开始时,它们如下排列:
1: |
2: | |
3: | | |
4: | | | |
游戏规则
- 玩家轮流参加,每人可以从任何一个非空行中移走一根或多根棍子。移走最后一根棍子的人为输家。
- 游戏开始时,程序将显示游戏的状态:轮到谁了,还有几行,还有多少棍子。
- 操作不符合规则,程序将给出提示。(如所移走的棍子数目超过该行的棍子总数)
- 找到对象和类…
- 用CRC卡片
- 附加问题:哪个类负责记录轮到那个玩家了?
类Row 的CRC卡片
词汇(Vocabulary)
- 类(Class)
- – 抽象(Abstract) / 具体(Concrete) / 元(Meta)
- 对象(Object)
- – 实例(Instance)
- – 标识(Identity)
- 属性(Attribute)
- – 成员(Member)
- – 域(Field)
- – 状态(State)
- 行为(Behavior)
- – 方法(Method)
- – 成员函数(Member Function)
- – 操作(Operation)
- – 职责(Responsibility)
- – 消息(Message)
- – 调用方法(Method Call)
- 接口(Interface)
- 抽象(Abstraction)
- – 泛化(Generalization)
- – 特殊化(Specialization)
- 继承(Inheritance)
- – 接口(Interface) / 实现(Implementation)
- – 基类(Base) / 派生类(Derived), 父类(Parent) / 子类(Child), 超类(Super) / 子类(Sub)
- 委托(Delegation)
- 协作(Collaboration)
- 多态(Polymorphism)
- – Liskov 替换原则(Liskov Substitution Principle)
- – 动态绑定(Dynamic (run-time) Binding)
- 聚合(Aggregation)
- 底层结构(Infrastructure)
- – 服务(Services) / 中间件(Middleware) / 框架(Frameworks)
- 统一建模语言(Unified Modeling Language (UML))
- 分析(Analysis) / 设计(Design) / 实现(Implementation) / 架构(Architecture) / 过程(Process)
- 松散耦合(Loose Coupling & Flexibility)
- 封装(Encapsulation)
- – 信息隐藏(Information Hiding)
- 模块性(Modularity)
- 透明(Transparency)
- Java
- – 构造函数(Constructor) / 包(Package) / 静态(Static)
- 模式(Patterns)
- 职责驱动设计(Responsibility driven design)
阅读(566) | 评论(0) | 转发(0) |