一、设计模式的分类 总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
1.1 创建型
Singleton,单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点
Abstract Factory,抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。
Factory Method,工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到了子类。
Builder,建造模式:将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。
Prototype,原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。
1.2 行为型
Iterator,迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
Observer,观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
Template Method,模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法得某些特定步骤。
Command,命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。
State,状态模式:允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。
Strategy,策略模式:定义一系列的算法,把他们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。
China of Responsibility,职责链模式:使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的耦合关系
Mediator,中介者模式:用一个中介对象封装一些列的对象交互。
Visitor,访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。
Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
Memento,备忘录模式:在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
1.3 结构型
Composite,组合模式:将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。
Facade,外观模式:为子系统中的一组接口提供一致的界面,facade提供了一高层接口,这个接口使得子系统更容易使用。
Proxy,代理模式:为其他对象提供一种代理以控制对这个对象的访问Adapter,适配器模式:将一类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
Decrator,装饰模式:动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加灵活。
Bridge,桥模式:将抽象部分与它的实现部分相分离,使他们可以独立的变化。
Flyweight,享元模式
二、设计模式的六大原则 2.1 开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2.1 里氏代换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
2.3 依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
2.4 接口隔离原则(Interface Segregation Principle) 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦 合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出 发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
2.5 迪米特法则(最少知道原则)(Demeter Principle) 为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使 得系统功能模块相对独立。
2.6 合成复用原则(Composite Reuse Principle) 原则是尽量使用合成/聚合的方式,而不是使用继承。
三、工厂方法模式(Factory Method) 工厂方法模式分为三种:
3.1 普通工厂模式 就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
功能:
实现短信发送功能
Sender接口
MailSender实现发送邮箱信息
SMSSender实现发送Sms信息
SendFactory工厂实现分发
3.1.1 Sender接口和实现 1)Sender接口
package com.bamboo.service;public interface Sender { public void Send () ; }
2)MailSender实现
package com.bamboo.service.impl;public class MailSender implements Sender { @Override public void Send () { System.out.println("this is mail sender!" ); } }
3)SmsSender实现
package com.bamboo.service.impl;public class SmsSender implements Sender { @Override public void Send () { System.out.println("this is sms sender!" ); } }
3.1.2 SendFactory工厂 package com.bamboo.service.factory;public class SendFactory { public Sender produce (String type) { if ("mail" .equals(type)) { return new MailSender (); } else if ("sms" .equals(type)) { return new SmsSender (); } else { System.out.println("请输入正确的类型!" ); return null ; } } }
3.1.3 测试 public class Main { public static void main (String[] args) { SendFactory factory = new SendFactory (); Sender sender = factory.produce("sms" ); sender.Send(); } }
3.2 多个工厂方法模式 对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
在Factory工厂进行改造,去除用户输入的功能,直接调用指定构建。
3.2.1 SendFactory改造 public class SendFactory { public Sender produceMail () { return new MailSender (); } public Sender produceSms () { return new SmsSender (); } }
3.2.2 测试 public class Main { public static void main (String[] args) { SendFactory factory = new SendFactory (); Sender sender = factory.produceMail(); sender.Send(); } }
3.3 静态工厂方法模式 将多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
3.3.1 SendFactory改造 public class SendFactory { public static Sender produceMail () { return new MailSender (); } public static Sender produceSms () { return new SmsSender (); } }
3.3.2 测试 public class Main { public static void main (String[] args) { Sender sender = SendFactory.produceMail(); sender.Send(); } }
3.4 小结 总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时 ,可以通过工厂方法模式进行创建。
在以上的三种模式中:
第一种如果传入的字符串有误,不能正确创建对象
第三种相对于第二种,不需要实例化工厂类
所以,大多数情况下,我们会选用第三种——静态工厂方法模式 。
四、抽象工厂模式(Abstract Factory) 工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则 。
所以,从设计角度考虑,有一定的问题,如何解决?
抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
4.1 Sender业务 4.1.1 Sender接口 public interface Sender { public void Send () ; }
4.1.2 MailSender实现 public class MailSender implements Sender { @Override public void Send () { System.out.println("this is mail sender!" ); } }
4.1.3 SmsSender实现 public class SmsSender implements Sender { @Override public void Send () { System.out.println("this is sms sender!" ); } }
4.2 SendFactory抽象工厂 4.2.1 Provider接口 package com.bamboo.factory;public interface Provider { public Sender produce () ; }
4.2.2 SendMailFactory工厂实现 public class SendMailFactory implements Provider { @Override public Sender produce () { return new MailSender (); } }
4.2.3 SendSmsFactory工厂实现 public class SendSmsFactory implements Provider { @Override public Sender produce () { return new SmsSender (); } }
4.3 测试 public class Main { public static void main (String[] args) { Provider provider = new SendMailFactory (); Sender sender = provider.produce(); sender.Send(); } }
4.4 小结 这个模式的好处就是,如果现在想增加一个功能:发即时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现 Provider 接口,就OK 了,无需去改动现成的代码。这样做,拓展性较好 。
五、单例模式(Singleton) 单例对象(Singleton)是一种常用的设计模式。在 Java 应用中,单例对象能保证在一个 JVM 中,该对象只有一个实例存在。这样的模式有几个好处:
某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。
有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
5.1 构建简单单例 public class Singleton { private static Singleton instance = null ; private Singleton () { } public static Singleton getInstance () { if (instance == null ) { instance = new Singleton (); } return instance; } public Object readResolve () { return instance; } }
5.2 线程安全 这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,首先会想到对getInstance 方法加synchronized
关键字。
public static synchronized Singleton getInstance () { if (instance == null ) { instance = new Singleton (); } return instance; }
synchronized
关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance()
,都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:
public static Singleton getInstance () { if (instance == null ) { synchronized (instance) { if (instance == null ) { instance = new Singleton (); } } } return instance; }
在 Java 指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM 并不保证这两个操作的先后顺序,也就是说有可能 JVM 会为新的Singleton 实例分配空间,然后直接赋值给instance 成员,然后再去初始化这个 Singleton 实例。这样就可能出错了。继续改进代码:
private static class SingletonFactory { private static Singleton instance = new Singleton (); } public static Singleton getInstance () { return SingletonFactory.instance; }
单例模式使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance 的时候,JVM 能够帮我们保证instance 只被创建一次,并且会保证把赋值给instance 的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。
5.3 单例模式实现 5.3.1 内部类维护的单例模式 单例模式使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance 的时候,JVM 能够帮我们保证instance 只被创建一次,并且会保证把赋值给instance 的内存初始化完毕。
public class Singleton { private Singleton () { } private static class SingletonFactory { private static Singleton instance = new Singleton (); } public static Singleton getInstance () { return SingletonFactory.instance; } public Object readResolve () { return getInstance(); } }
如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。
5.3.2 synchronized维护的单例模式 因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized
关键字。
public class Singleton { private static Singleton instance = null ; private Singleton () { } private static synchronized void syncInit () { if (instance == null ) { instance = new Singleton (); } } public static Singleton getInstance () { if (instance == null ) { syncInit(); } return instance; } public Object readResolve () { return getInstance(); } }
考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响。
5.3.3 影子实例 采用”影子实例”的办法为单例对象的属性同步更新。
public class Singleton { private static Singleton instance = null ; private Vector properties = null ; public Vector getProperties () { return properties; } private Singleton () { } private static synchronized void syncInit () { if (instance == null ) { instance = new Singleton (); } } public static Singleton getInstance () { if (instance == null ) { syncInit(); } return instance; } public void updateProperties () { Singleton shadow = new Singleton (); properties = shadow.getProperties(); } public Object readResolve () { return getInstance(); } }
5.4 小结
单例模式理解起来简单,但是具体实现起来还是有一定的难度。
synchronized 关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。
六、建造者模式(Builder) 工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是抽象工厂模式和最后的Test结合 起来得到的。
6.1 Sender接口 1)Sender接口
package com.bamboo.service;public interface Sender { public void Send () ; }
2)MailSender实现
package com.bamboo.service.impl;public class MailSender implements Sender { @Override public void Send () { System.out.println("this is mail sender!" ); } }
3)SmsSender实现
package com.bamboo.service.impl;public class SmsSender implements Sender { @Override public void Send () { System.out.println("this is sms sender!" ); } }
6.2 建造者类 public class Builder { private List<Sender> list = new ArrayList <>(); public void produceMailSender (int count) { for (int i = 0 ; i < count; i++) { list.add(new MailSender ()); } } public void produceSmsSender (int count) { for (int i = 0 ; i < count; i++) { list.add(new SmsSender ()); } } }
6.3 测试 public class Main { public static void main (String[] args) { Builder builder = new Builder (); builder.produceMailSender(10 ); } }
修改建造者得到输出内容:
public class Builder { private List<Sender> list = new ArrayList <>(); public void produceMailSender (int count) { for (int i = 0 ; i < count; i++) { list.add(new MailSender ()); } invoke(); } public void produceSmsSender (int count) { for (int i = 0 ; i < count; i++) { list.add(new SmsSender ()); } invoke(); } private void invoke () { for (Sender sender : list) { sender.Send(); } } }
this is mail sender! * 10
6.4 小结 建造者模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。
与工程模式的区别:
工厂模式关注的是创建单个产品
建造者模式则关注创建符合对象,多个部分。
因此,是选择工厂模式还是建造者模式,依实际情况而定。
七、原型模式(Prototype) 原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象 。
本结会通过对象的复制,进行讲解。
7.1 简易原型类 在 Java 中,复制对象是通过 clone()实现的,先创建一个原型类。
public class Prototype implements Cloneable { @Override protected Object clone () throws CloneNotSupportedException { Prototype proto = (Prototype) super .clone(); return proto; } }
一个原型类,只需要实现Cloneable 接口,覆写 clone 方法,此处clone 方法可以改成任意的名称,因为Cloneable 接口是个空接口,你可以任意定义实现类的方法名,如cloneA 或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object 类中,clone()是 native 的。
7.2 对象深、浅复制的概念 7.2.1 浅复制 将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
7.2.2 深复制 将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
7.3 深、浅复制案例 public class Prototype implements Cloneable , Serializable { private static final long serialVersionUID = 1L ; private String string; private SerialzableObject obj; @Override protected Object clone () throws CloneNotSupportedException { Prototype proto = (Prototype) super .clone(); return proto; } public Object deepClone () throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (bos); oos.writeObject(this ); ByteArrayInputStream bis = new ByteArrayInputStream (bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bis); return ois.readObject(); } public String getString () { return string; } public void setString (String string) { this .string = string; } public SerialzableObject getObj () { return obj; } public void setObj (SerialzableObject obj) { this .obj = obj; } } class SerialzableObject implements Serializable { private static final long seriaVersionUID = 1L ; }
要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
八、适配器模式(Adapter) 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。
主要分为三类:
类的适配器模式
对象的适配器模式
接口的适配器模式
8.1 类的适配器模式 核心思想:有一个 Source 类,拥有一个方法,待适配,目标接口时Targetable,通过Adapter 类,将Source 的功能扩展到Targetable 里。
8.1.1 Source public class Source { public void method1 () { System.out.println("this is original method!" ); } }
8.1.2 Targetable public interface Targetable { public void method1 () ; public void method2 () ; }
8.1.3 Adapter public class Adapter extends Source implements Targetable { @Override public void method2 () { System.out.println("this is the targetable method!" ); } }
8.1.4 测试 public class Main { public static void main (String[] args) { Targetable target = new Adapter (); target.method1(); target.method2(); } }
this is original method! this is the targetable method!
这样Targetable 接口的实现类就具有了Source 类的功能。
8.2 对象的适配器模式 将Adapter 类作修改,这次不继承Source 类,而是持有Source 类的实例,以达到解决兼容性的问题。
8.2.1 Wrapper适配器 public class Wrapper implements Targetable { private Source source; public Wrapper (Source source) { super (); this .source = source; } @Override public void method1 () { source.method1(); } @Override public void method2 () { System.out.println("this is the targetable method!" ); } }
8.2.2 测试 public class Main { public static void main (String[] args) { Source source = new Source (); Targetable target = new Wrapper (source); target.method1(); target.method2(); } }
this is original method! this is the targetable method!
输出与第一种一样,只是适配的方法不同而已。
8.3 接口的适配器模式 有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法 ,这明显有时比较浪费 ,因为并不是所有的方法都是我们需要的 ,有时只需要某一些,此处为了解决 这个问题,我们引入了接口的适配器模式 ,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
8.3.1 Sourceable public interface Sourceable { public void method1 () ; public void method2 () ; }
8.3.2 SourceSub 1)
public class SourceSub1 extends Wrapper2 { @Override public void method1 () { System.out.println("the sourceable interface's first Sub1!" ); } }
2)
public class SourceSub2 extends Wrapper2 { @Override public void method2 () { System.out.println("the sourceable interface's second Sub2!" ); } }
8.3.3 Wrapper适配器 public abstract class Wrapper2 implements Sourceable { public void method1 () {} public void method2 () {} }
8.3.4 测试 public class Main { public static void main (String[] args) { SourceSub1 source1 = new SourceSub1 (); SourceSub2 source2 = new SourceSub2 (); source1.method1(); source1.method2(); source2.method1(); source2.method2(); } }
the sourceable interface's first Sub1! the sourceable interface's second Sub2!
8.4 应用场景
类的适配器模式:当希望将一个类转换成满足另一个新接口的类时 ,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时 ,可以创建一个Wrapper 类,持有原类的一个实例,在Wrapper 类的方法中,调用实例的方法就行。
接口的适配器模式:当不希望实现一个接口中所有的方法时 ,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
九、装饰模式(Decorator) 装饰模式就是给一个对象增加一些新的功能 ,而且是动态 的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
关系:Source 类是被装饰类,Decorator 类是一个装饰类,可以为 Source 类动态的添加一些功能
9.1 Sourceable 接口 public interface Sourceable { public void method () ; }
9.2 Source 被装饰类 public class Source implements Sourceable { @Override public void method () { System.out.println("the original method!" ); } }
9.3 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!" ); } }
9.4 测试 public class Main { public static void main (String[] args) { Sourceable source = new Source (); Sourceable obj = new Decorator (source); obj.method(); } }
before decorator! the original method! after decorator!
9.5 应用场景
需要扩展一个类的功能。
动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)
缺点 :产生过多相似的对象,不易排错。
十、代理模式(Proxy) 每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作。
10.1 Sourceable 接口 public interface Sourceable { public void method () ; }
10.2 Source 被代理类 public class Source implements Sourceable { @Override public void method () { System.out.println("the original method" ); } }
10.3 Proxy代理类 public class Proxy implements Sourceable { private Source source; public Proxy () { super (); this .source = new Source (); } @Override public void method () { before(); source.method(); after(); } private void after () { System.out.println("after proxy!" ); } private void before () { System.out.println("before proxy!" ); } }
10.4 测试 public class Main { public static void main (String[] args) { Sourceable source = new Proxy (); source.method(); } }
before proxy! the original method after proxy!
10.5 应用场景 如果已有的方法 在使用的时候需要对原有的方法进行改进 ,此时有两种办法:
修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护。
十一、外观模式(Facade) 外观模式是为了解决类与类之家的依赖关系的,像 spring 一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade 类中,降低了类类之间的耦合度,该模式中没有涉及到接口。
案例:封装电脑启动与关闭的内部程序
11.1 CPU public class CPU { public void startup () { System.out.println("cpu startup" ); } public void shutdown () { System.out.println("cpu shutdown" ); } }
11.2 Disk public class Disk { public void startup () { System.out.println("disk startup" ); } public void shutdown () { System.out.println("disk shutdown" ); } }
11.3 Memory public class Memory { public void startup () { System.out.println("memory startup" ); } public void shutdown () { System.out.println("memory shutdown" ); } }
11.4 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" ); } }
11.5 测试 public class Main { public static void main (String[] args) { Computer computer = new Computer (); computer.startup(); computer.shutdown(); } }
start the computer!cpu startup memory startup disk startup start computer finishedbegin to close the computer! cpu shutdown memory shutdown disk shutdown computer closed
如果我们没有Computer 类,那么,CPU、Memory、Disk 他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer 类,他们之间的关系被放在了Computer 类里,这样就起到了解耦的作用,这,就是外观模式!
十二、桥接模式(Bridge) 桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化。
像我们常用的 JDBC 桥DriverManager 一样,JDBC 进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC 提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。
12.1 Sourceable 接口 public interface Sourceable { public void method () ; }
12.2 SourceSub 实现 1)SourceSub1
public class SourceSub1 implements Sourceable { @Override public void method () { System.out.println("this is the first sub" ); } }
2)SourceSub2
public class SourceSub2 implements Sourceable { @Override public void method () { System.out.println("this is second sub" ); } }
12.3 Bridge public abstract class Bridge { private Sourceable source; public void method () { source.method(); } public Sourceable getSource () { return source; } public void setSource (Sourceable source) { this .source = source; } }
12.4 MyBridge public class MyBridge extends Bridge { @Override public void method () { getSource().method(); } }
12.5 测试 public class Main { public static void main (String[] args) { Bridge bridge = new MyBridge (); Sourceable source1 = new SourceSub1 (); bridge.setSource(source1); bridge.method(); Sourceable source2 = new SourceSub2 (); bridge.setSource(source2); bridge.method(); } }
this is the first sub this is second sub
十三、组合模式(Composite) 组合模式有时又叫部分-整体 模式在处理类似树形结构的问题时比较方便
13.1 TreeNode public class TreeNode { private String name; private TreeNode parent; private Vector<TreeNode> children = new Vector <>(); public TreeNode (String name) { this .name = name; } public String getName () { return name; } public void setName (String name) { this .name = name; } public TreeNode getParent () { return parent; } public void setParent (TreeNode parent) { this .parent = parent; } public void add (TreeNode node) { children.add(node); } public void remove (TreeNode node) { children.remove(node); } public Enumeration<TreeNode> getChildren () { return children.elements(); } }
13.2 Tree public class Tree { TreeNode root = null ; public Tree (String name) { root = new TreeNode (name); } }
13.3 测试 public class Main { public static void main (String[] args) { Tree tree = new Tree ("A" ); TreeNode nodeB = new TreeNode ("B" ); TreeNode nodeC = new TreeNode ("C" ); nodeB.add(nodeC); tree.root.add(nodeB); System.out.println("build the tree finished" ); } }
13.4 使用场景 将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
十四、享元模式(Flyweight) 享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
FlyWeightFactory 负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight 是超类。
一提到共享池,我们很容易联想到Java 里面的JDBC 连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password 及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。
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 ; } } }
通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能。
十五、策略模式(strategy) 策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。
15.1 统一接口 public interface ICalculator { public int calculate (String exp) ; }
15.2 辅助类 public abstract class AbstractCalculator { public int [] split(String exp, String opt) { String[] array = exp.split(opt); int [] arrayInt = new int [2 ]; arrayInt[0 ] = Integer.parseInt(array[0 ]); arrayInt[1 ] = Integer.parseInt(array[1 ]); return arrayInt; } }
15.3 三个实现类 1)Minus
public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate (String exp) { int [] arrayInt = split(exp, "-" ); return arrayInt[0 ] - arrayInt[1 ]; } }
2)Multiply
public class Multiply extends AbstractCalculator implements ICalculator { @Override public int calculate (String exp) { int [] arrayInt = split(exp, "\\*" ); return arrayInt[0 ] * arrayInt[1 ]; } }
3)Plus
public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate (String exp) { int [] arrayInt = split(exp, "\\+" ); return arrayInt[0 ] + arrayInt[1 ]; } }
15.4 测试 public class Main { public static void main (String[] args) { String exp = "2+8" ; ICalculator cal = new Plus (); int result = cal.calculate(exp); System.out.println(result); } }
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
十六、模板方法模式(Template Method) 一个抽象类中,有一个主方法,再定义1…n 个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用。
16.1 实现思想 在AbstractCalculator 类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus 分别继承AbstractCalculator 类,通过对 AbstractCalculator 的调用实现对子类的调用。
16.2 AbstractCalculator public abstract class AbstractCalculator { public final int calculate (String exp, String opt) { int [] array = split(exp, opt); return calculate(array[0 ], array[1 ]); } abstract public int calculate (int num1, int num2) ; public int [] split(String exp, String opt) { String[] array = exp.split(opt); int [] arrayInt = new int [2 ]; arrayInt[0 ] = Integer.parseInt(array[0 ]); arrayInt[1 ] = Integer.parseInt(array[1 ]); return arrayInt; } }
16.3 Plus public class Plus extends AbstractCalculator { @Override public int calculate (int num1, int num2) { return num1 + num2; } }
16.4 测试 public class Main { public static void main (String[] args) { String exp = "8+8" ; AbstractCalculator cal = new Plus (); int result = cal.calculate(exp, "\\+" ); System.out.println(result); } }
十七、观察者模式(Observer) 包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承
观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或 wiki 时,经常会看到RSS 图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。
当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化
类的作用:MySubject 类就是我们的主对象,Observer1 和 Observer2 是依赖于MySubject 的对象,当MySubject 变化时,Observer1 和 Observer2 必然变化。
AbstractSubject 类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject 变化时,负责通知在列表内存在的对象。
17.1 Observer 接口 public interface Observer { public void update () ; }
17.2 两个实现类 1)
public class Observer1 implements Observer { @Override public void update () { System.out.println("observer1 has received" ); } }
2)
public class Observer2 implements Observer { @Override public void update () { System.out.println("observer2 has received" ); } }
17.3 Subject接口及实现类 1)接口
public interface Subject { public void add (Observer observer) ; public void del (Observer observer) ; public void notifyObservers () ; public void operation () ; }
2)抽象
public abstract class AbstractSubject implements Subject { private Vector<Observer> vector = new Vector <>(); @Override public void add (Observer observer) { vector.add(observer); } @Override public void del (Observer observer) { vector.remove(observer); } @Override public void notifyObservers () { Enumeration<Observer> enumo = vector.elements(); while (enumo.hasMoreElements()) { enumo.nextElement().update(); } } }
3)实现类
public class MySubject extends AbstractSubject { @Override public void operation () { System.out.println("update self" ); notifyObservers(); } }
17.4 测试 public class Main { public static void main (String[] args) { Subject sub = new MySubject (); sub.add(new Observer1 ()); sub.add(new Observer2 ()); sub.operation(); } }
update self observer1 has received observer2 has received
十八、迭代子模式(Iterator) 顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。
18.1 两个接口 1)Collection
public interface Collection { public Iterator iterator () ; public Object get (int i) ; public int size () ; }
2)Iterator
public interface Iterator { public Object previous () ; public Object next () ; public boolean hasNext () ; public Object first () ; }
18.2 两个实现 1)MyCollection
public class MyCollection implements Collection { public String[] string = {"A" , "B" , "C" , "D" , "E" }; @Override public Iterator iterator () { return new MyIterator (this ); } @Override public Object get (int i) { return string[i]; } @Override public int size () { return string.length; } }
2)MyIterator
public class MyIterator implements Iterator { private Collection collection; private int pos = -1 ; public MyIterator (Collection collection) { this .collection = collection; } @Override public Object previous () { if (pos > 0 ) { pos--; } return collection.get(pos); } @Override public Object next () { if (pos < collection.size() - 1 ) { pos++; } return collection.get(pos); } @Override public boolean hasNext () { if (pos < collection.size() - 1 ) { return true ; } else { return false ; } } @Override public Object first () { pos = 0 ; return collection.get(pos); } }
18.3 测试 public class Main { public static void main (String[] args) { Collection collection = new MyCollection (); Iterator iterator = collection.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
十九、责任链模式(Chain of Responsibility) 多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。
Abstracthandler 类提供了get 和set 方法,方便MyHandle 类设置和修改引用对象,MyHandle 类是核心,实例化后生成一系列相互持有的对象,构成一条链。
19.1 Handler接口 public interface Handler { public void operator () ; }
19.2 AbstractHandler抽象类 public abstract class AbstractHandler { private Handler handler; public Handler getHandler () { return handler; } public void setHandler (Handler handler) { this .handler = handler; } }
19.3 MyHandler实现 public class MyHandler extends AbstractHandler implements Handler { private String name; public MyHandler (String name) { this .name = name; } @Override public void operator () { System.out.println(name + "deal!" ); if (getHandler() != null ) { getHandler().operator(); } } }
19.4 测试 public class Main { public static void main (String[] args) { MyHandler h1 = new MyHandler ("h1" ); MyHandler h2 = new MyHandler ("h2" ); MyHandler h3 = new MyHandler ("h3" ); h1.setHandler(h2); h2.setHandler(h3); h1.operator(); } }
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
二十、命令模式(Command) 命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。
Invoker 是调用者(司令员),Receiver 是被调用者(士兵),MyCommand 是命令,实现了Command 接口,持有接收对象,看实现代码:
20.1 Command接口 public interface Command { public void exe () ; }
20.2 MyCommand实现 public class MyCommand implements Command { private Receiver receiver; public MyCommand (Receiver receiver) { this .receiver = receiver; } @Override public void exe () { receiver.action(); } }
20.3 Receiver类 public class Receiver { public void action () { System.out.println("command received!" ); } }
20.4 Invoker类 public class Invoker { private Command command; public Invoker (Command command) { this .command = command; } public void action () { command.exe(); } }
20.5 测试 public class Main { public static void main (String[] args) { Receiver receiver = new Receiver (); Command cmd = new MyCommand (receiver); Invoker invoker = new Invoker (cmd); invoker.action(); } }
命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开。
二十一、备忘录模式(Memento) 主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A 中有各种属性,A 可以决定需要备份的属性,备忘录类B 是用来存储A 的一些内部状态,类C 呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。
Original 类是原始类,里面有需要保存的属性value 及创建一个备忘录类,用来保存 value值。Memento 类是备忘录类,Storage 类是存储备忘录的类,持有 Memento 类的实例,该模式很好理解。
21.1 Original public class Original { private String value; public String getValue () { return value; } public void setValue (String value) { this .value = value; } public Original (String value) { this .value = value; } public Memento createMemento () { return new Memento (value); } public void restoreMemento (Memento memento) { this .value = memento.getValue(); } }
21.2 Memento public class Memento { private String value; public Memento (String value) { this .value = value; } public String getValue () { return value; } public void setValue (String value) { this .value = value; } }
21.3 Storage public class Storage { private Memento memento; public Storage (Memento memento) { this .memento = memento; } public Memento getMemento () { return memento; } public void setMemento (Memento memento) { this .memento = memento; } }
21.4 测试 public class Main { public static void main (String[] args) { Original original = new Original ("egg" ); Storage storage = new Storage (original.createMemento()); System.out.println("初始化状态为:" + original.getValue()); original.setValue("niu" ); System.out.println("修改后的状态为:" + original.getValue()); original.restoreMemento(storage.getMemento()); System.out.println("恢复后的状态为:" + original.getValue()); } }
二十二、状态模式(State) 核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ 来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。
State 类是个状态类,Context 类可以实现切换
22.1 State public class State { private String value; public void method1 () { System.out.println("execute the first opt!" ); } public void method2 () { System.out.println("execute the second opt!" ); } public String getValue () { return value; } public void setValue (String value) { this .value = value; } }
22.2 Context public class Context { private State state; public void method () { if (state.getValue().equals("state1" )) { state.method1(); } else if (state.getValue().equals("state2" )) { state.method2(); } } public State getState () { return state; } public void setState (State state) { this .state = state; } public Context (State state) { this .state = state; } }
22.3 测试 public class Main { public static void main (String[] args) { State state = new State (); Context context = new Context (state); state.setValue("state1" ); context.method(); } }
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
二十三、访问者模式(Visitor) 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。—— From 百科
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。
23.1 Visitor 一个Visitor 类,存放要访问的对象
public interface Visitor { public void visit (Subject sub) ; }
public class MyVisitor implements Visitor { @Override public void visit (Subject sub) { System.out.println("visit the subject: " + sub.getSubject()); } }
23.2 Subject Subject 类,accept 方法,接受将要访问它的对象,getSubject()获取将要被访问的属性
public interface Subject { public void accept (Visitor visitor) ; public String getSubject () ; }
public class MySubject implements Subject { @Override public void accept (Visitor visitor) { visitor.visit(this ); } @Override public String getSubject () { return "love" ; } }
23.3 测试 public class Main { public static void main (String[] args) { Visitor visitor = new MyVisitor (); Subject subject = new MySubject (); subject.accept(visitor); } }
23.4 小结 该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:
新功能会不会与现有功能出现兼容性问题?
以后会不会再需要添加?
如果类不允许修改代码怎么办?
面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦。
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator 类的关系,具体类类之间的关系及调度交给Mediator 就行,这有点像spring 容器的作用。
User 类统一接口,User1 和User2 分别是不同的对象,二者之间有关联,如果不采用中介者模式,则需要二者相互持有引用,这样二者的耦合度很高,为了解耦,引入了 Mediator类,提供统一接口,MyMediator 为其实现类,里面持有 User1 和User2 的实例,用来实现对User1 和User2 的控制。这样User1 和User2 两个对象相互独立,他们只需要保持好和Mediator 之间的关系就行,剩下的全由 MyMediator 类来维护。
public interface Mediator { public void createMediator () ; public void workAll () ; }
public class MyMediator implements Mediator { private User user1; private User user2; public User getUser1 () { return user1; } public User getUser2 () { return user2; } @Override public void createMediator () { user1 = new User1 (this ); user2 = new User2 (this ); } @Override public void workAll () { user1.work(); user2.work(); } }
24.3 User 1)User
public abstract class User { private Mediator mediator; public Mediator getMediator () { return mediator; } public void setMediator (Mediator mediator) { this .mediator = mediator; } public User (Mediator mediator) { this .mediator = mediator; } public abstract void work () ; }
2)User1
public class User1 extends User { public User1 (Mediator mediator) { super (mediator); } @Override public void work () { System.out.println("user1 exe!" ); } }
3)User2
public class User2 extends User { public User2 (Mediator mediator) { super (mediator); } @Override public void work () { System.out.println("user2 exe!" ); } }
24.4 测试 public class Main { public static void main (String[] args) { Mediator mediator = new MyMediator (); mediator.createMediator(); mediator.workAll(); } }
二十五、解释器模式(Interpreter) 解释器模式是我们暂时的最后一讲,一般主要应用在 OOP 开发中的编译器的开发中,所以适用面比较窄。
Context 类是一个上下文环境类,Plus 和Minus 分别是用来计算的实现。
25.1 Expression接口 public interface Expression { public int interpret (Context context) ; }
25.2 Context类 public class Context { private int num1; private int num2; public Context (int num1, int num2) { this .num1 = num1; this .num2 = num2; } public int getNum1 () { return num1; } public void setNum1 (int num1) { this .num1 = num1; } public int getNum2 () { return num2; } public void setNum2 (int num2) { this .num2 = num2; } }
25.3 Minus and Plus 1)Minus
public class Minus implements Expression { @Override public int interpret (Context context) { return context.getNum1() - context.getNum2(); } }
2)Plus
public class Plus implements Expression { @Override public int interpret (Context context) { return context.getNum1()+context.getNum2(); } }
25.4 测试 public class Main { public static void main (String[] args) { int result = new Minus ().interpret((new Context (new Plus () .interpret(new Context (9 , 2 )), 8 ))); System.out.println(result); } }
解释器模式用来做各种各样的解释器,如正则表达式等的解释器等等