结构模式
结构模式描述如何将类或者对象结合在一起形成更大的结构。可以分为类的结构模式与对象的结构模式两种。
类的结构模式:使用继承来把类、接口组合在一起,以形成更大的结构。典型例子是适配器模式。
对象的结构模式:描述怎样把各种不同的类型的对象组合在一起,以实现新的功能的方法。对象结构模式是动态的。典型例子有合成模式、装饰模式、对象适配器模式。
适配(Adapter)器模式
把一个类的接口变的成客户端所期待的另一种接口,从而使用原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
GoF:将一个类的接口转换成客户希望的另外一个接口。此模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
它有两种形式:类适配器、对象适配器。
类适配器模式结构
目标接口,这就是所期待得到的接口,现用系统就是构建在这个接口之上的。
publicinterfaceTarget {
//需要的方法
voidrequest();
}
虽然Adaptee类已经具有Target所要的功能,但是Adaptee中的提供的接口specificRequest不是Target中所提供的接口request,现在如果要使Adaptee来实现Target,则需要提供一个适配器,因为Adaptee是你不能修改的类,它是第三所提供的。
//源接口,其所拥有的方法需要适配
publicclassAdaptee {
//需要适配的方法
publicvoidspecificRequest() {
}
}
将上面Adaptee转换成符合Target接口的实现类,让Adaptee实现可以用在Target所使用的地方。它在继承Adaptee的同时又扩展了Target接口,将已有的但并不符合目标接口实现转换成符合目标接口的实现。
//适配器,模式的核心
publicclassAdapter extends Adaptee implements Target {
//适配后的方法
publicvoidrequest() {
this.specificRequest();//调用已有的实现
}
}
对象适配器结构
与类适配器模式一样,对象适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
适配器,这里不是直接继承直接源接口,而是采用组合。其他的类与类适配器模式没有区别。
publicclassAdapter implements Target {
//持有的待适配对象
privateAdaptee _adaptee;
publicAdapter(Adaptee adaptee) {
_adaptee = adaptee;
}
publicvoidrequest() {//适配后的方法
_adaptee.specificRequest();//通过委派调用现有实现
}
}
最后需要的是,适配器模式的用意是将接口不同而功能相同或者“相近”的两个接口加以转换,这里面包括适配器角色可补充一个源角色没有的方法。上面的源与目标接口的功能是完全一样的,如果目标接口中要的功能接口比源要多的话,我们可以在适配源的基本之上或以补充源没有的功能,这是允许的,不要误以为源与目标接口要一一对应才能使用适配器。换言这,适配器允许转换之外还可以补充,而且,对象适配器还可以同时适配多个源,而类适配器是不易做到的。
从Iterator到Enumeration的适配
如果使用JDK中新的Iterator功能来替换掉老系统中Enumeration,则需要将Iterator适配成Enumeration。
public class Itermeration implements Enumeration{//适配器
Iterator it;
public Itermeration(Iterator it){//可将指定的Iterator转换为Enumeration
this.it = it;
}
public boolean hasMoreElements(){
return it.hasNext();
}
public Object nextElement() throws NoSuchElementException{
return it.next();
}
}
从Enumeration到Iterator的适配
反过来,可以将Enumeration到Iterator,即使用旧的迭代器功能完成新的迭代接口,好比新瓶装老洒的感觉。
public class Enuterator implements Iterator{
Enumeration enum;
public Enuterator(Enumeration enum) {
this.enum = enum;
}
public boolean hasNext(){
return enum.hasMoreElements();
}
public Object next() throws NoSuchElementException{
return enum.nextElement();
}
public void remove(){
throw new UnsupportedOperationException();
}
}
缺省适配(Default Adapter)器模式
缺省适配模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。
在很多情况下,必须让一个具体类实现某一个接口,但是这个类又用不到接口所规定的所有方法。通常的处理方法是,这个具体类要实现所有的方法,那些有用的方法要有实现,那些没有用的方法也要有空的、平庸的实现。
比如java.awt.event.WindowAdapter(WindowAdapter实现了WindowListener,而WindowListener又继承自EventListener)就是一接收窗口事件的抽象适配器类,此类中的所有方法为空。此类存在的目的是方便用户从它扩展重写自己感兴趣的事件方法。
模式结构
public interface AbstractService{
void serviceOperation1();
int serviceOperation2();
String serviceOperation3();
}
缺省适配器,提供平庸的实现,用户可以从它进行扩展,只需提供自己感兴趣的方法实现。
public abstract class ServiceAdapter implements AbstractService{
public void serviceOperation1(){ }
public int serviceOperation2(){ return 0; }
public String serviceOperation3(){ return null; }
}
与适配器模式区别
适配器模式的用意是要改变源的接口,以便与目标类接口兼容。而缺省适配器的用意是为了方便建立一个不平庸的适配器类而提供的一种平庸实现,此类应当是抽象类。
J2EE中的缺省适配模式
合成(Composite)模式
合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。可以使客户端将单纯元素与复合元素同等看待。
根据在哪里提供子对象的管理方法,合成模式可要成安全方式和透明方式。
透明方式
在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等地对待所有的对象。这就是透明形式的合成模式。
缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,但是在编译时期不会出错,而只会在运行时期才会出错。
安全方式
节点管理方法从Component移到了Composite类里面。这样的做法 是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。编译通不过,就不会出现运行时期错误。
这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。
安全方式的合成模式结构
publicinterfaceComponent {//抽象构件
//返回自身:如果是复合节点返回自身,如果是叶子节点返回null
Composite getComposite();
//简单的业务
voidsampleOperation();
}
publicclassComposite implements Component {//复合节点
privateVector componentVector = new Vector();
publicComposite getComposite() {
returnthis;//如果是复合节点返回自身
}
publicvoidadd(Component component) {//添加子节点
componentVector.addElement(component);
}
publicvoidremove(Component component) {//删除子节点
componentVector.removeElement(component);
}
publicEnumeration getChild() {//返回所有孩子节点
returncomponentVector.elements();
}
//客户端不应当直接调用树叶类,应当由其父类向树叶类进行委派,
//这样可以增加代码的复用性
publicvoidsampleOperation() {//调用所节点上的业务方法
Enumeration enumeration = getChild();
while(enumeration.hasMoreElements()) {
((Component) enumeration.nextElement()).sampleOperation();
}
}
}
publicclassLeaf implements Component {//叶子节点
publicComposite getComposite(){
returnnull;//如果是叶子节点返回null
}
publicvoidsampleOperation(){
System.out.println("Leaf");
}
}
publicclassClient {//场景
publicstaticvoidmain(String[] args) {
Component c = new Composite();
c.getComposite().add(new Leaf());
c.getComposite().add(new Leaf());
Component c2 = new Composite();
c2.getComposite().add(new Leaf());
c.getComposite().add(c2);
c.sampleOperation();
}
}
其实我们还可以在每个节点上定义一个父节点的引用,这样可以搜索出子节点的父节点,从而在遍历时可以回溯。
透明方式的合成模式结构
publicinterfaceComponent {
voidsampleOperation();
Composite getComposite();
voidadd(Component component);
voidremove(Component component);
Enumeration components();
}
publicclassComposite implements Component {
privateVector componentVector = new Vector();
publicvoidadd(Component component) {
componentVector.addElement(component);
}
publicvoidremove(Component component) {
componentVector.removeElement(component);
}
publicEnumeration components() {
returncomponentVector.elements();
}
publicComposite getComposite() {
returnthis;
}
publicvoidsampleOperation() {
java.util.Enumeration enumeration = components();
while(enumeration.hasMoreElements()) {
((Component) enumeration.nextElement()).sampleOperation();
}
}
}
publicclassLeaf implements Component {
publicvoidsampleOperation() {
System.out.println("Leaf");
}
publicvoidadd(Component component) {}
publicvoidremove(Component component) {}
publicComposite getComposite() {
returnnull;
}
publicEnumeration components() {
returnnull;
}
}
AWT库中的例子
AWT与Swing图形界面构件是建立在AWT库中的Container类和Component类上的,从下面的AWT合成模式类图可以看出,Button和CheckBox是树的叶子节点,而Container则是复合节点。在Container类中有操作集合的对应方法,而Component类中则没有这样的方法,AWT是安全形式的合成模式。
应用场景
需要描述对象的部分和整体的等级结构。
希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
装饰(Decorator)模式
装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
GoF:动态地给一个对象添加一些额外的职责。
模式结构
//一个可以扩展功能的对象接口
publicinterfaceComponent {
//可扩展的某种操作
publicvoidoperation();
}
//实现基本功能的具体类,即将要接受附加职责的类,它是构造修饰链的起点
publicclassConcreteComponent implements Component {
publicvoidoperation() {
System.err.println("ConcreteComponent");
}
}
//装饰者,持有一个Component对象的实例
publicabstractclassDecorator implements Component {
//被修饰的Component对象
privateComponent comp;
publicDecorator(Component myC) {
comp = myC;
}
publicvoidoperation() {
if(comp != null)
comp.operation();
}
}
//具体修饰者,给被修饰对象加上A的职责
publicclassConcreteDecoratorA extends Decorator {
publicConcreteDecoratorA(Component comp) {
super(comp);
}
publicvoidoperation() {
//添加额外操作
System.out.println("ConcreteDecoratorA");
//再运动修饰对象本身的操作
super.operation();
}
}
//具体修饰者,给被修饰对象加上B的职责
publicclassConcreteDecoratorB extends Decorator {
publicConcreteDecoratorB(Component comp) {
super(comp);
}
publicvoidoperation() {
//添加额外操作
System.out.println("ConcreteDecoratorB");
//再运动修饰对象本身的操作
super.operation();
}
}
publicclassClient {//场景
publicstaticvoidmain(String[] args) {
//最里面的是具体的基本功能类,并作为修饰链的起点
Component comp = new ConcreteDecoratorA(new ConcreteDecoratorB(
newConcreteComponent()));
comp.operation();
}
}
注意:对象链总是以ConcreteComponent对象结束。
应用场景
允许扩展一个对象的功能,而不必借助于继承。
动态地给一个对象添加一些额外的职责,这些功能可以再动态地撤销。
需要增加由一些基本功能的排列组合而产生的非常大的功能,而使继承关系变得不现实。
半透明的装饰模式
透明的(理想的)装饰模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。但是,装饰模式有透明的和半透明的两种。这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。
如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰模式在本书也是可以接受的,称为“半透明”的装饰模式,如下图所示。
I/O中的设计模式
Java I/O库的两个对称性
输出—输入对称:比如InputStream和OutputStream各自占据Byte流的输入与输出的两个平行的等级结构的根部;而Reader和Writer各自占据Char流的输入与输出的两个平行的等级结构的根部。
byte-char对称:InputStream与Reader的子类分别负责Byte和Char流的输入;OutputStream与Writer的子类分别负责Byte和Char流的输出,它们分别形成平行的等级结构。
Java I/O库的两个设计模式
Java I/O库的总体设计是符合装饰模式和适配器模式的。
适配器模式应用到了原始流处理器的设计上面,构成了I/O库所有流处理器的起点。
I/O中的装饰模式抽象结构图:
为什么要在Java I/O库中使用装饰模式
由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量的类出现。而如果采用装饰模式,那么类的数目就会人大减少。
Java I/O库是由—些基本的原始流处理器和围绕它们的装饰流处理器所组成。
从byte流到char流的适配
InputStreamReader(InputStream in)是从byte输入流到char输入流的一个适配器。OutputStreamWriter(OutputStream out)是从OutputStream到Writer的一个适配器。
InputStrearn
根据输入流的源的类型,可以将这些流类分成两种,即原始流和链接流。
原始流处理器
ByteArrayInputStream(byte[] buf)FileInputStream(File file)PipedInputStream(PipedOutputStream src)StringBufferInputStream(String s)
注,已过时,已经由StringReader所代替。
链接流处理器
FilterlnputStream:称为过滤输入流,它将另一个输入流作为流源。这个类的子类包括以下几种:BufferedInputStream(InputStream in)、DataInputStream(InputStream in)、LineNumberInputStream(InputStream in)、PushbackInputStream(InputStream in)。ObjectlnputStream可以将使用ObjectOutputStream串行化的原始数据类型和对象重新并行化。SeqcueneInputStream可以将两个已有的输入流连接起来,形成一个输入流,从而将多个输入流排列构成—个输入流序列。
值得指出的是,虽然PipedlnputStream接收一个流对象PipedOuputStream作为流的源,但是,PipedOutputStream流对象的类型不是InputStream。因此,PipedInputStream流处理器仍然属于原始流处理器。
适配器模式
ByleArraylnpuCStream将—个byte数组的接口适配成InputStream流处理器的接口。FileInputStream将PileDescdptor对象适配成InputStream类型的对象形式的适配器模式。StringBufferInputStream将String对象适配成InputStream类型的对象形式的适配器模式。
装饰模式各角色
链接流其实就是装饰角色,而原始流就是具体构件角色,如下图所示(底色为灰色的表示是链接流):
OutputStrearn
原始流处理器
ByteArrayOutputStream(int size)FileOutputStream(File file)PipedOutputStream(PipedInputStream snk)
链接流处理器
FilterOutputStream:称为过滤输出流,它将另一个输出流作为流汇,这个类的子类有如下几种:BufferedOutputStream(OutputStream out)、DataOutputStream(OutputStream out)、PrintStream(OutputStream out)。ObjectOutputStream:可以将原始数据类型和对象串行化。
适配器模式
ByteArrayOutputStream把一个byte数组的接口适配成OutputStream类型的接口。FileOutputStream将File接口适配成OutputStream接口的对象形式的适配器模式。PipedOutputStream接收一个类型为PipedlnputStream的输入类型,并将之转换成类型为OutputStream类型的输出流。
装饰模式各角色
Reader
原始流处理器
CharArrayReader(char[] buf)InputStreamReader(InputStream in)PipedReader(PipedWriter src)StringReader(String s)
链接流处理器
链接流处理器包括以下的几种:BufferedReader(Reader in)、FilterReader(Reader in)
适配器模式
CharArrayReader将—个char数组适配成为Reader类型的输入流。StringReader将String的接口适配成Reader类型的接口。
装饰模式各角色
Writer
原始流处理器
CharArrayWriter(int initialSize)OutputStreamWriter(OutputStream out)PipedWriter(PipedReader snk)StringWriter(int initialSize)
链接流处理器
BufferedWriter(Writer out)FilterWriter(Writer out)PrintWriter(File file)
适配器模式
CharArrayWriter将—个char数组适配成为Writer接口。PipedWriter将一个PipedReader对象的接口适配成一个Writer类型的接口。StringWriter将StringBuffer对象的接口适配成为了Writer类型的接口。
装饰模式各角色
由于抽象装饰角色与具体装饰角色发生合并,因此装饰模式在这里被简化了。
I/O中的适配器类
InputStreamReader:将InputStream适配成Reader类,可以指定编码方式OutputStreamWriter:将OutputStream适配成Writer类,可以指定编码方式PrintWriter:将OutputStream适配成Writer类。还可以以File、Writer来构造。
从I/O来看,也只有InputStreamReader、OutputStreamWriter转换流才涉及到编码方式。
附:涉及到编码的流
InputStreamReader(InputStream in, String charsetName)OutputStreamWriter(OutputStream out, String charsetName)PrintStream(OutputStream out, boolean autoFlush, String encoding)PrintWriter(String fileName, String csn)