《JAVA面向对象编程》读书笔记-继承与组合

 

一些

面向对象的设计

法则

 

Bob Tarr

outmyth

mahope

      

 


 

n          

法则#1:将类和成员的可访问性最小化

[Minimize The Accessibility of Classes and Members]

抽象的含义

n         Tony Hoare: “抽象起源于对真实世界中的对象、事态、过程之间的相似性的认识,以及在聚集这些相似性的同时忽略差异性的决心

n         Grady Booch: “抽象表示一个对象区别与其他类别的对象的本质特征,从而相对于观察者的角度来说,为其提供了明确定义的概念边界

n         抽象是处理复杂事物的基本方法之一;

n         抽象关注对象的外部视图并将其行为与它的实现隔离开来。

封装

n         Grady Booch: “封装是划分抽象的元素的过程,这些元素构成了抽象的结构和行为;封装的功能是将抽象的契约接口与其实现分开

n         Craig Larman: “封装是隐藏对象的数据、内部结构和实现细节的机制。所有与对象的交互都是通过公共操作这个接口

n         类应该是不透明的;

n         类不应暴露其内部实现细节。

Java中的信息隐藏

n         尽可能第使用私有成员和相应的访问器/修改器(accessors and mutators

n         举例:

替换

public double speed;

     private double speed;

     public double getSpeed()

     {

         return (speed);

     }

     public void setSpeed(double newSpeed)

     {

         speed = newSpeed;

}

使用访问器/修改器,而不是共有成员

n         你可以在值上附加约束条件

     public void setSpeed(double newSpeed)

     {

         if (newSpeed < 0)

         {

              sendErrorMessage(...);

              newSpeed = Math.abs(newSpeed);

         }

         speed = newSpeed;

     }

n         如果使用你的类的用户直接访问类的字段,那么他们也会担负检查约束条件的责任

n         你可以改变类的内部表示而不用改变接口

     // Now using metric units (kph, not mph)

     public void setSpeedInMPH(double newSpeed)

     {

         speedInKPH = convert(newSpeed);

     }

     public void setSpeedInKPH(double newSpeed)

     {

         speedInKPH = newSpeed;

     }

n         你可以执行任意的附加效果

     public double setSpeed(double newSpeed)

     {

         speed = newSpeed;

         notifyObservers();

     }

n         如果使用你的类的用户直接访问类的字段,那么他们也会担负执行附加效果的责任

法则#2:优先使用(对象)组合,而非(类)继承

[ Favor Composition Over Inheritance ]

组合

n         (对象)组合是一种通过创建一个组合了其它对象的对象,从而获得新功能的复用方法。

n         将功能委托给所组合的一个对象,从而获得新功能。

n         有些时候也称之为“聚合”(aggregation)或“包容”(containment),尽管有些作者对这些术语赋予了专门的含义

n         例如:

F        聚合:一个对象拥有另一个对象或对另一个对象负责(即一个对象包含另一个对象或是另一个对象的一部分),并且聚合对象和其所有者具有相同的生命周期。(译者注:即所谓的“同生共死”关系,可参见GOFDesign Patterns: Elements of Reusable Object-Oriented Software的引言部分。)

F        包容:一种特殊类型的组合,对于其它对象而言,容器中的被包含对象是不可见的,其它对象仅能通过容器对象来访问被包含对象。(Coad

 

n         包含可以通过以下两种方式实现:

F        根据引用(By reference

F        根据值(By value

n         C++允许根据值或引用来实现包含。

n         但是在Java中,一切皆为对象的引用!

组合的优点和缺点

n         优点:

n        

F        容器类仅能通过被包含对象的接口来对其进行访问。

F        “黑盒”复用,因为被包含对象的内部细节对外是不可见。

F        对装性好。

F        实现上的相互依赖性比较小。(译者注:被包含对象与容器对象之间的依赖关系比较少)

F        每一个类只专注于一项任务。

F        通过获取指向其它的具有相同类型的对象引用,可以在运行期间动态地定义(对象的)组合。

 

n         缺点:

F        从而导致系统中的对象过多。

F        为了能将多个不同的对象作为组合块(composition block)来使用,必须仔细地对接口进行定义。

继承

n         (类)继承是一种通过扩展一个已有对象的实现,从而获得新功能的复用方法。

n         泛化类(超类)可以显式地捕获那些公共的属性和方法。

n         特殊类(子类)则通过附加属性和方法来进行实现的扩展。

继承的优点和缺点

n         优点:

F        容易进行新的实现,因为其大多数可继承而来。

F        易于修改或扩展那些被复用的实现。

n         缺点:

F        破坏了封装性,因为这会将父类的实现细节暴露给子类。

F        “白盒”复用,因为父类的内部细节对于子类而言通常是可见的。

F        当父类的实现更改时,子类也不得不会随之更改。

F        从父类继承来的实现将不能在运行期间进行改变。

继承 vs 组合 的示例

n         这个例子来自Effective JavaJoshua Bloch著)

n         假设我们希望一个HashSet变量跟踪试图插入的数量,那么我们采取继承HashSet如下所示:

public class InstrumentedHashSet extends HashSet

{

     // The number of attempted element insertions

     private int addCount = 0;

     public InstrumentedHashSet(Collection c) { super(c); }

     public InstrumentedHashSet(int initCap, float loadFactor)

     {

         super(initCap, loadFactor);

     }

     public boolean add(Object o)

     {

         addCount++;

         return super.add(o);

     }

     public boolean addAll(Collection c)

     {

         addCount += c.size();

         return super.addAll(c);

     }

     public int getAddCount()

     {

         return addCount;

     }

}

 

n         看起来都还不错。但是让我们检验一下!

     public static void main(String[] args)

     {

         InstrumentedHashSet s = new InstrumentedHashSet();

         s.addAll(Arrays.asList(new String[] { "Snap", "Crackle", "Pop" }));

         System.out.println(s.getAddCount());

     }

n         我们得到6这个结果,而不是所期望的3。为什么?

n         因为超类HashSet自身中的addAll()的内部实现调用了add()方法。所以首先我们在InstrumentedHashSet addAll()中给addCount加了3,接着我们调用HashSetaddAll()。对于每个元素,这个addAll()调用add()方法,后者因为被InstrumentedHashSet重写,对每个元素都加上1。结果是每个元素都被二次计数了。

n         有很多办法解决这个问题,但是注意子类的脆弱性。子类的实现细节影响了其上的操作。

n         解决这个问题最好的办法是使用组合。让我们写一个组合了Set对象的InstrumentedSet 类,我们的InstrumentedSet 类将复制Set的接口,但所有的Set操作实际上将被前转到内部的Set对象。

n         InstrumentedSet被称为包装类,因为它包装了一个Set对象的实例。

n         这是一个通过组合代理的例子!

public class InstrumentedSet implements Set

{

     private final Set s;

     private int addCount = 0;

     public InstrumentedSet(Set s) { this.s = s; }

     public boolean add(Object o)

     {

         addCount++;

         return s.add(o);

     }

     public boolean addAll(Collection c)

     {

         addCount += c.size();

         return s.addAll(c);

     }

     public int getAddCount() { return addCount; }

     // Forwarding methods (the rest of the Set interface methods)

     public void clear() { s.clear(); }

     public boolean contains(Object o) { return s.contains(o); }

     public boolean isEmpty() { return s.isEmpty(); }

     public int size() { return s.size(); }

     public Iterator iterator() { return s.iterator(); }

     public boolean remove(Object o) { return s.remove(o); }

     public boolean containsAll(Collection c)

     { return s.containsAll(c); }

     public boolean removeAll(Collection c)

     { return s.removeAll(c); }

     public boolean retainAll(Collection c)

     { return s.retainAll(c); }

     public Object[] toArray() { return s.toArray(); }

     public Object[] toArray(Object[] a) { return s.toArray(a); }

     public boolean equals(Object o) { return s.equals(o); }

     public int hashCode() { return s.hashCode(); }

     public String toString() { return s.toString(); }

n         注意几点:

F        这个类是一个Set

F        它有一个构造器参数为Set

F        被包含的Set对象可以是任何实现了Set接口的类(并不仅仅是HashSet

F        这个类非常灵活,可以包装任何已存在的Set对象

n         举例

List list = new ArrayList();

     Set s1 = new InstrumentedSet(new TreeSet(list));

     int capacity = 7;

     float loadFactor = .66f ;

     Set s2 = new InstrumentedSet(new HashSet(capacity, loadFactor));

Coad规则

仅当下列的所有标准被满足时,方可使用继承:

n         子类表达了“是一个的特殊类型”,而非“是一个由所扮演的角色”。

n         子类的一个实例永远不需要转化(transmute)为其它类的一个对象。

n         子类是对其父类的职责(responsibility)进行扩展,而非重写或废除(nullify)。

n         子类没有对那些仅作为一个工具类(utility class)的功能进行扩展。

n         对于一个位于实际的问题域(Problem Domain)的类而言,其子类特指一种角色(role),交易(transaction)或设备(device)。

继承/组合示例1


图表
1

n         “是一个的特殊类型”,而非“是一个由所扮演的角色”

F        失败。乘客是人所扮演的一种角色。代理人亦然。

n         永远不需要转化

F        失败。随着时间的发展,一个Person的子类实例可能会从Passenger转变成Agent,再到Agent Passenger

n         扩展,而非重写和废除

F        通过。

n         不要扩展一个工具类

F        通过。

n         在问题域内,特指一种角色,交易或设备

F        失败。Person不是一种角色,交易或设备。

继承并非适用于此处!

 

使用组合进行挽救!


图表
2

继承/组合示例2


图表
3

n         “是一个的特殊类型”,而非“是一个由所扮演的角色”

F        通过。乘客和代理人都是特殊类型的人所扮演的角色。

n         永远不需要转化

F        通过。一个Passenger对象将保持不变;Agent对象亦然。

n         扩展,而非重写和废除

F        通过。

n         不要扩展一个工具类

F        通过。

n         在问题域内,特指一种角色,交易或设备

F        通过。PersonRole是一种类型的角色。

继承适用于此处!

继承/组合示例3


图表
4

n         “是一个的特殊类型”,而非“是一个由所扮演的角色”

F        通过。预订和购买都是一种特殊类型的交易。

n         永远不需要转化

F        通过。一个Reservation对象将保持不变;Purchase对象亦然。

n         扩展,而非重写和废除

F        通过。

n         不要扩展一个工具类

F        通过。

n         在问题域内,特指一种角色,交易或设备

F        通过。是一种交易。

继承适用于此处!

继承/组合示例4

图表 5

n         “是一个的特殊类型”,而非“是一个由所扮演的角色”

F        失败。预订不是一种特殊类型的observable

n         永远不需要转化

F        通过。一个Reservation对象将保持不变。

n         扩展,而非重写和废除

F        通过。

n         不要扩展一个工具类

F        失败。Observable就是一个工具类。

n         在问题域内,特指一种角色,交易或设备

F        不适用。Observable是一个工具类,并非一个问题域的类。。

继承并非适用于此处!

继承/组合总结

n         组合与继承都是重要的重用方法

n         OO开发的早期,继承被过度地使用

n         随着时间的发展,我们发现优先使用组合可以获得重用性与简单性更佳的设计

n         当然可以通过继承,以扩充(enlarge)可用的组合类集(the set of composable classes)。

n         因此组合与继承可以一起工作

n         但是我们的基本法则是:

优先使用对象组合,而非(类)继承
[ Favor Composition Over Inheritance ]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java面向对象程序设计第三版耿祥义第一章主要介绍了Java的基础知识和面向对象的概念。 1. Java语言的特点 Java语言是一种面向对象编程语言,具有以下特点: - 简单易学:Java语言的语法类似C++,但是去掉了C++中比较难理解的特性,使得Java更加容易学习和使用。 - 面向对象Java语言是一种纯面向对象编程语言,所有的程序都是由对象组成的。 - 平台无关性:Java语言可以在不同的操作系统和硬件平台上运行,只需要安装相应的Java虚拟机即可。 - 安全性:Java语言的安全性非常高,可以在不信任的环境下运行程序,避免了一些安全漏洞。 - 高性能:Java语言的运行速度比较快,且可以通过各种优化技术来提高性能。 2. 面向对象的概念 面向对象是一种软件设计的思想,其核心是将问题看作是由对象组成的。对象是指具有一定属性和行为的实体,属性是对象的特征,行为是对象的动作。 在面向对象的设计中,需要考虑以下几个方面: - 类的设计:类是创建对象的模板,需要定义类的属性和方法。 - 对象的创建:创建对象时,需要使用new关键字来调用类的构造方法。 - 对象的访问:访问对象的属性和方法时,需要使用点号操作符来进行访问。 - 继承和多态:继承是指一个类可以继承另一个类的属性和方法,多态是指同一种行为可以用不同的方式实现。 3. Java的基础知识 Java语言的基础知识包括数据类型、运算符、流程控制语句等。 - 数据类型:Java语言的数据类型包括基本数据类型和引用数据类型。基本数据类型包括整型、浮点型、字符型和布尔型,引用数据类型包括类、接口、数组等。 - 运算符:Java语言的运算符包括算术运算符、关系运算符、逻辑运算符、位运算符等。 - 流程控制语句:Java语言的流程控制语句包括if语句、switch语句、for循环、while循环、do-while循环等。 4. Java程序的基本结构 Java程序的基本结构包括类的定义、方法的定义和语句块的定义。 - 类的定义:类是Java程序的基本组成单元,需要使用class关键字来定义类。 - 方法的定义:方法是类中的一个函数,用于实现特定的功能,需要使用方法名、参数列表和返回值类型来定义方法。 - 语句块的定义:语句块是一组语句的集合,需要使用大括号来定义语句块。 总的来说,Java面向对象程序设计第三版耿祥义第一章介绍了Java语言的基础知识和面向对象的概念,为后续的学习打下了基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值