软件开发设计模式之二(结构型)

接上篇创建型的模式,现在来看看结构型模式,共七种:适配器模式、外观模式、桥接模式、装饰器模式、代理模式、享元模式、组合模式。


适配器模式(Adapter)

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能一起工作。适配器模式主要分为三类:

  • 类的适配器模式
  • 对象的适配器模式
  • 接口的适配器模式
类的适配器模式

类的适配器模式核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。

/**
 * 以类适配器模式实现
 */

public class Source {
    public void method1() {
        System.out.println("this is original method!");
    }
}


public interface Targetable {
    /* 与原类中的方法相同 */
    public void method1();

    /* 新类的方法 */
    public void method2();
}

public class Adapter extends Source implements Targetable {
    public void method2() {
        System.out.println("this is the targetable method!");
    }
}

public class AdapterTest {
    public static void main(String[] args) {
        Adapter adapter = new Adapter();
        adapter.method1();
        adapter.method2();
    }
}

对象的适配器模式

对象的适配器模式的基本思路和类的适配器模式相同,只是将Adapter类作修改成Wrapper,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。

/**
 * 以对象适配器模式实现
 */

public class Source {
    public void method1() {
        System.out.println("this is original method!");
    }
}


public interface Targetable {
    /* 与原类中的方法相同 */
    public void method1();

    /* 新类的方法 */
    public void method2();
}


public class Wrapper implements Targetable {

    private Source source;

    public Wrapper(Source source) {
        super();
        this.source = source;
    }

    @Override
    public void method2() {
        System.out.println("this is the targetable method!");
    }

    @Override
    public void method1() {
        source.method1();
    }
}

public class AdapterTest {
    public static void main(String[] args) {
        Source source = new Source();
        Wrapper target = new Wrapper(source);
        target.method1();
        target.method2();
    }
}

运行结果跟类的适配器模式例子的一样。

接口的适配器模式

接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行了。

适配器模式的优缺点:

  • 优点:更好的复用性:系统需使用现有的类,而此类的接口不符系统需求,则通过适配器模式可让这些功能得到更好的复用。更好的扩展性。

  • 缺点:若可对系统重构,尽可能不使用适配器,过多使用适配器,容易让系统凌乱,不易整体把握。


外观模式(Facade)

外观模式要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次接口,使得子系统更易于使用。

使用场景:为一个复杂的子系统提供一个简单接口。对于系统进行定制、修改,这种易变性,使得隐藏子系统的具体实现变得尤为重要,对外隐藏子系统的具体实现,隔离变化。

public class CPU {

    public void startup() {
        System.out.println("cpu startup!");
    }

    public void shutdown() {
        System.out.println("cpu shutdown!");
    }
}


public class Disk {

    public void startup() {
        System.out.println("disk startup!");
    }

    public void shutdown() {
        System.out.println("disk shutdown!");
    }
}


public class Memory {

    public void startup() {
        System.out.println("memory startup!");
    }

    public void shutdown() {
        System.out.println("memory shutdown!");
    }
}


public class Computer {

    private CPU cpu;
    private Memory memory;
    private Disk disk;

    public Computer() {
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }

    public void startup() {
        System.out.println("start the computer!");
        cpu.startup();
        memory.startup();
        disk.startup();
        System.out.println("start computer finished!");
    }

    public void shutdown() {
        System.out.println("begin to close the computer!");
        cpu.shutdown();
        memory.shutdown();
        disk.shutdown();
        System.out.println("computer closed!");
    }
}

package com.model.structure;

public class User {

    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.startup();
        computer.shutdown();
    }
}

外观模式的精髓在于封装。通过一高层次结构为用户提供统一的 API 入口,使得用户通过一个类就基本能够操作整个系统。

外观模式的优缺点

  • 优点:对客户端隐藏子系统细节,因而减少客户对于子系统的耦合。外观类对子系统的接口封装,使得系统更易于使用。
  • 缺点:外观类没有遵循开闭原则,当业务出现变更时,可能需要直接修改外观类。

桥接模式(Bridge)

在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。

使用场景:

  • 对于不希望使用继承或因多层次继承导致系统类的个数急剧增加的系统,考虑使用桥接模式。
  • 需要在构件的抽象化角色和具体角色之间增加更多灵活性,避免两层次间建立静态的继承关系,可通过桥接模式使它们在抽象层建立一个关联关系。
  • 一个类存在两个独立变化的维度,且这两个维度都需进行扩展。
  • 任何多维度变化类或多个树状类之间的耦合可通过桥接模式解耦。
// 实现部分的抽象接口
public interface Implementor {
    /**
     * 实现抽象部分的具体方法
     */
    public void operationImpl();
}

// 实现部分具体的实现
public class ConcreteImplementorA implements Implementor {
    @Override
    public void operationImpl() {
        // 忽略实现逻辑
    }
}

// 抽象部分
public abstract class Abstraction {
    // 声明一私有成员变量引用实现部分的对象
    private Implementor mImplementor; 

    /**
     * 通过实现部分对象的引用构造抽象部分的对象
     * @param implementor 实现部分对象的引用
     */
    public Abstraction(Implementor implementor) {
        mImplementor = implementor;
    }

    /**
     * 通过调用实现部分具体的方法实现具体的功能
     */
    public void operation() {
        mImplementor.operationImpl();
    }   
}

// 优化的抽象部分
public class RefinedAbstraction extends Abstraction {
    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }

    /**
     * 对 Abstraction 中的方法进行扩展
     */
    public void refinedOperation() {
        // 忽略实现逻辑
    }
}

// 客户端实现
public class Client {
    public static void main(String[] args) {
        RefinedAbstraction abstration = 
            new RefinedAbstraction( new ConcreteImplementorA );

        abstraction.operation();
        abstraction.refinedOperation();
    }
}
public interface Driver {  
    public void connect();
}

public class MysqlDriver implements Driver {

    @Override
    public void connect() {
        System.out.println("connect mysql done!");
    }
}

public class DB2Driver implements Driver {

    @Override
    public void connect() {
        System.out.println("connect db2 done!");
    }
}

public abstract class DriverManager {

    private Driver driver;

    public void connect() {
        driver.connect();
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver(Driver driver) {
        this.driver = driver;
    }
}

public class MyDriverManager extends DriverManager {

    public void connect() {
        super.connect();
    }

}

public class Client {

    public static void main(String[] args) {

        DriverManager driverManager = new MyDriverManager();
        Driver driver1 = new MysqlDriver();
        driverManager.setDriver(driver1);
        driverManager.connect();

        Driver driver2 = new DB2Driver();
        driverManager.setDriver(driver2);
        driverManager.connect();

    }
}

桥接模式,分离抽象与实现,其优点毋庸置疑,即灵活的扩展以及对客户来说透明的是实现。但不足之处在于运用桥接模式进行设计,是有一定难度的,需多加推敲与研究。


装饰器模式(Decorator)

定义:动态的给对象添加一些额外的责任,就增加功能来说,装饰比生成子类更为灵活。

使用场景:

  • 想要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  • 想要扩展一个类的行为,却做不到。类定义可能被隐藏,无法进行子类化;或者对类的每个行为的扩展,哦支持每种功能组合,将产生大量的子类。

public interface Sourceable { public void method(); } public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); } } public class Decorator implements Sourceable { private Sourceable source; public Decorator(Sourceable source) { super(); this.source = source; } @Override public void method() { System.out.println("before decorator!"); source.method(); System.out.println("after decorator!"); } } public class DecoratorTest { public static void main(String[] args) { //(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。 //(2) 装饰对象包含一个真实对象的引用(reference) //(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。 //(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。 // 在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。 // 继承不能做到这一点,继承的功能是静态的,不能动态增删。 Sourceable source = new Source(); Sourceable obj = new Decorator(source); obj.method(); } }

代理模式(Proxy)

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。
使用场景:

  • 无法或不想直接访问某个对象或访问某对象存在困难
  • 为保证客户端使用的透明性,委托对象与代理对象需实现相同的接口
public interface Sourceable {
    public void method();
}

public class Source implements Sourceable {

    @Override
    public void method() {
        System.out.println("the original method!");
    }
}


public class Proxy implements Sourceable {

    private Source source;

    public Proxy() {
        super();
        this.source = new Source();
    }

    @Override
    public void method() {
        before();
        source.method();
        atfer();
    }

    private void atfer() {
        System.out.println("after proxy!");
    }

    private void before() {
        System.out.println("before proxy!");
    }
}

public class ProxyTest {

    public static void main(String[] args) {
        Sourceable source = new Proxy();
        source.method();
    }
}
  • Sourceable:抽象主题类,该类主要职责是声明真实主题与代理的共同接口方法,其可是抽象类或接口。

  • Source:真实主题类,该类也被称为被委托类或者被代理类,该类定义了代理所表示的真实对象,由其执行具体的业务逻辑方法,而客户类则通过代理类间接地调用真实主题类中定义的方法。

  • Proxy:代理类,该类也称为委托类或者代理类,该类持有一个对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行,以起到代理的作用。

代理模式的优缺点:

  • 优点 - 代理模式可看作一种针对性优化
  • 缺点 - 暂没有明显的缺点

装饰模式与代理模式的区别 (容易混淆)

  • 装饰模式:以对客户端透明的方式 扩展对象的功能,即继承关系的一个替代方案。
  • 代理模式:给一个对象提供一个代理对象,并由 代理对象 来控制对原有对象引用。

享元模式(Flyweight)

享元模式的定义:使用共享对象可有效地支持大量的细粒度的对象。
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。

使用场景:

  • 系统中存在大量的 相似对象。
  • 细粒度的对象都具备较接近的外部状态,且内部状态与环境无关,即对象没有特定身份。
  • 需要 缓冲池 的场景。
public class ConnectionPool {

    private Vector<Connection> pool;

    /* 公有属性 */
    private String url = "adbc:mysql://localhost:3306/test";
    private String username = "root";
    private String password = "root";
    private String driverClassName = "com.adbc.Driver";

    private int poolSize = 100;
    private static ConnectionPool instance = null;
    Connection conn = null;

    /* 构造方法,做一些初始化工作 */
    private ConnectionPool() {
        pool = new Vector<Connection>(poolSize);

        for (int i = 0; i < poolSize; i++) {
            try {
                Class.forName(driverClassName);
                conn = DriverManager.getConnection(url, username, password);
                pool.add(conn);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /* 返回连接到连接池 */
    public synchronized void release() {
        pool.add(conn);
    }

    /* 返回连接池中的一个数据库连接 */
    public synchronized Connection getConnection() {
        if (pool.size() > 0) {
            Connection conn = pool.get(0);
            pool.remove(conn);
            return conn;
        } else {
            return null;
        }
    }
}

通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!

享元模式的优缺点

  • 优点:大幅度地降低内存中对象的数量。
  • 缺点:为了使对象可共享,需将一些状态外部化,使程序的逻辑复杂化;将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

组合模式(Composite)

组合模式的定义:将对象组合成 树状结构 以表示 "部分-整体" 的 层次结构,使得用户对单个对象和组合对象的使用具有一致性。

使用场景

  • 表示对象的 部分-整体 层次结构时。
  • 从一个整体中能够独立出部分模块或功能的场景。

组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 “组合对象” 的含义。

public abstract class Company {

    private String name;

    public Company() {
    }

    public Company(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    protected abstract void add(Company company);

    protected abstract void romove(Company company);

    protected abstract void display(int depth);

}

/* Composite:定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。*/
public class ConcreteCompany extends Company {

    private List<Company> cList;

    public ConcreteCompany() {
        cList = new ArrayList();
    }

    public ConcreteCompany(String name) {
        super(name);
        cList = new ArrayList();
    }

    @Override
    protected void add(Company company) {
        cList.add(company);
    }

    @Override
    protected void display(int depth) {

        StringBuilder sb = new StringBuilder("");
        for (int i = 0; i < depth; i++) {
            sb.append("-");
        }
        System.out.println(new String(sb) + this.getName());
        for (Company c : cList) {
            c.display(depth + 2);
        }
    }

    @Override
    protected void romove(Company company) {
        cList.remove(company);
    }
}

/* Leaf:在组合中表示叶子结点对象,叶子结点没有子结点。*/
public class HRDepartment extends Company {
    public HRDepartment(String name) {
        super(name);
    }

    @Override
    protected void add(Company company) {
    }

    @Override
    protected void display(int depth) {
        StringBuilder sb = new StringBuilder("");
        for (int i = 0; i < depth; i++) {
            sb.append("-");
        }
        System.out.println(new String(sb) + this.getName());
    }

    @Override
    protected void romove(Company company) {
    }
}

public class FinanceDepartment extends Company {
    public FinanceDepartment(String name) {
        super(name);
    }

    @Override
    protected void add(Company company) {
    }

    @Override
    protected void display(int depth) {
        StringBuilder sb = new StringBuilder("");
        for (int i = 0; i < depth; i++) {
            sb.append("-");
        }
        System.out.println(new String(sb) + this.getName());
    }

    @Override
    protected void romove(Company company) {
    }
}

//Client:
public class Client {
    public static void main(String[] args) {
        Company root = new ConcreteCompany();
        root.setName("北京总公司");
        root.add(new HRDepartment("总公司人力资源部"));
        root.add(new FinanceDepartment("总公司财务部"));
        Company shandongCom = new ConcreteCompany("山东分公司");
        shandongCom.add(new HRDepartment("山东分公司人力资源部"));
        shandongCom.add(new FinanceDepartment("山东分公司账务部"));
        Company zaozhuangCom = new ConcreteCompany("枣庄办事处");
        zaozhuangCom.add(new FinanceDepartment("枣庄办事处财务部"));
        zaozhuangCom.add(new HRDepartment("枣庄办事处人力资源部"));
        shandongCom.add(zaozhuangCom);
        Company huadongCom = new ConcreteCompany("上海华东分公司");
        huadongCom.add(new HRDepartment("上海华东分公司人力资源部"));
        huadongCom.add(new FinanceDepartment("上海华东分公司账务部"));
        Company hangzhouCom = new ConcreteCompany("杭州办事处");
        hangzhouCom.add(new FinanceDepartment("杭州办事处财务部"));
        hangzhouCom.add(new HRDepartment("杭州办事处人力资源部"));
        huadongCom.add(hangzhouCom);
        root.add(shandongCom);
        root.add(huadongCom);
        root.display(0);
    }
}

组合模式的优缺点:

  • 优点:清楚定义分层次的复杂对象,表示对象的全部或部分层次,让高层模块忽略了层次差异,方便对整个层次结构进行控制;高层模块可一致地使用一个组合结构或其中单个对象;在组合模式中增加新的枝干结构和叶子构件很方便,无须对类库进行修改;通过叶子对象和枝干对象的递归组合,形成复杂的树形结构,但对其控制却非常简单。
  • 缺点:新增构件时,不好对枝干中的构件类型进行限制,不能依赖类型系统来施加这些约束,因为大多数情况下他们来自相同的抽象层;因此必须进行类型检查来实现。

参考书箱:

  • 《设计模式之禅》

注,本文参考了以下链接的文章,某些地方有结合自己的一些理解加以修改:
https://blog.csdn.net/u013142781/article/details/50821155