Programming for reuse
黑盒复用:源代码不可见,不能修改
Behavioral subtyping
Preconditions cannot be strengthened in a subtype. 前置条件不能强化
Covariance (协变)
父类型子类型:越来越具体specific 返回值类型:不变或变得更具体 异常的类型:也是如此。
Contravariance (反协变、逆变)
父类型子类型:越来越具体specific 参数类型:要相反的变化,要不变或越来 越抽象
Consider LSP for generics 泛型中的LSP
Box<Integer> is not a subtype of Box<Number> even though Integer is a subtype of Number
Given two concrete types A and B (for example, Number and Integer), MyClass<A> has no relationship to MyClass<B>, regardless of whether or not A and B are related. The common parent of MyClass<A> and MyClass<B> is Object
1. 无边界的通配符(Unbounded Wildcards), 就是<?>, 比如List<?>.
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.
2. 固定上边界的通配符(Upper Bounded Wildcards):
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据. 要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界. 注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类.
3. 固定下边界的通配符(Lower Bounded Wildcards):
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据. 要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界. 注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界.
List<Number> is a subtype of List<? extends Object>
List<Object> is a subtype of List<? super String>
Interface Comparator<T>
Interface Comparable<T>
与使用Comparator的区别:不需要构建新的Comparator类,比较代 码放在ADT内部。
Delegation and Composition
委派的几种类型归纳
Use (A use B)
Composition/aggregation (A owns B)
Association (A has B
Dependency(依赖): 临时性的delegation
- 在这种关系中,一个类使用另一个类而不将其作为一个属性。
- 两类之间的这种关系称为“uses-a”关系。例如,它可以是一个参数,或者在一个方法中本地使用,参考下面的代码:
首先新建一个课程类:
public class Course {}
- 1
在课表类总使用课程类:
public class CourseSchedule {
List<Course> courses = new ArrayList<>();
public void add (Course c) {
courses.add(c);
}
public void remove (Course c) {
courses.remove(c);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在这里,并没有将Course类作为CourseSchedule类的属性来使用,而是作为迭代器中的元素和方法中的参数来使用。
Association(关联): 永久性的delegation
- 关联是类之间的持久关系,允许一个对象实例让另一个对象实例代表它自己做其他事。
- 这种关系属于has-a的关系,是结构化的,因为它指定了一种对象与另一种对象相连接,并且不代表行为,即不在该类的方法中使用另一个类的方法,只是简单的将不同的对象连接起来。
下面展示一下关联的代码:
class Teacher {
private Student [] students;
}
- 1
- 2
- 3
class Student {
private Teacher teacher;
Course[] selectedCourses;
}
- 1
- 2
- 3
- 4
可以看到,在两个类中(Student和Teacher)互相都有彼此的实例,而且没有使用继承,就直接将这几个不同的类相连接,这就是利用了Association方式。
Composition: 更强的delegation
- 组合是将简单对象或数据类型组合成更复杂的方法的一种方法。
- 这种关系是a-part-of关系,一个类有另一个属性或实例变量——实现了一个对象包含另一个对象。
let`s show 代码:
class Heart {}
- 1
class Person {
private Heart heart = new Heart();
public void operation () {
heart.operation();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 这种方式理解起来就很简单了,直接在该类中实例化一个其他类,然后该调用方法调用方法,对这个实例想怎么用怎么用,十分灵活。
- 不过需要注意的是:
- 这里的实例是private的,也就是说,外界访问不到,这样的话,更改其值只能在该方法中;而且每次创建该类的对象时,就已经创建好这个类中的实例;也就是说一旦创建好该类的对象,其中的属性指向便已经创建好。
Aggregation
- 聚集:对象存在于另一个之外,是在外部创建的,所以它作为一个参数传递给构造函数。
这种关系是has-a的关系,区别于
让我们看一个这个例子的代码:
class Student {}
- 1
class Course {
private Student[] students;
public addStudent (Student s) {
studtents.append(s);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
可以看到,在这里,内部的属性是可以在外部指定的,而不是完全依赖该类。
Composition vs. Aggregation
组合和聚集是最常用的两种delegation方式,可以说,其中的使用包括了依赖和关联方式,并在其上做了进一步的扩展。二者很相似,但又有很多不同之处,这里举一个例子看一下二者的最大不同之处:
public class WebServer {
private HttpListener listener;
private RequestProcessor processor;
public WebServer(HttpListener listener, RequestProcessor processor) {
this.listener = listener;
this.processor = processor;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
public class WebServer {
private HttpListener listener;
private RequestProcessor processor;
public WebServer() {
this.listener = new HttpListener(80);
this.processor = new RequestProcessor(“/www/root”);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以看到,第一个代码是聚集的方式,第二个代码是组合的方式,这是为什么呢?
在组合中,当拥有的对象被销毁时,所包含的对象也是如此。
比如:
一所大学拥有不同的部门,每个部门都有许多教授,因为是大学创建了这些部门,所以如果大学关闭,这些部门将不复存在,但这些部门的教授将继续存在。但是在聚集中,这并不一定是正确的:
大学可以被看作是部门的组成,而院系则是教授的集合。一个教授可以在一个以上的部门工作,但是一个部门不能成为一所大学的一部分,部门是在大学外创建的,故大学倒闭,部门还在。
合成(Composition)和聚合(Aggregation)都是关联(Association)的特殊种类。聚合表示整体和部分的关系,表示"拥有";合成则是一种更强的"拥有",部分和整体的生命周期一样。合成的新的对象完全支配其组成部分,包括它们的创建和销毁等。一个合成关系的成分对象是不能与另一个合成关系共享的。
在面向对象设计中,有两种基本的办法可以实现复用:
第一种是通过合成/聚合,即合成复用原则,含义是指,尽量使用合成/聚合,而不是使用继承。
第二种就是通过继承。
要正确地选择合成/复用和继承的方法是,只有当以下的条件全部被满足时,才应当使用继承关系:
子类是超类的一个特殊种类,而不是超类的一个角色,也就是区分"Has-A"和"Is-A"。只有"Is-A"关系才符合继承关系,"Has-A"关系应当用聚合来描述。
永远不会出现需要将子类换成另外一个类的子类的情况。如果不能肯定将来是否会变成另外一个子类的话,就不要使用继承。
子类具有扩展超类的责任,而不是具有置换掉(override)或注销掉(Nullify)超类的责任。如果一个子类需要大量的置换掉超类的行为,那么这个类就不应该是这个超类的子类。
只有在分类学角度上有意义时,才可以使用继承。不要从工具类继承。
错误的使用继承而不是合成/聚合的一个常见原因是错误地把"Has-A"当成了"Is-A"。"Is-A"代表一个类是另外一个类的一种;"Has-A"代表一个类是另外一个类的一个角色,而不是另外一个类的特殊种类。
白盒框架的原理与实现
白盒框架是基于面向对象的继承机制。之所以说是白盒框架,是因为在这种框架中,父类的方法对子类而言是可见的。子类可以通过继承或重写父类的方法来实现更具体的方法。
虽然层次结构比较清晰,但是这种方式也有其局限性,父类中的方法子类一定拥有,要么继承,要么重写,不可能存在子类中不存在的方法而在父类中存在。
软件构造课程中有关白盒框架的例子:
public abstract class PrintOnScreen {
public void print() {
JFrame frame = new JFrame();
JOptionPane.showMessageDialog(frame, textToShow());
frame.dispose();
}
protected abstract String textToShow();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
public class MyApplication extends PrintOnScreen {
@Override protected String textToShow() {
return "printing this text on " + "screen using PrintOnScreen " + "white Box Framework";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
黑盒框架的原理与实现
黑盒框架时基于委派的组合方式,是不同对象之间的组合。之所以是黑盒,是因为不用去管对象中的方法是如何实现的,只需关心对象上拥有的方法。
这种方式较白盒框架更为灵活,因为可以在运行时动态地传入不同对象,实现不同对象间的动态组合;而继承机制在静态编译时就已经确定好。
黑盒框架与白盒框架之间可以相互转换,具体例子可以看一下,软件构造课程中有关黑盒框架的例子,更改上面的白盒框架为黑盒框架:
public interface TextToShow {
String text();
}
- 1
- 2
- 3
public class MyTextToShow implements TextToShow {
@Override
public String text() {
return "Printing";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
public final class PrintOnScreen {
TextToShow textToShow;
public PrintOnScreen(TextToShow tx) {
this.textToShow = tx;
}
public void print() {
JFrame frame = new JFrame();
JOptionPane.showMessageDialog(frame, textToShow.text());
frame.dispose();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
猜测下面代码的结果:
PrintOnScreen m = new PrintOnScreen(new MyTextToShow());
m.print();
二者对比
白盒框架利用subclassing:
- 允许扩展每一个非私有方法
- 需要理解父类的实现
- 一次只进行一次扩展
- 通常被认为是开发者框架
黑盒框架使用委派中的组合composition:
- 允许在接口中对public方法扩展
- 只需要理解接口
- 通常提供更多的模块
- 通常被认为是终端用户框架,平台
Design Patterns for Reuse
Adapter(适配器模式)
通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。
Decorator (装饰器模式)
2、ConcreteComponent 是Component的子类,实现了相应的方法,它充当了“被装饰者”的角色。
3、Decorator 也是Component的子类,它是装饰者共同实现的抽象类(也可以是接口)。比如说,Decorator代表衣服这一类装饰者,那么它的子类应该是T恤、裙子这样的具体的装饰者。
4、ConcreteDecorator 是Decorator的子类,是具体的装饰者,由于它同时也是Component的子类,因此它能方便地拓展Component的状态(比如添加新的方法)。每个装饰者都应该有一个实例变量用以保存某个Component的引用,这也是利用了组合的特性。在持有Component的引用后,由于其自身也是Component的子类,那么,相当于ConcreteDecorator包裹了Component,不但有Component的特性,同时自身也可以有别的特性,也就是所谓的 装饰 。
Step 1、创建Component基类
因为总体对象是人,所以我们可以把人抽象为基类,新建Person.java:
public abstract class Person {
String description = "Unkonwn";
public String getDescription()
{
return description;
}
public abstract double cost(); //子类应该实现的方法
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Step 2、创建被装饰者——ConcreteComponent
客户分为很多种,有儿童、青少年、成年人等,因此我们可以创建不同的被装饰者,这里我们创建青少年的被装饰者,新建Teenager.java:
public class Teenager extends Person {
public Teenager() {
description = "Shopping List:";
}
@Override
public double cost() {
//什么都没买,不用钱
return 0;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
Step 3、创建Decorator
由于不同的部位有不同的衣物,不能混为一谈,比如说,衣服、帽子、鞋子等,那么这里我们创建的Decorator为衣服和帽子,分别新建ClothingDecorator.java和HatDecorator.java:
public abstract class ClothingDecorator extends Person {
public abstract String getDescription();
}
- 1
- 2
- 3
- 4
public abstract class HatDecorator extends Person {
public abstract String getDescription();
}
- 1
- 2
- 3
- 4
- 5
Step 4、创建ConcreteDecorator
上面既然已经创建了两种Decorator,那么我们基于它们进行拓展,创建出不同的装饰者,对于Clothing,我们新建Shirt.java,对于Hat,我们新建Casquette,其实可以根据不同类型的衣物创建更多不同的装饰者,这里只是作为演示而创建了两种。代码如下所示:
public class Shirt extends ClothingDecorator {
//用实例变量保存Person的引用
Person person;
public Shirt(Person person)
{
this.person = person;
}
@Override
public String getDescription() {
return person.getDescription() + "a shirt ";
}
@Override
public double cost() {
return 100 + person.cost(); //实现了cost()方法,并调用了person的cost()方法,目的是获得所有累加值
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
public class Casquette extends HatDecorator {
Person person;
public Casquette(Person person) {
this.person = person;
}
@Override
public String getDescription() {
return person.getDescription() + "a casquette "; //鸭舌帽
}
@Override
public double cost() {
return 75 + person.cost();
}
}
最后我们在测试类内测试我们的代码:
public class Shopping {
public static void main(String[] args) {
Person person = new Teenager();
person = new Shirt(person);
person = new Casquette(person);
System.out.println(person.getDescription() + " ¥ " +person.cost());
}
}
Facade(外观模式)
提供一个统一的接口来取代一系列小接口调用,相当于对复杂 系统做了一个封装,简化客户端使用。 客户端需要通过一个简化的接口来访问复杂系统内的功能。
Strategy(策略设计模式)
Template Method(模板设计模式)
使用继承和重写实现模板模式。
Iterator