结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
一、适配器模式
1、类的适配器模式
类的适配器模式核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。
/** * Source类 */ public class Source { public void method1() { System.out.println("this is original method!"); } } /** * Targetable类 */ public interface Targetable { /* 与原类中的方法相同 */ public void method1(); /* 新类的方法 */ public void method2(); } /** * Adapter类 */ 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) { Targetable target = new Adapter(); target.method1(); target.method2(); } }
2、对象的适配器模式
对象的适配器模式的基本思路和类的适配器模式相同,只是将Adapter类作修改成Wrapper,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
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 static void main(String[] args) { Source source = new Source(); Targetable target = new Wrapper(source); target.method1(); target.method2(); } }
3、接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行了。
二、装饰器模式
装饰模式:在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
装饰模式的特点:
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的引用(reference)
(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。继承不能做到这一点,继承的功能是静态的,不能动态增删。
/** * Sourceable类 */ public interface Sourceable { public void method(); } /** * Source类 */ public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); } } /** * Decorator类 */ 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!"); } } /** * DecoratorTest类 */ 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(); } }
运行结果:
三、代理模式
/** * Sourceable类 */ public interface Sourceable { public void method(); } /** * Source类 */ public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); } } /** * Proxy类 */ 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!"); } } /** * ProxyTest类 */ public class ProxyTest { public static void main(String[] args) { Sourceable source = new Proxy(); source.method(); } }
四、外观模式
外观模式是为了解决类与类之间的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。
我们以一个计算机的启动过程为例,看看如下的代码:
/** * CPU类 */ public class CPU { public void startup() { System.out.println("cpu startup!"); } public void shutdown() { System.out.println("cpu shutdown!"); } } /** * Disk类 */ public class Disk { public void startup() { System.out.println("disk startup!"); } public void shutdown() { System.out.println("disk shutdown!"); } } /** * Memory类 */ public class Memory { public void startup() { System.out.println("memory startup!"); } public void shutdown() { System.out.println("memory shutdown!"); } } /** * Computer类 */ 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!"); } } /** * User类 */ public class User { public static void main(String[] args) { Computer computer = new Computer(); computer.startup(); computer.shutdown(); } }
五、桥接模式
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。
在提出桥梁模式的时候指出,桥梁模式的用意是”将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”。这句话有三个关键词,也就是抽象化、实现化和脱耦。
抽象化:存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待。
实现化:抽象化给出的具体实现,就是实现化。
脱耦:所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。
/** * Driver类 */ public interface Driver { public void connect(); } /** * MysqlDriver类 */ public class MysqlDriver implements Driver { @Override public void connect() { System.out.println("connect mysql done!"); } } /** * DB2Driver类 */ public class DB2Driver implements Driver { @Override public void connect() { System.out.println("connect db2 done!"); } } /** * DriverManager类 */ 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; } } /** * MyDriverManager类 */ public class MyDriverManager extends DriverManager { public void connect() { super.connect(); } } /** * Client类 */ 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(); } }执行结果:
public class MyDriverManager2 extends DriverManager { public void connect() { System.out.println("before connect"); super.connect(); System.out.println("after connect"); } }再将Client代码中的MyDriverManager 改成 MyDriverManager2 ,执行结果如下:
六、组合模式
组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 “组合对象” 的含义。
组合模式让你可以优化处理递归或分级数据结构。
涉及角色:
Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
Leaf:在组合中表示叶子结点对象,叶子结点没有子结点。
Composite:定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。
比如现实中公司内各部门的层级关系,请看代码:
Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
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)等。
import java.util.ArrayList; import java.util.List; 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("枣庄办事处人力资源部")); Company jinanCom = new ConcreteCompany("济南办事处"); jinanCom.add(new FinanceDepartment("济南办事处财务部")); jinanCom.add(new HRDepartment("济南办事处人力资源部")); shandongCom.add(jinanCom); 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("杭州办事处人力资源部")); Company nanjingCom = new ConcreteCompany("南京办事处"); nanjingCom.add(new FinanceDepartment("南京办事处财务部")); nanjingCom.add(new HRDepartment("南京办事处人力资源部")); huadongCom.add(hangzhouCom); huadongCom.add(nanjingCom); root.add(shandongCom); root.add(huadongCom); root.display(0); } }运行结果:
七、享元模式
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
一提到共享池,我们很容易联想到Java里面的JDBC连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。
看下数据库连接池的代码:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Vector; public class ConnectionPool { private Vector<Connection> pool; /* 公有属性 */ private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.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; } } }通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!