首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件开发 >

设计方式备忘 - 结构型

2012-10-09 
设计模式备忘 - 结构型适配器模式(Adapter)将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得

设计模式备忘 - 结构型
适配器模式(Adapter)

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。在《设计模式》一书中将适配器模式分为类适配器模式和对象适配器模式。区别仅在于适配器角色对于被适配角色的适配是通过继承完成的还是通过组合来完成的。由于在Java中不支持多重继承,而且继承有破坏封装之嫌,故在此使用组合来代替继承。该模式包含以下组成部分:

目标(Target)角色:定义Client使用的接口。被适配(Adaptee)角色:这个角色有一个已存在并使用了的接口,而这个接口是需要我们适配的。适配器(Adapter)角色:这是适配器模式的核心。它将被适配角色已有的接口转换为目标角色希望的接口。

以下我们对TextCircle类做适配,使之能满足客户端的Circle接口。

class Circle extends Shape {    //这里引用了TextCircle    private TextCircle tc;    public Circle() {        tc = new TextCircle(); //初始化    }    void public display() {        tc.displayIt(); //在规定的方法里面调用 TextCircle 原来的方法    }}

?

该模式与代理模式的主要区别在于代理模式应用的情况是不改变接口命名的,而且是对已有接口功能的一种控制。而适配器模式则强调接口转换。

?

题外话:在Java中有一种叫做“缺省适配模式”的应用,它和我们所讲的适配器模式是完全的两种东西。缺省适配模式是为一个接口提供缺省的实现,这样子类型就可以从缺省适配模式中进行扩展 ,避免了从原有接口中扩展时要实现一些自己不关心的接口。

?

桥梁模式(Bridge)

GOF 在《设计模式》中给桥梁模式的定义为:将抽象部分与它的实现部分分离,使它们都可以独立地变化。这里的抽象部分和实现部分不是我们通常认为的父类与子类、接口与实现类的关系,而是组合关系。也就是说,实现部分是被抽象部分调用,以用来完成抽象部分的功能。

?

简单的说,你可以分析变化的种类,将不变的框架使用抽象类定义出来,然后再将变化的内容使用具体的子类来分别实现。
同时,如果在具体实现中出现了几乎重复的功能代码,则将这些行为提取出来,也采用接口的方式提供出来,然后以组合的方式将服务提供给原来的子类。这样就达到了抽象部分和实现部分独立的变化,而且还达到了实现部分的重用。该模式由以下角色组成:

抽象(Abstraction)角色:它定义了抽象类的接口而且维护着一个指向实现(Implementor)角色的引用。精确抽象(RefinedAbstraction)角色:实现并扩充由抽象角色定义的接口。实现(Implementor)角色:给出了实现类的接口,这里的接口与抽象角色中的接口可以不一致。具体实现(ConcreteImplementor)角色:给出了实现角色定义接口的具体实现。

以下代码来自《Thinking in Patterns with Java》:

//抽象部分的抽象角色class Abstraction {    //维护着一个指向实现(Implementor)角色的引用    private Implementation implementation;    public Abstraction(Implementation imp) {        implementation = imp;    }    //下面定义了抽象部分应该有的接口    public void service1() {        //使用了实现部分已有的接口        //组合实现功能        implementation.facility1();        implementation.facility2();    }    public void service2() {        implementation.facility2();        implementation.facility3();    }}//抽象部分的精确抽象角色class ClientService1 extends Abstraction {    public ClientService1(Implementation imp) { super(imp); }    //使用抽象角色提供的方法组合起来完成某项功能    //这就是为什么叫精确抽象角色(修正抽象角色)    public void serviceA() {        service1();        service2();    }    public void serviceB() {        service3();    }}//实现部分的实现角色interface Implementation {    //这个接口只是定义了一定的接口    void facility1();    void facility2();    void facility3();    void facility4();}//具体实现角色就是要将实现角色提供的接口实现并完成一定的功能class Implementation1 implements Implementation {? ? //这里省略了    ......

?

使用场景

    当你的系统中有多个地方要使用到类似的行为,或者是多个类似行为的组合时,可以考虑使用桥梁模式来提高重用,并减少因为行为的差异而产生的子类。系统中某个类的行为可能会有几种不同的变化趋势,为了有效的将变化封装,可以考虑将类的行为抽取出来。当然上面的情况也可以是这样,行为可能要被不同相似类使用,也可以考虑使用桥梁模式来实现。
组合模式(Composite)

将对象以树形结构组织起来,以达成“部分 - 整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。从定义中可以得到使用组合模式的环境为:在设计中想表示对象的“部分 - 整体”层次结构,希望用户忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。其组成包括:

抽象构件角色(Component):它为组合中的对象声明接口,也可以为共有接口实现缺省行为。树叶构件角色(Leaf):在组合中表示叶节点对象 —— 没有子节点,实现抽象构件角色声明的接口。树枝构件角色(Composite):在组合中表示分支节点对象 —— 有子节点,实现抽象构件角色声明的接口,存储子部件。

具体实例可参照JUnit的TestCase和TestSuite实现。

?

装饰模式(Decorator)

装饰模式也叫包装器模式(Wrapper),GOF 在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。其组成包括:

抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。

以下以JUnit为例,采用装饰模式对TestCase进行扩展:

//这个就是抽象构件角色public interface Test {    public abstract int countTestCases();    public abstract void run(TestResult result);}//具体构件对象,但是这里是个抽象类public abstract class TestCase extends Assert implements Test {    ......    public int countTestCases() {        return 1;    }    ......    public TestResult run() {        TestResult result = createResult();        run(result);        return result;    }    public void run(TestResult result) {        result.run(this);    }    ......}//装饰角色public class TestDecorator extends Assert implements Test {    //这里按照上面的要求,保留了一个对构件对象的实例    protected Test fTest;    public TestDecorator(Test test) {        fTest= test;    }    public void basicRun(TestResult result) {        fTest.run(result);    }    public int countTestCases() {        return fTest.countTestCases();    }    public void run(TestResult result) {        basicRun(result);    }}//具体装饰角色,这个类的增强作用就是可以设置测试类的执行次数public class RepeatedTest extends TestDecorator {    private int fTimesRepeat;    public RepeatedTest(Test test, int repeat) {        super(test);        if (repeat < 0)            throw new IllegalArgumentException("Repetition count must be > 0");        fTimesRepeat= repeat;    }    //看看怎么装饰的吧    public int countTestCases() {        return super.countTestCases() * fTimesRepeat;    }    public void run(TestResult result) {        for (int i= 0; i < fTimesRepeat; i++) {            if (result.shouldStop()) break;            super.run(result);        }    }}//使用的时候,就可以采用下面的方式TestDecorator test = new RepeatedTest(new TestXXX() , 3);

?

题外话:在 java.io 中,并不是纯装饰模式的范例,它是装饰模式、适配器模式的混合使用。

?

门面模式(Facade)

门面模式又称外观模式。GOF在《设计模式》一书中给出如下定义:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。该模式由三个角色组成:

门面角色(facade):这是门面模式的核心。它被客户角色调用,因此它熟悉子系统的功能。它内部根据客户角色已有的需求预定了几种功能组合。子系统角色:实现了子系统的功能。对它而言,facade角色就和客户角色一样是未知的,它没有任何facade角色的信息和链接。客户角色:调用facade角色来完成要得到的功能。使用环境:
    要为一个复杂子系统提供一个简单接口时。客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。当你需要构建一个层次结构的子系统时,使用facade 模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade 进行通讯,从而简化了它们之间的依赖关系。
该模式的一个典型应用就是进行数据库连接。一般我们在每一次对数据库进行访问,都要进行以下操作:
    先得到connect 实例然后打开connect 获得连接,得到一个statement执行sql 语句进行查询,得到查询结果集。
我们可以将这些步骤提取出来,封装在一个类里面。这样,每次执行数据库访问只需要将必要的参数传递到这个类中就可以了。
享元模式(Flyweight)

采用一个共享类来避免大量拥有相同内容的“小类”的开销。这种开销中最常见、直观的影响就是增加了内存的损耗。享元模式以共享的方式高效的支持大量的细粒度对象,减少其带来的开销。


事物之间都是不同的,但是又存在一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以说就是不可行的。因此我们应该尽量将事物的共性共享,而又保留它的个性。为了做到这点,享元模式中区分了内部状态(intrinsic)和外部状态(extrinsic)。内部状态就是共性,外部状态就是个性了。内部状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的。外部状态是不可以共享的,它随环境的改变而改变的,因此外部状态是由客户端来保持。在每个具体的环境下,客户端将外部状态传递给享元,从而创建不同的对象出来。
单纯享元模式抽象享元角色:为具体享元角色规定了必须实现的方法,而外部状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。具体享元角色:实现抽象角色规定的方法。如果存在内部状态,就负责为内部状态提供存储空间。享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外部状态。复合享元模式抽象享元角色:为具体享元角色规定了必须实现的方法,而外部状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。具体享元角色:实现抽象角色规定的方法。如果存在内部状态,就负责为内部状态提供存储空间。复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外部状态。复合享元模式采用了组合模式——为了将具体享元角色和复合享元角色同等对待和处理。这也就决定了复合享元角色中所包含的每个单纯享元都具有相同的外部状态,而这些单纯享元的内部状态可以是不同的。
Java中最长见的享元模式就是String,在Java中,会维护一个String Pool,对于一些可以共享的字串物件,会先在String Pool中查找是否存在相同的String内容(字元相同),如果有就直接传回,而不是直接创造一个新的String物件,以减少记忆体的耗用。
题外话:外部状态维护与内部状态之间的对应关系,在查找时,GOF书中所使用的是BTree结构,由于查找必须花费时间,所以这也指出了使用Flyweight模式所必须付出的代价:以时间换取空间。如何设计外部状态的资料结构,以使得查找时间缩短,这是另一个重要的课题。
代理模式(Proxy)为其他对象提供一种代理以控制对这个对象的访问。根据《Java 与模式》书中对代理模式的分类,代理模式分为8 种,这里将几种常见的、重要的列举如下:远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。比如:你可以将一个在世界某个角落一台机器通过代理假象成你局域网中的一部分。虚拟(Virtual)代理:根据需要将一个资源消耗很大或者比较复杂的对象延迟的真正需要时才创建。比如:如果一个很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,这个大图片可能就影响了文档的阅读,这时需要做个图片Proxy来代替真正的图片。保护(Protect or Access)代理:控制对一个对象的访问权限。比如:在论坛中,不同的身份登陆,拥有的权限是不同的,使用代理模式可以控制权限(当然,使用别的方式也可以实现)。智能引用(Smart Reference)代理:提供比对目标对象额外的服务。比如:纪录访问的流量(这是个再简单不过的例子),提供一些友情提示等等。代理模式中的“代理商”要想实现代理任务,就必须和被代理的“厂商”使用共同的接口,于是代理模式就有三个角色组成了:抽象主题角色:声明了真实主题和代理主题的共同接口。代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相同的接口。真实主题角色:定义真实的对象。以下以论坛中已注册用户和游客的权限不同来作为例子。
//首先我们先实现一个抽象主题角色MyForumpublic interface MyForum {    public void AddFile();}//真实的主题角色public class RealMyForum implements MyForum {    public void AddFile() {        //实现具体方法        ......    }}//代理主题角色public class MyForumProxy implements MyForum {    private RealMyForum forum = new RealMyForum();    private int permission; //权限值    public MyForumProxy(int permission) {        this.permission = permission;    }    //实现的接口    public void AddFile() {        //满足权限设置的时候才能够执行操作        //Constants 是一个常量类        if (Constants.ASSOCIATOR == permission) {            forum.AddFile();        } else            System.out.println("You are not a associator of MyForum!");        }    }}
?

?

热点排行