[关闭]
@lasdtc 2013-12-24T07:29:11.000000Z 字数 9883 阅读 1115

设计模式

分类

创建型模式 结构型模式 行为型模式 其它
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 并发型模式和线程池模式。

结构型模式概要图

结构型模式

行为型模式概要图

行为型模式

六大原则

  1. 开闭原则(Open Close Principle)
    开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
  2. 里氏代换原则(Liskov Substitution Principle)
    里氏代换原则面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
  3. 依赖倒转原则(Dependence Inversion Principle)
    这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
  4. 接口隔离原则(Interface Segregation Principle)
    这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
  5. 迪米特法则(最少知道原则)(Demeter Principle)
    一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
  6. 合成复用原则(Composite Reuse Principle)
    原则是尽量使用合成/聚合的方式,而不是使用继承。

5种创建型模式

工厂方法模式(Factory Method)

应用场景:需要根据用户的指定建立一个类时; 出现了大量的产品需要创建,并且具有共同的接口时。

  1. public class SendFactory {
  2. public Sender produce(String type) {
  3. if ("mail".equals(type)) {
  4. return new MailSender();
  5. } else if ("sms".equals(type)) {
  6. return new SmsSender();
  7. } else {
  8. System.out.println("请输入正确的类型!");
  9. return null;
  10. }
  11. }
  12. }
  1. public class SendFactory {
  2. public Sender produceMail(){
  3. return new MailSender();
  4. }
  5. public Sender produceSms(){
  6. return new SmsSender();
  7. }
  8. }
  1. public class SendFactory {
  2. public static Sender produceMail(){
  3. return new MailSender();
  4. }
  5. public static Sender produceSms(){
  6. return new SmsSender();
  7. }
  8. }

抽象工厂模式(Abstract Factory)

应用场景:需要增加工厂类而不想改动原有工厂类的代码时。 抽象工厂模式

单例模式(Singleton)

应用场景:保证在一个JVM中,该对象只有一个实例存在。

  1. public class Singleton {
  2. /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载。按我的理解,延迟加载主要是防止第一次初始化之后失败就再也不能初始化了。(静态变量只被初始化一次) */
  3. private static Singleton instance = null;
  4. /* 私有构造方法,防止被实例化 */
  5. private Singleton() {
  6. }
  7. /* 静态工程方法,创建实例,为保证多线程安全,加synchronized关键字,为保证性能,只有在第一次创建对象的时候需要加锁 */
  8. public static Singleton getInstance() {
  9. if (instance == null) {
  10. synchronized (instance) {
  11. if (instance == null) {
  12. instance = new Singleton();
  13. }
  14. }
  15. }
  16. return instance;
  17. }
  18. /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
  19. public Object readResolve() {
  20. return instance;
  21. }
  22. }

上面的方法有个缺陷。在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间(空白内存),然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这时,有可能在初始化singleton实例之前,CPU交给了另一个线程。从而导致在另一个线程中,instance只是一个空白的实例,并没有初始化,报错。改进代码:

  1. public class Singleton {
  2. /* 私有构造方法,防止被实例化 */
  3. private Singleton() {
  4. }
  5. /* 此处使用一个内部类来维护单例 */
  6. private static class SingletonFactory {
  7. private static Singleton instance = new Singleton();
  8. }
  9. /* 获取实例 */
  10. public static Singleton getInstance() {
  11. return SingletonFactory.instance;
  12. }
  13. /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
  14. public Object readResolve() {
  15. return getInstance();
  16. }
  17. }

在上面的代码中,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。
但是这个方法也不完美,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。

建造者模式(Builder)

应用场景:需要一个对象来管理具有同一接口的不同类的集合。
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。

  1. public class Builder {
  2. private List<Sender> list = new ArrayList<Sender>();
  3. public void produceMailSender(int count){
  4. for(int i=0; i<count; i++){
  5. list.add(new MailSender());
  6. }
  7. }
  8. public void produceSmsSender(int count){
  9. for(int i=0; i<count; i++){
  10. list.add(new SmsSender());
  11. }
  12. }
  13. }

原型模式(Prototype)

应用场景:对象从另一个对象复制而来。
该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
代码为深浅复制的例子。

  1. /* 浅复制 */
  2. public Object clone() throws CloneNotSupportedException {
  3. Prototype proto = (Prototype) super.clone();
  4. return proto;
  5. }
  6. /* 深复制 */
  7. public Object deepClone() throws IOException, ClassNotFoundException {
  8. /* 写入当前对象的二进制流 */
  9. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  10. ObjectOutputStream oos = new ObjectOutputStream(bos);
  11. oos.writeObject(this);
  12. /* 读出二进制流产生的新对象 */
  13. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  14. ObjectInputStream ois = new ObjectInputStream(bis);
  15. return ois.readObject();
  16. }

7种结构型模式

适配器模式(Adapter)

应用场景:消除由于接口不匹配所造成的类的兼容性问题。
将某个类的接口转换成客户端期望的另一个接口表示。

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

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

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

装饰模式(Decorator)

应用场景:动态的为一个对象增加功能,而且还能动态撤销。
装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
装饰模式

  1. public class Decorator implements Sourceable {
  2. private Sourceable source;
  3. public Decorator(Sourceable source){
  4. super();
  5. this.source = source;
  6. }
  7. @Override
  8. public void method() {
  9. System.out.println("before decorator!");
  10. source.method();
  11. System.out.println("after decorator!");
  12. }
  13. }

代理模式(Proxy)

应用场景:与装饰模式相同,只是更局限化了。
代理模式就是多一个代理类出来,替原对象进行一些操作,类似我们在租房子的时候去找的中介。
代理模式与装饰模式类似,只是代理类中聚合的不是接口类,而是实现了接口的实体类。
代理模式

  1. public class Proxy implements Sourceable {
  2. private Source source;
  3. public Proxy(){
  4. super();
  5. this.source = new Source();
  6. }
  7. @Override
  8. public void method() {
  9. before();
  10. source.method();
  11. atfer();
  12. }
  13. private void atfer() {
  14. System.out.println("after proxy!");
  15. }
  16. private void before() {
  17. System.out.println("before proxy!");
  18. }
  19. }

外观模式(Facade)

应用场景:用一个facade类管理其他类,从而保证了只使用一个类与“外界 ”打交道,提高了封装性。另外,也降低了(facade类中的)类与类之间的耦合度。
外观模式

  1. public class Computer {
  2. private CPU cpu;
  3. private Memory memory;
  4. private Disk disk;
  5. public Computer(){
  6. cpu = new CPU();
  7. memory = new Memory();
  8. disk = new Disk();
  9. }
  10. public void startup(){
  11. System.out.println("start the computer!");
  12. cpu.startup();
  13. memory.startup();
  14. disk.startup();
  15. System.out.println("start computer finished!");
  16. }
  17. public void shutdown(){
  18. System.out.println("begin to close the computer!");
  19. cpu.shutdown();
  20. memory.shutdown();
  21. disk.shutdown();
  22. System.out.println("computer closed!");
  23. }
  24. }

如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样Computer类作为facade类就起到了解耦的作用。

桥接模式(Bridge)

(所谓桥接,也就是类似与双掷开关一样的东东)
应用场景:一个类需要切换所使用类的类型。
桥接模式就是把事物和其具体实现分开,也就是将抽象化与实现化解耦,使得二者可以独立变化。
桥接模式

  1. public abstract class Bridge {
  2. private Sourceable source;
  3. public void method(){
  4. source.method();
  5. }
  6. public Sourceable getSource() {
  7. return source;
  8. }
  9. public void setSource(Sourceable source) {
  10. this.source = source;
  11. }
  12. }
  13. //==============================================
  14. public class MyBridge extends Bridge {
  15. public void method(){
  16. getSource().method();
  17. }
  18. }
  19. //==============================================
  20. public class BridgeTest {
  21. public static void main(String[] args) {
  22. Bridge bridge = new MyBridge();
  23. /*调用第一个对象*/
  24. Sourceable source1 = new SourceSub1();
  25. bridge.setSource(source1);
  26. bridge.method();
  27. /*调用第二个对象*/
  28. Sourceable source2 = new SourceSub2();
  29. bridge.setSource(source2);
  30. bridge.method();
  31. }
  32. }

我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图:
JDBC桥类图

组合模式(Composite)

应用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
组合模式

享元模式(Flyweight)

应用场景:用共享池管理共享的资源(主要是控制资源,即相关类的实例,个数)。

由于资源的创建(即类的实例化)是需要消耗“大量”系统资源的,因此用资源池可以降低系统开销。

享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。 享元模式
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。
一提到共享池,我们很容易联想到Java里面的JDBC连接池。
JDBC连接池类图

11种行为型模式

策略模式(strategy)

应用场景:策略模式多用在算法决策系统中,系统封装好算法,外部用户只需要决定用哪个算法即可。
简单理解就是设计一个接口,提供统一的方法,然后多个实现类实现该接口。
策略模式

吐槽:不就是单纯地使用接口嘛= =。

模板方法模式(Template Method)

应用场景:有一个方法,其中的某一些步骤需要根据子类的类型来决定。于是把这些步骤抽出来成为一个抽象方法,放到子类中实现。
一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法。这样调用抽象类的主方法时,对应子类的方法会被调用。
模板方法模式

  1. public abstract class AbstractCalculator {
  2. /*主方法,实现对本类其它方法的调用*/
  3. public final int calculate(String exp,String opt){
  4. int array[] = split(exp,opt);
  5. return calculate(array[0],array[1]);
  6. }
  7. /*被子类重写的方法*/
  8. abstract public int calculate(int num1,int num2);
  9. public int[] split(String exp,String opt){
  10. String array[] = exp.split(opt);
  11. int arrayInt[] = new int[2];
  12. arrayInt[0] = Integer.parseInt(array[0]);
  13. arrayInt[1] = Integer.parseInt(array[1]);
  14. return arrayInt;
  15. }
  16. }
  17. //==============================================
  18. public class Plus extends AbstractCalculator {
  19. @Override
  20. public int calculate(int num1,int num2) {
  21. return num1 + num2;
  22. }
  23. }
  24. //==============================================
  25. public class StrategyTest {
  26. public static void main(String[] args) {
  27. String exp = "8+8";
  28. AbstractCalculator cal = new Plus();
  29. int result = cal.calculate(exp, "\\+");
  30. System.out.println(result);
  31. }
  32. }

吐槽:与strategy模式一样,都是用来指定不同类实现同一方法。不同的是,一个是让类实现某一接口来实现指定的方法,另一个是让类继承一个抽象类来实现其指定的抽象方法。

观察者模式(Observer)

应用场景:用一个对象控制多个其它对象的更新。
当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化。对象之间是一种一对多的关系。
观察者模式
其实就实现上来说,Observer类才是被控制的类。一个目标物件(MySubject)管理所有相依于它的观察者物件(Observer),并且在它(MySubject)本身的状态改变时主动发出通知(notifyObserver),(调用Observer的update方法)更新观察者物件状态。

迭代器模式(Iterator)

应用场景:用于顺序访问集合中的对象。
这个跟平常用的迭代器是一个东西。简而言之(我的理解)就是额外用一个类去管理一个集合类的顺序访问。
迭代器模式

责任链模式(Chain of Responsibility)

应用场景:在隐瞒客户端的情况下,对系统进行动态的调整。 有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求。
链接上一个对象可以有多个对象的引用(List),因此责任链可以是树状的,但是,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
责任链模式 实例。公司中请假时,需要经由上级审批。假设,如果你的请假时间小于0.5天,那么只需要向项目经理打声招呼就OK了。如果超过了0.5天,但是还小于2天,那么就要去找人事部处理,当然,这就要扣工资了。如果超过了2天,你就需要去找总经理了,工资当然也玩完了。这种例子就可以依据责任链模式设计。请假的请求可以按责任链一级一级上传。

命令模式(Command)

应用场景:将“行为请求者”与“行为实现者”解耦。
在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式。
命令模式
Invoker是调用者(行为请求者),Receiver是被调用者(行为实现者),MyCommand是命令,实现了Command接口,持有接收对象。
Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想。

备忘录模式(Memento)

应用场景:保存一个对象的某个状态,以便在适当的时候恢复对象。
假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。
备忘录模式

状态模式(State)

应用场景: 当控制一个对象状态的条件表达式过于复杂时,把状态的判断逻辑转移到另一个类(状态类)中,从而把复杂的判断逻辑简化。 当对象的状态改变时,同时改变其所在类的行为。 状态模式 State类是个状态类,Context类可以实现切换。Context类的method方法中,依据state对象的状态(value)来决定执行method1还是method2。

访问者模式(Visitor)

应用场景:适用于数据结构相对稳定算法又易变化的系统。
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
访问者模式
可联想 Java 中Collection和Array类的静态方法。

中介者模式(Mediator)

应用场景:各个对象之间的交互操作非常多;每个对象的行为操作都依赖彼此对方,修改一个对象的行为,同时会涉及到修改很多其他对象的行为。
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
对象改变时会影响其它与之关联的对象,为了不使对象显式引用其它对象,使用mediator类来执行改变。mediator类拥有所有对象。
中介者模式
吐槽:图中work和allwork方法并不能直观表达对象之间有交互操作。

解释器模式(Interpreter)

应用场景:主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 解释器模式

自我理解

所有模式的核心思想都是减少依赖,降低耦合度。在不改变原代码的基础上扩充代码。

原文

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注