结构型模式,顾名思义讨论的是类和对象的结构,它采用继承机制来组合接口或实现,或者通过组合一些对象,从而实现新的功能。GoF23种设计模式中的结构型模式有7种,分别是适配器模式(Adapter)、装饰器模式(Decorator)、代理模式(Proxy)、外观模式(Facade)、桥接模式(Bridge)、组合模式(Composite)、享元模式(Flyweight)。
其中对象的适配器模式是各种模式的起源,是一种比较重要的适配器模式。这7种模式的作用不同,分别如下。
适配器模式(Adapter):将某个类的接口转换成客户端期望的另一个接口表示。适配器模式可以消除由于接口不匹配所造成的类兼容性问题。
根据适配对象的不同,又分为3种类型:
类的适配器模式:用于对类进行适配。
对象的适配器模式:用于对对象进行包装。
接口的适配器模式:用于对接口抽象化。
装饰器模式(Decorator):向某个对象动态地添加更多的功能。装饰器模式是除类继承外另一种扩展功能的方法。
代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。
外观模式(Facade):为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
桥接模式(Bridge):将一个抽象与实现解耦,以便两者可以独立的变化。
组合模式(Composite):把多个对象组成树状结构来表示局部与整体,这样用户可以一样地对待单个对象和对象的组合。
享元模式(Flyweight):通过共享以便有效地支持大量小颗粒对象。
接下来将对7种常用结构型模式进行比较,并通过经典的代表实例来说明该如何使用各种结构型模式:
适配器模式—Iterator适配器(对象的适配器模式)、Enumeration适配器(对象的适配器模式)、AWT事件适配器(接口的适配器模式)、I/O字节流到字符流的适配器(对象的适配器模式)。
装饰器模式—I/O输入/输出流管道的装饰器模式、Sitemesh装饰器。
代理模式—Java动态代理机制。
桥接模式—JDBC桥DriverManager。
组合模式—AWT容器Container。
享元模式—数据库连接池。
--------------------------------------------------------------------------------------------------------------------------
下面来看详细的内容。
适配器模式(Adapter)
下面从以下几个方面来详细讲解适配器模式。
适配器模式的核心思想。
第一种:类的适配器模式(对类进行适配)。
第二种:对象的适配器模式(对对象进行包装)。
第三种:接口的适配器模式(对接口抽象化)。
何时使用适配器模式。
Java中的应用—Iterator适配器(对象的适配器模式)。
Java中的应用—Enumeration适配器(对象的适配器模式)。
Java中的应用—AWT事件适配器(接口的适配器模式)。
Java中的应用—I/O字节流到字符流的适配器(对象的适配器模式)。
适配器模式的核心思想
适配器模式的核心思想:把原有的接口转变成调用者期待的接口,从而使不同接口的类可以一起工作。
适配器中包含如下3个角色。
源角色Adaptee:需要适配的目标类或接口。
目标角色Target:所期望得到的接口。
适配器角色Adapter:适配器类是本模式的核心,用来把源接口转换成目标接口,显然这一角色不可以是接口,而必须是具体类。
这3者角色之间的交互关系便组成了适配器模式的模型,
Adaptee类只有operation()方法,没有newoperation()方法,但是客户端又需要目标类同时拥有这两个方法,这时便可以新建一个接口Target,并提供一个中间环节Adapter类,Adapter类实现了Target接口,并继承自Adaptee,Adapter类的operation()方法重新封装了Adapter类的operation()方法,并同时实现了newoperation()方法,这便实现了适配的目的。
适配器也叫做包装器模式(Wrapper),根据适配对象的不同,又可以将适配器模式分为3种子类型。
类的适配器模式:用于对类进行适配。
对象的适配器模式:用于对对象进行包装。
接口的适配器模式:用于对接口抽象化。
下面分别通过实例的形式来展示这3种模式的使用。
第一种:类的适配器模式(对类进行适配)
第一种模式是类的适配器模式,它用来对目标类进行包装,如图12-3所示。
Source类是具体的原始类,是待适配的对象,它拥有一个函数operation1()。
Targetable是要适配的目标接口,它拥有一个与Source同样的接口函数operation1(),并提供了一个新的接口函数operation2(),该函数是要扩展的功能。
Adapter是适配器类,它必须继承Source类,并实现Targetable接口,从而将Source的功能扩展到Targetable接口所具有的功能。
适配后的Source类,即可以通过调用Targetable接口来实现对Source类的操作,
下面来看具体的实现。
(1)Source类的源代码如程序12-1所示,其默认的操作函数operation1()用来输出一个字符串。
源类Source.java
- package structure.adapter;
-
- public class Source {
- public void operation1() {
- System.out.println("原始类的方法");
- }
- }
(2)Targetable必须首先具备与Source相同的函数接口,这样它才能够实现Source功能,然后增加扩展函数operation2()。
目标接口Targetable.java
- package structure.adapter;
-
- public interface Targetable {
- public void operation1();
- public void operation2();
- }
(3)Adapter的作用是将Source适配为Targetable,因此它必须继承Source类并实现Targetable接口,这样它便拥有了Source函数operation1()的功能,该函数还拥有了与Targetable同样的接口;同时,它必须实现Targetable的扩展函数,它往控制台输出了一个字符串。
适配器类Adapter.java
- package structure.adapter;
-
- public class Adapter extends Source implements Targetable {
-
- public void operation2() {
- System.out.println("适配目标类后的方法");
- }
- }
(4)我们就可以创建一个Adapter类的对象,该对象属于Targetable,调用该对象的operation1()函数将会实现对Source函数的调用,调用operation2()函数将会实现对Adatper实现函数的调用,这样就实现了对类Source到Targetable的适配。
测试类AdapterTest.java
- package structure.adapter;
-
- public class AdapterTest {
- public static void main(String[] args) {
- // 创建目标接口的类实例
- Targetable obj = new Adapter();
- // 调用目标类的方法
- obj.operation1();
- obj.operation2();
- }
- }
运行该程序的输出为:
原始类的方法
适配目标类后的方法
这正是我们想要的结果:把类Source按照接口Targetable进行调用。
第二种:对象的适配器模式(对对象进行包装)
第二种模式是对象的适配器模式,它用来对目标对象进行包装,因此又叫做包装器模式,如图12-4所示。
Source类是具体的原始类,是待包装对象的类,它拥有一个函数operation1()。
Targetable是要适配的目标接口,它拥有一个与Source同样的接口函数operation1(),并提供了一个新的接口函数operation2(),该函数是要扩展的功能。
Wrapper是包装器类,它与Adapter适配器不同,它不需要继承Source,但必须拥有一个Source类型的对象source,该对象在构造函数中被赋值。在该包装器的operation1()函数中,需要调用source的operation1(),这样就实现了对原有对象的调用;同时扩展实现Targetable的operation2()函数。
包装后的Wrapper类,即可以通过调用Targetable接口来实现对Source类的操作,以上的Source类和Targetable接口与第一种模式相同,唯一不同的是Wrapper的实现方式不同。
包装器类Wrapper.java
- package structure.adapter;
-
- public class Wrapper implements Targetable {
- private Source source;
-
- public Wrapper(Source source) {
- super();
- this.source = source;
- }
-
- public void operation1() {
- source.operation1();
- }
-
- public void operation2() {
- System.out.println("包装目标类后的方法");
- }
- }
创建一个Source类的对象source,并根据该对象创建一个Wrapper包装器obj,该包装器对象属于Targetable,调用该对象的operation1()函数将会实现对Source函数的调用,调用operation2()函数将会实现对Wrapper实现函数的调用,这样就实现了对类Source到Targetable的包装。
测试类WrapperTest.java
- package structure.adapter;
-
- public class WrapperTest {
- public static void main(String[] args) {
- // 创建源类对象
- Source source = new Source();
-
- // 创建source的包装类对象
- Targetable obj = new Wrapper(source);
-
- // 调用目标类的方法
- obj.operation1();
- obj.operation2();
- }
- }
运行该程序的输出为:
原始类的方法
包装目标类后的方法
输出的结果与第一种模式相同,因此效果是与第一种模式相同的,不同的是适配的方式不同。
第三种:接口的适配器模式(对接口抽象化)
有时我们会在一个接口中定义多个接口方法,如果要实现该接口编写一个类,就必须为每一个接口方法编写实现代码,这显然会造成很大的浪费。为了解决这个问题,可以使用第三种适配器模式—默认适配器。它会为原有的接口类实现一个默认的抽象类,在该抽象类中编写每一个接口的默认实现,当我们需要编写一个具体类时,只需要继承自该抽象类,而不需要实现原有的接口。并且,此时我们不需要实现所有的接口方法,只实现需要的函数即可。
如图12-5所示为接口的适配器模式。
Sourceable是定义了多个接口函数的接口类。
DefaultWrapper是一个抽象类,它实现了接口Sourcable,并为每一个接口函数提供了默认的实现。
依据DefaultWrapper就可以编写不同的实现,在实现中只需要重写部分待实现的函数,而不需要重写全部。
下面来看具体的实现。
(1)Sourcable类的源代码如程序12-7所示,它定义了两个操作函数。
源接口Sourcable.java
- package structure.adapter;
-
- public interface Sourcable {
- public void operation1();
- public void operation2();
- }
(2)DefaultWrapper实现了Sourcable接口,并提供了其两个接口函数的实现,在该实现中可以什么也不做,目的只是为了给其子类提供一个默认的实现。
默认适配器类DefaultWrapper.java
- package structure.adapter;
-
- public abstract class DefaultWrapper implements Sourcable {
- public void operation1(){}
- public void operation2(){}
- }
(3)SourceSub1继承自DefaultWrapper,由于DefaultWrapper的屏蔽作用,SourceSub1可以只重新实现自己关心的函数operation1(),它负责输出一个字符串。
源接口的实现子类SourceSub1.java
- package structure.adapter;
-
- public class SourceSub1 extends DefaultWrapper {
- public void operation1() {
- System.out.println("源接口的一个实现子类Sub1");
- }
- }
(4)SourceSub2继承自DefaultWrapper,由于DefaultWrapper的屏蔽作用,SourceSub2可以只重新实现自己关心的函数operation2(),它负责输出一个字符串。
源接口的实现子类SourceSub2.java
- package structure.adapter;
-
- public class SourceSub2 extends DefaultWrapper {
- public void operation2() {
- System.out.println("源接口的一个实现子类Sub2");
- }
- }
以上我们编写了两个实现SourceSub1和SourceSub2,下面分别创建一个对象,创建的对象都属于Sourcable。然后分别调用它们的方法operation1()和operation2()。
测试类DefaultWrapperTest.java
- package structure.adapter;
- public class DefaultWrapperTest {
- public static void main(String[] args) {
- Sourcable source1 = new SourceSub1();
- Sourcable source2 = new SourceSub2();
- source1.operation1();
- source1.operation2();
- source2.operation1();
- source2.operation2();
- }
- }
运行该程序的结果如下:
源接口的一个实现子类Sub1
源接口的一个实现子类Sub2
从输出的结果可以看出,source1和source2仅仅在运行自身实现的函数时发生了作用,对于没有实现的函数则调用了默认适配器DefaultWrapper的默认函数,什么也没有输出。
何时使用适配器模式
从以上的讲解我们已经知道,如果需要将一个类变成另一个类时就可以使用适配器模式。但是根据需求的不同,可以分别选用3种不同的子模式。
类的适配器模式:当希望将一个类转换成满足另一个接口时,可以模仿Adapter的做法来构造一个新的适配器类,该类继承原有的类并实现新的接口即可。
对象的适配器模式:当希望将一个对象转换成另一个接口时,可以模仿Wrapper的做法来构造一个新的包装类,该类调用原有的类并实现新的接口即可。
默认适配器模式:当不希望实现一个接口的所有方法时,可以模仿DefaultWrapper的做法构造一个抽象类,给出所有方法的默认的实现,这样,从这个抽象类再继承下去的子类就不必实现所有的方法了。