设计模式总结
设计模式总结
参考:
https://www.cnblogs.com/wangzhongqiu/p/6245820.html
Java开发中的23种设计模式详解
一、设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:
二、设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
三、Java的23中设计模式
从这一块开始,我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析。
1、工厂方法模式(Factory Method)
工厂方法模式分为三种:
11、普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:
举例如下:(我们举一个发送邮件和短信的例子)
首先,创建二者的共同接口:
- public interface Sender {
- public void Send();
- }
其次,创建实现类:
- public class MailSender implements Sender {
- @Override
- public void Send() {
- System.out.println("this is mailsender!");
- }
- }
- public class SmsSender implements Sender {
- @Override
- public void Send() {
- System.out.println("this is sms sender!");
- }
- }
最后,建工厂类:
- 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;
- }
- }
- }
我们来测试下:
- public class FactoryTest {
- public static void main(String[] args) {
- SendFactory factory = new SendFactory();
- Sender sender = factory.produce("sms");
- sender.Send();
- }
- }
输出:this is sms sender!
22、多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:
将上面的代码做下修改,改动下SendFactory类就行,如下:
- return new MailSender();
- }
- public Sender produceSms(){
- return new SmsSender();
- }
- }
测试类如下:
- public class FactoryTest {
- public static void main(String[] args) {
- SendFactory factory = new SendFactory();
- Sender sender = factory.produceMail();
- sender.Send();
- }
- }
输出:this is mailsender!
33、静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
- public class SendFactory {
- public static Sender produceMail(){
- return new MailSender();
- }
- public static Sender produceSms(){
- return new SmsSender();
- }
- }
- public class FactoryTest {
- public static void main(String[] args) {
- Sender sender = SendFactory.produceMail();
- sender.Send();
- }
- }
输出:this is mailsender!
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
2、抽象工厂模式(Abstract Factory)
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解。
请看例子:
- public interface Sender {
- public void Send();
- }
两个实现类:
- public class MailSender implements Sender {
- @Override
- public void Send() {
- System.out.println("this is mailsender!");
- }
- }
- public class SmsSender implements Sender {
- @Override
- public void Send() {
- System.out.println("this is sms sender!");
- }
- }
两个工厂类:
- public class SendMailFactory implements Provider {
- @Override
- public Sender produce(){
- return new MailSender();
- }
- }
- public class SendSmsFactory implements Provider{
- @Override
- public Sender produce() {
- return new SmsSender();
- }
- }
在提供一个接口:
- public interface Provider {
- public Sender produce();
- }
测试类:
- public class Test {
- public static void main(String[] args) {
- Provider provider = new SendMailFactory();
- Sender sender = provider.produce();
- sender.Send();
- }
- }
其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
3、单例模式(Singleton)
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
首先我们写一个简单的单例类:
- public class Singleton {
- /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
- private static Singleton instance = null;
- /* 私有构造方法,防止被实例化 */
- private Singleton() {
- }
- /* 静态工程方法,创建实例 */
- public static Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
- /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
- public Object readResolve() {
- return instance;
- }
- }
这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对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;
- }
似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:
- private static class SingletonFactory{
- private static Singleton instance = new Singleton();
- }
- public static Singleton getInstance(){
- return SingletonFactory.instance;
- }
实际情况是,单例模式使用内部类来维护单例的实现,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();
- }
- }
其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:
- public class SingletonTest {
- private static SingletonTest instance = null;
- private SingletonTest() {
- }
- private static synchronized void syncInit() {
- if (instance == null) {
- instance = new SingletonTest();
- }
- }
- public static SingletonTest getInstance() {
- if (instance == null) {
- syncInit();
- }
- return instance;
- }
- }
考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响。
补充:采用"影子实例"的办法为单例对象的属性同步更新
- public class SingletonTest {
- private static SingletonTest instance = null;
- private Vector properties = null;
- public Vector getProperties() {
- return properties;
- }
- private SingletonTest() {
- }
- private static synchronized void syncInit() {
- if (instance == null) {
- instance = new SingletonTest();
- }
- }
- public static SingletonTest getInstance() {
- if (instance == null) {
- syncInit();
- }
- return instance;
- }
- public void updateProperties() {
- SingletonTest shadow = new SingletonTest();
- properties = shadow.getProperties();
- }
- }
通过单例模式的学习告诉我们:
1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。
2、synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。
到这儿,单例模式基本已经讲完了,结尾处,笔者突然想到另一个问题,就是采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?
首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!
4、建造者模式(Builder)
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。我们看一下代码:
还和前面一样,一个Sender接口,两个实现类MailSender和SmsSender。最后,建造者类如下:
- public class Builder {
- private List<Sender> list = new ArrayList<Sender>();
- 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());
- }
- }
- }
测试类:
- public class Test {
- public static void main(String[] args) {
- Builder builder = new Builder();
- builder.produceMailSender(10);
- }
- }
从这点看出,建造者模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。所以与工程模式的区别就是:工厂模式关注的是创建单个产品,而建造者模式则关注创建符合对象,多个部分。因此,是选择工厂模式还是建造者模式,依实际情况而定。
5、原型模式(Prototype)
原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。本小结会通过对象的复制,进行讲解。在Java中,复制对象是通过clone()实现的,先创建一个原型类:
- public class Prototype implements Cloneable {
- public 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的,具体怎么实现,我会在另一篇文章中,关于解读Java中本地方法的调用,此处不再深究。在这儿,我将结合对象的浅复制和深复制来说一下,首先需要了解对象深、浅复制的概念:
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
此处,写一个深浅复制的例子:
- public class Prototype implements Cloneable, Serializable {
- private static final long serialVersionUID = 1L;
- private String string;
- private SerializableObject obj;
- /* 浅复制 */
- public 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 SerializableObject getObj() {
- return obj;
- }
- public void setObj(SerializableObject obj) {
- this.obj = obj;
- }
- }
- class SerializableObject implements Serializable {
- private static final long serialVersionUID = 1L;
- }
我们接着讨论设计模式,上篇文章我讲完了5种创建型模式,这章开始,我将讲下7种结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中对象的适配器模式是各种模式的起源,我们看下面的图:
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。首先,我们来看看类的适配器模式,先看类图:
核心思想就是:有一个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 {
- @Override
- public void method2() {
- System.out.println("this is the targetable method!");
- }
- }
Adapter类继承Source类,实现Targetable接口,下面是测试类:
- public class AdapterTest {
- 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类的功能。
对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。看图:
只需要修改Adapter类的源码即可:
- 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();
- Targetable target = new Wrapper(source);
- target.method1();
- target.method2();
- }
- }
输出与第一种一样,只是适配的方法不同而已。
第三种适配器模式是接口的适配器模式,接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。看一下类图:
这个很好理解,在实际开发中,我们也常会遇到这种接口中定义了太多的方法,以致于有时我们在一些实现类中并不是都需要。看代码:
- public interface Sourceable {
- public void method1();
- public void method2();
- }
抽象类Wrapper2:
- public abstract class Wrapper2 implements Sourceable{
- public void method1(){}
- public void method2(){}
- }
- public class SourceSub1 extends Wrapper2 {
- public void method1(){
- System.out.println("the sourceable interface's first Sub1!");
- }
- }
- public class SourceSub2 extends Wrapper2 {
- public void method2(){
- System.out.println("the sourceable interface's second Sub2!");
- }
- }
- public class WrapperTest {
- public static void main(String[] args) {
- Sourceable source1 = new SourceSub1();
- Sourceable source2 = new SourceSub2();
- source1.method1();
- source1.method2();
- source2.method1();
- source2.method2();
- }
- }
测试输出:
the sourceable interface's first Sub1!
the sourceable interface's second Sub2!
达到了我们的效果!
讲了这么多,总结一下三种适配器模式的应用场景:
类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
7、装饰模式(Decorator)
顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例,关系图如下:
Source类是被装饰类,Decorator类是一个装饰类,可以为Source类动态的添加一些功能,代码如下:
- 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) {
- Sourceable source = new Source();
- Sourceable obj = new Decorator(source);
- obj.method();
- }
- }
输出:
before decorator!
the original method!
after decorator!
装饰器模式的应用场景:
1、需要扩展一个类的功能。
2、动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)
缺点:产生过多相似的对象,不易排错!
8、代理模式(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();
- }
- }
输出:
before proxy!
the original method!
after proxy!
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
9、外观模式(Facade)
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,看下类图:(我们以一个计算机的启动过程为例)
我们先看下实现类:
- public class CPU {
- public void startup(){
- System.out.println("cpu startup!");
- }
- public void shutdown(){
- System.out.println("cpu shutdown!");
- }
- }
- public class Memory {
- public void startup(){
- System.out.println("memory startup!");
- }
- public void shutdown(){
- System.out.println("memory shutdown!");
- }
- }
- public class Disk {
- public void startup(){
- System.out.println("disk startup!");
- }
- public void shutdown(){
- System.out.println("disk 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!");
- }
- }
User类如下:
- public class User {
- 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 finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样就起到了解耦的作用,这,就是外观模式!
10、桥接模式(Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图:
实现代码:
先定义接口:
- public interface Sourceable {
- public void method();
- }
分别定义两个实现类:
- public class SourceSub1 implements Sourceable {
- @Override
- public void method() {
- System.out.println("this is the first sub!");
- }
- }
- public class SourceSub2 implements Sourceable {
- @Override
- public void method() {
- System.out.println("this is the second sub!");
- }
- }
定义一个桥,持有Sourceable的一个实例:
- 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;
- }
- }
- public class MyBridge extends Bridge {
- public void method(){
- getSource().method();
- }
- }
测试类:
- public class BridgeTest {
- 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();
- }
- }
output:
this is the first sub!
this is the second sub!
这样,就通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用。接下来我再画个图,大家就应该明白了,因为这个图是我们JDBC连接的原理,有数据库学习基础的,一结合就都懂了。
11、组合模式(Composite)
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
直接来看代码:
- public class TreeNode {
- private String name;
- private TreeNode parent;
- private Vector<TreeNode> children = new Vector<TreeNode>();
- 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();
- }
- }
- public class Tree {
- TreeNode root = null;
- public Tree(String name) {
- root = new TreeNode(name);
- }
- 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!");
- }
- }
使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
12、享元模式(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;
- }
- }
- }
本章是关于设计模式的最后一讲,会讲到第三种设计模式——行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。这段时间一直在写关于设计模式的东西,终于写到一半了,写博文是个很费时间的东西,因为我得为读者负责,不论是图还是代码还是表述,都希望能尽量写清楚,以便读者理解,我想不论是我还是读者,都希望看到高质量的博文出来,从我本人出发,我会一直坚持下去,不断更新,源源动力来自于读者朋友们的不断支持,我会尽自己的努力,写好每一篇文章!希望大家能不断给出意见和建议,共同打造完美的博文!
先来张图,看看这11中模式的关系:
第一类:通过父类与子类的关系进行实现。第二类:两个类之间。第三类:类的状态。第四类:通过中间类
13、策略模式(strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,关系图如下:
图中ICalculator提供同意的方法,
AbstractCalculator是辅助类,提供辅助方法,接下来,依次实现下每个类:
首先统一接口:
- public interface ICalculator {
- public int calculate(String exp);
- }
辅助类:
- 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;
- }
- }
三个实现类:
- public class Plus extends AbstractCalculator implements ICalculator {
- @Override
- public int calculate(String exp) {
- int arrayInt[] = split(exp,"\\+");
- return arrayInt[0]+arrayInt[1];
- }
- }
- public class Minus extends AbstractCalculator implements ICalculator {
- @Override
- public int calculate(String exp) {
- int arrayInt[] = split(exp,"-");
- return arrayInt[0]-arrayInt[1];
- }
- }
- public class Multiply extends AbstractCalculator implements ICalculator {
- @Override
- public int calculate(String exp) {
- int arrayInt[] = split(exp,"\\*");
- return arrayInt[0]*arrayInt[1];
- }
- }
简单的测试类:
- public class StrategyTest {
- public static void main(String[] args) {
- String exp = "2+8";
- ICalculator cal = new Plus();
- int result = cal.calculate(exp);
- System.out.println(result);
- }
- }
输出:10
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
14、模板方法模式(Template Method)
解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,先看个关系图:
就是在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对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;
- }
- }
- public class Plus extends AbstractCalculator {
- @Override
- public int calculate(int num1,int num2) {
- return num1 + num2;
- }
- }
测试类:
- public class StrategyTest {
- public static void main(String[] args) {
- String exp = "8+8";
- AbstractCalculator cal = new Plus();
- int result = cal.calculate(exp, "\\+");
- System.out.println(result);
- }
- }
我跟踪下这个小程序的执行过程:首先将exp和"\\+"做参数,调用AbstractCalculator类里的calculate(String,String)方法,在calculate(String,String)里调用同类的split(),之后再调用calculate(int ,int)方法,从这个方法进入到子类中,执行完return num1 + num2后,将值返回到AbstractCalculator类,赋给result,打印出来。正好验证了我们开头的思路。
15、观察者模式(Observer)
包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承,学的时候应该 记得归纳,记得本文最开始的那个图。观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。先来看看关系图:
我解释下这些类的作用:MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象。我们看实现代码:
一个Observer接口:
- public interface Observer {
- public void update();
- }
两个实现类:
- public class Observer1 implements Observer {
- @Override
- public void update() {
- System.out.println("observer1 has received!");
- }
- }
- public class Observer2 implements Observer {
- @Override
- public void update() {
- System.out.println("observer2 has received!");
- }
- }
Subject接口及实现类:
- public interface Subject {
- /*增加观察者*/
- public void add(Observer observer);
- /*删除观察者*/
- public void del(Observer observer);
- /*通知所有的观察者*/
- public void notifyObservers();
- /*自身的操作*/
- public void operation();
- }
- public abstract class AbstractSubject implements Subject {
- private Vector<Observer> vector = new Vector<Observer>();
- @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();
- }
- }
- }
- public class MySubject extends AbstractSubject {
- @Override
- public void operation() {
- System.out.println("update self!");
- notifyObservers();
- }
- }
测试类:
- public class ObserverTest {
- 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!
这些东西,其实不难,只是有些抽象,不太容易整体理解,建议读者:根据关系图,新建项目,自己写代码(或者参考我的代码),按照总体思路走一遍,这样才能体会它的思想,理解起来容易!
16、迭代子模式(Iterator)
顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。我们看下关系图:
这个思路和我们常用的一模一样,MyCollection中定义了集合的一些操作,MyIterator中定义了一系列迭代操作,且持有Collection实例,我们来看看实现代码:
两个接口:
- public interface Collection {
- public Iterator iterator();
- /*取得集合元素*/
- public Object get(int i);
- /*取得集合大小*/
- public int size();
- }
- public interface Iterator {
- //前移
- public Object previous();
- //后移
- public Object next();
- public boolean hasNext();
- //取得第一个元素
- public Object first();
- }
两个实现:
- 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;
- }
- }
- 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);
- }
- }
测试类:
- public class Test {
- public static void main(String[] args) {
- Collection collection = new MyCollection();
- Iterator it = collection.iterator();
- while(it.hasNext()){
- System.out.println(it.next());
- }
- }
- }
输出:A B C D E
此处我们貌似模拟了一个集合类的过程,感觉是不是很爽?其实JDK中各个类也都是这些基本的东西,加一些设计模式,再加一些优化放到一起的,只要我们把这些东西学会了,掌握好了,我们也可以写出自己的集合类,甚至框架!
17、责任链模式(Chain of Responsibility)
接下来我们将要谈谈责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。先看看关系图:
Abstracthandler类提供了get和set方法,方便MyHandle类设置和修改引用对象,MyHandle类是核心,实例化后生成一系列相互持有的对象,构成一条链。
- public interface Handler {
- public void operator();
- }
- public abstract class AbstractHandler {
- private Handler handler;
- public Handler getHandler() {
- return handler;
- }
- public void setHandler(Handler handler) {
- this.handler = handler;
- }
- }
- 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();
- }
- }
- }
- public class Test {
- 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();
- }
- }
输出:
h1deal!
h2deal!
h3deal!
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
18、命令模式(Command)
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。我们看看关系图:
Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象,看实现代码:
- public interface Command {
- public void exe();
- }
- public class MyCommand implements Command {
- private Receiver receiver;
- public MyCommand(Receiver receiver) {
- this.receiver = receiver;
- }
- @Override
- public void exe() {
- receiver.action();
- }
- }
- public class Receiver {
- public void action(){
- System.out.println("command received!");
- }
- }
- public class Invoker {
- private Command command;
- public Invoker(Command command) {
- this.command = command;
- }
- public void action(){
- command.exe();
- }
- }
- public class Test {
- public static void main(String[] args) {
- Receiver receiver = new Receiver();
- Command cmd = new MyCommand(receiver);
- Invoker invoker = new Invoker(cmd);
- invoker.action();
- }
- }
输出:command received!
这个很哈理解,命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想!
其实每个设计模式都是很重要的一种思想,看上去很熟,其实是因为我们在学到的东西中都有涉及,尽管有时我们并不知道,其实在Java本身的设计之中处处都有体现,像AWT、JDBC、集合类、IO管道或者是Web框架,里面设计模式无处不在。因为我们篇幅有限,很难讲每一个设计模式都讲的很详细,不过我会尽我所能,尽量在有限的空间和篇幅内,把意思写清楚了,更好让大家明白。本章不出意外的话,应该是设计模式最后一讲了,首先还是上一下上篇开头的那个图:
本章讲讲第三类和第四类。
19、备忘录模式(Memento)
主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。做个图来分析一下:
Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值。Memento类是备忘录类,Storage类是存储备忘录的类,持有Memento类的实例,该模式很好理解。直接看源码:
- 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();
- }
- }
- 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;
- }
- }
- 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;
- }
- }
测试类:
- public class Test {
- public static void main(String[] args) {
- // 创建原始类
- Original origi = new Original("egg");
- // 创建备忘录
- Storage storage = new Storage(origi.createMemento());
- // 修改原始类的状态
- System.out.println("初始化状态为:" + origi.getValue());
- origi.setValue("niu");
- System.out.println("修改后的状态为:" + origi.getValue());
- // 回复原始类的状态
- origi.restoreMemento(storage.getMemento());
- System.out.println("恢复后的状态为:" + origi.getValue());
- }
- }
输出:
初始化状态为:egg
修改后的状态为:niu
恢复后的状态为:egg
简单描述下:新建原始类时,value被初始化为egg,后经过修改,将value的值置为niu,最后倒数第二行进行恢复状态,结果成功恢复了。其实我觉得这个模式叫“备份-恢复”模式最形象。
20、状态模式(State)
核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。看图:
State类是个状态类,Context类可以实现切换,我们来看看代码:
- package com.xtfggef.dp.state;
- /**
- * 状态类的核心类
- * 2012-12-1
- * @author erqing
- *
- */
- public class State {
- private String value;
- public String getValue() {
- return value;
- }
- public void setValue(String value) {
- this.value = value;
- }
- public void method1(){
- System.out.println("execute the first opt!");
- }
- public void method2(){
- System.out.println("execute the second opt!");
- }
- }
- package com.xtfggef.dp.state;
- /**
- * 状态模式的切换类 2012-12-1
- * @author erqing
- *
- */
- public class Context {
- private State state;
- public Context(State state) {
- this.state = state;
- }
- public State getState() {
- return state;
- }
- public void setState(State state) {
- this.state = state;
- }
- public void method() {
- if (state.getValue().equals("state1")) {
- state.method1();
- } else if (state.getValue().equals("state2")) {
- state.method2();
- }
- }
- }
- public class Test {
- public static void main(String[] args) {
- State state = new State();
- Context context = new Context(state);
- //设置第一种状态
- state.setValue("state1");
- context.method();
- //设置第二种状态
- state.setValue("state2");
- context.method();
- }
- }
execute the first opt!
execute the second opt!
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
21、访问者模式(Visitor)
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。—— From 百科
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。
来看看我自己写的demo。场景:银行柜台提供的服务和来办业务的人。把银行的服务和业务的办理解耦了。缺点:如果银行要修改底层业务接口,所有继承接口的类都需要作出修改。不过java8的新特性接口默认方法可以解决这个问题,或者java8之前可以通过接口的适配器模式来解决这个问题
public class VisitorDemo {
// 银行柜台服务,以后银行要新增业务,只需要新增一个类实现这个接口就可以了。 interface Service { public void accept(Visitor visitor); }
// 来办业务的人,里面可以加上权限控制等等 static class Visitor { public void process(Service service) { // 基本业务 System.out.println("基本业务"); } public void process(Saving service) { // 存款 System.out.println("存款"); } public void process(Draw service) { // 提款 System.out.println("提款"); } public void process(Fund service) { System.out.println("基金"); // 基金 } } static class Saving implements Service { public void accept(Visitor visitor) { visitor.process(this); } } static class Draw implements Service { public void accept(Visitor visitor) { visitor.process(this); } } static class Fund implements Service { public void accept(Visitor visitor) { visitor.process(this); } } public static void main(String[] args) { Service saving = new Saving(); Service fund = new Fund(); Service draw = new Draw(); Visitor visitor = new Visitor(); Visitor guweiwei = new Visitor(); fund.accept(guweiwei); saving.accept(visitor); fund.accept(visitor); draw.accept(visitor); } }
public static void main(String[] args) { Service saving = new Saving(); Service fund = new Fund(); Service draw = new Draw(); Visitor visitor = new Visitor(); Visitor guweiwei =new Visitor(); fund.accept(guweiwei); saving.accept(visitor); fund.accept(visitor); draw.accept(visitor); }
基金
存款
基金
提款
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦,
22、中介者模式(Mediator)
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和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();
- }
- }
- public abstract class User {
- private Mediator mediator;
- public Mediator getMediator(){
- return mediator;
- }
- public User(Mediator mediator) {
- this.mediator = mediator;
- }
- public abstract void work();
- }
- public class User1 extends User {
- public User1(Mediator mediator){
- super(mediator);
- }
- @Override
- public void work() {
- System.out.println("user1 exe!");
- }
- }
- public class User2 extends User {
- public User2(Mediator mediator){
- super(mediator);
- }
- @Override
- public void work() {
- System.out.println("user2 exe!");
- }
- }
- public class Test {
- public static void main(String[] args) {
- Mediator mediator = new MyMediator();
- mediator.createMediator();
- mediator.workAll();
- }
- }
user1 exe!
user2 exe!
23、解释器模式(Interpreter)
解释器模式是我们暂时的最后一讲,一般主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。
Context类是一个上下文环境类,Plus和Minus分别是用来计算的实现,代码如下:
- public interface Expression {
- public int interpret(Context context);
- }
- public class Plus implements Expression {
- @Override
- public int interpret(Context context) {
- return context.getNum1()+context.getNum2();
- }
- }
- public class Minus implements Expression {
- @Override
- public int interpret(Context context) {
- return context.getNum1()-context.getNum2();
- }
- }
- 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;
- }
- }
- public class Test {
- public static void main(String[] args) {
- // 计算9+2-8的值
- int result = new Minus().interpret((new Context(new Plus()
- .interpret(new Context(9, 2)), 8)));
- System.out.println(result);
- }
- }
基本就这样,解释器模式用来做各种各样的解释器,如正则表达式等的解释器等等!
设计模式基本就这么大概讲完了,总体感觉有点简略,的确,这么点儿篇幅,不足以对整个23种设计模式做全面的阐述,此处读者可将它作为一个理论基础去学习,通过这四篇博文,先基本有个概念,虽然我讲的有些简单,但基本都能说明问题及他们的特点,如果对哪一个感兴趣,可以继续深入研究!同时我也会不断更新,尽量补全遗
1. 设计模式
1.简单工厂 创建型
面向对象设计的基本原则
单一职责
系统中的每一个对象应该只有一个单独的职责,所有对象关注的应该是自身职责的完成。
基本思想:高内聚,低耦合。
开闭原则
一个对象对扩展开放,对修改关闭。
基本思想:对类的改动是通过增加代码进行的,而不是修改现有的代码。
里氏替换原则
在任意父类出现的地方,都可以使用子类来替代。
依赖注入原则
要依赖于抽象,不要依赖于具体的实现。
基本思想:在开发中尽量的面向接口编程。
接口分离原则
不要去使用一些不需要使用的功能。
基本思想:一个接口不要提供太多的行为。
迪米特原则
一个对象对其他的对象应该尽可能少的了解。
基本思想:降低耦合。
优先使用组合而不是继承原则
基本思想:在复用对象的时候,要优先考虑组合,而不是继承。因为父类的任何改变都可能直接音响子类的
设计分类
创建型模式:创建对象。
结构型模式:对象的组成及对象的关系。
行为型模式:对象的行为。对象能够做什么。
简单工厂模式
简单工厂模式是指专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
从图中我们可以清楚的看到,该模式中主要包含下面3种角色:
工厂(Creator)角色
它是工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品的对象。
抽象(Product)角色
简单工厂模式所创建的所有对象的父类,负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色
是该模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。一般来讲是抽象产品类的子类,实现了抽象产品类中定义的所有接口方法。
举个例子:
代码说明一切
//Animal.java 抽象产品角色
public abstract class Animal {
public abstract void eat();
}
//Dog.java 具体产品角色
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("dog eat");
}
}
//Pig.java 具体产品角色
public class Pig extends Animal {
@Override
public void eat() {
System.out.println("pig eat");
}
}
//AnimalFactory.java 工厂角色
public class AnimalFactory {
private AnimalFactory(){}
public static Animal animalFactory(String string)
{
if(string.equals("dog"))
return new Dog();
else if(string.equals("pig"))
return new Pig();
else
return null;
}
}
//AnimalTest.java 客户端调用
public class AnimalTest {
public static void main(String[] args) {
Animal dog = AnimalFactory.animalFactory("dog");
dog.eat();
Animal pig = AnimalFactory.animalFactory("pig");
pig.eat();
Animal ani = AnimalFactory.animalFactory("dog");
ani.eat();
ani = AnimalFactory.animalFactory("pig");
ani.eat();
}
}
优缺点
对象的创建比较复杂的时候,就考虑使用工厂来实现。
优点:在简单工厂模式中,客户端不再负责对象的创建,而是把这个责任丢给了工厂类,客户端只负责对象的调用,从而明确了各个类的职责(单一职责)。
缺点:由于这个工厂类负责所有的对象的创建,那么当子类不断增多的时候,我们就需要去修改工厂的代码,这样就违反了开闭原则。
2.工厂模式 创建型
简单工厂模式 和 工厂方法模式 的区别
简单工厂模式
工厂方法模式
比较这两个结构图,我们会发现,每个结构图右侧的红色边框内有很大的区别,前者的简单工厂没有子类,只有一些逻辑判断代码;而后者却又重新分出四个具体的工厂。到这里,两种模式的区别之一就出来了。那么,工厂方法模式中的逻辑判断代码在哪里呢?答案是:客户端。因为,在前一种模式中,要想修改添加功能就必须在工厂类中进行修改,而后者是修改客户端。
联系:
都有运算类、客户端,其中运算类都有相应的子类。
区别:
最明显:工厂方法模式需要有一个工厂接口。
其次,工厂方法模式关于工厂类处是具体的工厂而不是一个简单的工厂类。
最后,添加功能时,前者改动的是工厂类(为工厂类添加相关功能的子类),后者改动的是客户端(为相应功能增加判断逻辑的代码)。
用实例(计算器)说明就是:
(1)都有运算类(包括加减乘除四个子类)、都需要一个客户端
(2)工厂方法模式中的工厂类处,产生与运算类的子类相对应的四个具体的工厂:加法工厂、减法工厂、乘法工厂和除法工厂
(3)代码中要构建一个工厂接口,然后四个具体工厂去实现这个接口
代码说明一切
//Animal .java
public abstract class Animal {
public abstract void sleep();
}
//AnimalFactory .java
public interface AnimalFactory {
public Animal createAnimal();
}
//Dog.java
public class Dog extends Animal {
@Override
public void sleep() {
System.out.println("dog sleep");
}
}
//DogFactory .java
public class DogFactory implements AnimalFactory{
@Override
public Animal createAnimal() {
return new Dog();
}
}
public class AnimalTest {
public static void main(String[] args) {
AnimalFactory af = null;
af = new DogFactory();
Animal dog = af.createAnimal();
dog.sleep();
AnimalFactory af2 =new PigFactory();
Animal pig = af2.createAnimal();
pig.sleep();
AnimalFactory af3 = new DogFactory();
Animal a = af3.createAnimal();
a.sleep();
af3 = new PigFactory();
a = af3.createAnimal();
a.sleep();
}
}
工厂方法模式与简单工厂模式在结构上的不同不是很明显。工厂方法类的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。
工厂方法模式之所以有一个别名叫多态性工厂模式是因为具体工厂类都有共同的接口,或者有共同的抽象父类。 当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了“开放-封闭”原则。而简单工厂模式在添加新产品对象后不得不修改工厂方法,扩展性不好。
工厂方法模式退化后可以演变成简单工厂模式。
3.模板方法 行为型
模板方法模式:定义一个算法的执行骨架,将具体的算法实现延迟到子类完成。
模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。
举个例子: 在现实生活中,很多事情都包含几个实现步骤,例如请客吃饭,无论吃什么,一般都包含点单、吃东西、买单等几个步骤,通常情况下这几个步骤的次序是:点单 --> 吃东西 --> 买单。在这三个步骤中,点单和买单大同小异,最大的区别在于第二步——吃什么?吃面条和吃满汉全席可大不相同。
这里,代码以一个看模版方法用了多少时间的例子来说明一下:
package com.yydcdut.java2;
public abstract class GetTime {
public void getTime()
{
long time1 = System.currentTimeMillis();
this.doSomething();
long time2 = System.currentTimeMillis();
System.out.println("耗时:"+(time2-time1));
}
public abstract void doSomething();
}
package com.yydcdut.java2;
public class Demo extends GetTime {
@Override
public void doSomething() {
for(int i=0;i<10000;i++)
System.out.println(""+i);
}
}
package com.yydcdut.java2;
public class Main {
public static void main(String[] args) {
GetTime gt = new Demo();
gt.getTime();
}
}
总结
模板方法模式:抽象的骨架类,具体的实现类。
优点:使用模板方法模式,在定义算法骨架的时候,可以灵活的实现具体的算法。满足用户多变的需求。
缺点:假如算法骨架有改动,就需要修改抽象类,那么,具体的实现类,也会跟着修改。
4 装饰模式 结构型
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
具体被装饰者和抽象装饰类都继承于抽象被装饰者类,继承的是类型,而不是行为。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系。
装饰者通常是用其他类似于工厂或生成器这样的模式创建的。
代码说明一切
package com.yydcdut.decorate;
//被装饰者抽象类
public interface Phone {
public void call();
}
package com.yydcdut.decorate;
//被装饰者具体类
public class RealPhone implements Phone {
@Override
public void call() {
System.out.println("打电话。。。");
}
}
package com.yydcdut.decorate;
//装饰者抽象类
public abstract class PhoneDecorate implements Phone {
private Phone phone = null;
public PhoneDecorate(Phone phone) {
this.phone = phone;
}
@Override
public void call() {
this.phone.call();
}
}
package com.yydcdut.decorate;
//装饰者的具体类
public class AfterPhone extends PhoneDecorate {
Phone phone = null;
public AfterPhone(Phone phone) {
super(phone);
this.phone = phone;
}
@Override
public void call() {
super.call();
System.out.println("打完电话。。。。");
}
}
package com.yydcdut.decorate;
//装饰者的具体类
public class BeforePhone extends PhoneDecorate {
Phone phone = null;
public BeforePhone(Phone phone) {
super(phone);
this.phone = phone;
}
@Override
public void call() {
System.out.println("打电话之前。。。。。");
super.call();
}
}
package com.yydcdut.decorate;
//测试类,主函数
public class PhoneMain {
public static void main(String[] args) {
Phone phone = new RealPhone();
phone.call();
System.out.println("-----------------------");
PhoneDecorate pd = new BeforePhone(phone);
pd.call();
System.out.println("-----------------------");
pd = new AfterPhone(phone);
pd.call();
System.out.println("-----------------------");
pd = new AfterPhone(new BeforePhone(phone));
pd.call();
}
}
总结
1)继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
2)在设计中,应该允许行为可以被扩展,而无须修改现有的代码。
3)组合和委托可用于在运行时动态地加上新的行为。
4)除了继承,装饰者模式也可以让我们扩展行为。
5)装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
6)装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。
7)装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
8)可以用无数个装饰者包装一个组件。
9)装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
10)装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
Java的IO流也是用了装饰模式,BufferedInputStream/BufferedOutputStream/BufferedReader/BufferesWriter,比如:
BufferedWriter bw = new BufferedWriter(new FileWriter(“abc.txt”));
5.抽象工厂 创建型
抽象工厂(abstractFactory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们的具体类。
适用场合:
1.一个系统要独立于它的产品的创建、组合和表示时。
2.一个系统要由多个产品系列中的一个来配置时。
3.当你强调一系列相关的产品对象的设计以便进行联合使用时。
4.当你提供一个产品类库,而只想显示他们的接口而不是实现时。
类图
代码
抽象工厂abstractFactory的代码:
public interface AbstractFactory {
public ProductA factoryA();
public ProductB factoryB();
}
具体工厂类实现AbstractFactory,用于创建对象。
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA factoryA() {
return new ProductA1();
}
@Override
public ProductB factoryB() {
return new ProductB1();
}
}
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA factoryA() {
return new ProductA2();
}
@Override
public ProductB factoryB() {
return new ProductB2();
}
}
产品A的抽象接口(同理产品B):
public interface ProductA {
public void method1();
public void method2();
}
产品A的具体实现(同理产品B):
public class ProductA1 implements ProductA {
@Override
public void method1() {
System.out.println("ProductA1---->method1");
}
@Override
public void method2() {
System.out.println("ProductA1---->method2");
}
}
public class ProductA2 implements ProductA {
@Override
public void method1() {
System.out.println("ProductA2---->method1");
}
@Override
public void method2() {
System.out.println("ProductA2---->method2");
}
}
抽象工厂模式的应用代码:
public class Main {
public static void main(String[] args) {
AbstractFactory abf1 = new ConcreteFactory1();
AbstractFactory abf2 = new ConcreteFactory2();
ProductA a1 = abf1.factoryA();
ProductA a2 = abf2.factoryA();
ProductB b1 = abf1.factoryB();
ProductB b2 = abf2.factoryB();
a1.method1();
a1.method2();
a2.method1();
a2.method2();
b1.method1();
b1.method2();
b2.method1();
b2.method2();
}
}
工厂方法与抽象工厂的区别
工厂方法模式
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例。
区别
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
抽象工厂的应用
抽象工厂模式的使用场景
抽象工厂模式的使用场景定义非常简单:一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式,什么意思呢?例如一个文本编辑器和一个图片处理器,都是软件实体,但是*nix下的文本编辑器和WINDOWS下的文本编辑器虽然功能和界面都相同,但是代码实现是不同的,图片处理器也是类似情况,也就是具有了共同的约束条件:操作系统类型,于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片处理器。
抽象工厂模式的注意实现
在抽象工厂模式的缺点中,我们提到抽象工厂模式的产品族扩展比较困难,但是一定要清楚是产品族扩展困难,而不是产品等级,在该模式下,产品等级是非常容易扩展的,增加一个产品等级,只要增加一个工厂类负责新增加出来的产品生产任务即可,也就是说横向扩展容易,纵向扩展困难。
最佳实践
一个模式在什么情况下才能够使用,是很多读者比较困惑的地方,抽象工厂模式是一个简单的模式,使用的场景非常多,大家在软件产品开发过程中,涉及到不同操作系统的时候,都可以考虑使用抽象工厂模式,例如一个应用,需要在三个不同平台上运行:Windows、Linux、Android上运行,你会怎么设计?分别设计三套不同的应用?非也非也,通过抽象工厂模式屏蔽掉操作系统对应用的影响。三个不同操作系统上的软件功能、应用逻辑、UI都应该是非常类似,唯一不同的是调用不同的工厂方法,由不同的产品类去处理与操作系统交互的信息。
6.建造者 创建型
概念
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
类图
代码
public abstract class Computer {
private String type;
private String cpu;
private String ram;
private String os;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getRam() {
return ram;
}
public void setRam(String ram) {
this.ram = ram;
}
public String getOs() {
return os;
}
public void setOs(String os) {
this.os = os;
}
}
创建两种型号的计算机:
public class T410 extends Computer {
private String hardDisk;
public T410() {
this.setType("ThinkPad T410i");
}
public String getHardDisk() {
return hardDisk;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
@Override
public String toString() {
return "T410 [hardDisk=" + hardDisk + ", getType()=" + getType()
+ ", getCpu()=" + getCpu() + ", getRam()=" + getRam()
+ ", getOs()=" + getOs() + ", getClass()=" + getClass()
+ ", hashCode()=" + hashCode() + "]";
}
}
public class X201 extends Computer {
public X201() {
this.setType("Thinkpad X201i");
}
@Override
public String toString() {
return "X201 [getType()=" + getType() + ", getCpu()=" + getCpu()
+ ", getRam()=" + getRam() + ", getOs()=" + getOs()
+ ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
+ "]";
}
}
在计算机产品类的基础上增加一个ComputerBuilder接口,以及两个的实现类,以便对计算机进行生产:
public interface ComputerBuilder {
void buildCpu();
void buildRam();
void buildHardDisk();
void BuildOs();
Computer getResult();
}
public class T410Builder implements ComputerBuilder {
private T410 computer = new T410();
@Override
public void buildCpu() {
computer.setCpu("i5");
}
@Override
public void buildRam() {
computer.setRam("4G");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("500G");
}
@Override
public void BuildOs() {
computer.setOs("Win7");
}
@Override
public Computer getResult() {
return computer;
}
}
public class X201Builder implements ComputerBuilder {
private X201 computer = new X201();
@Override
public void buildCpu() {
computer.setCpu("i7");
}
@Override
public void buildRam() {
computer.setRam("8G");
}
@Override
public void buildHardDisk() {
//没有HaedDisk
}
@Override
public void BuildOs() {
computer.setOs("Win8");
}
@Override
public Computer getResult() {
return computer;
}
}
再鞥家导演者:
public class ComputerDirector {
ComputerBuilder builder;
public T410 constructT410()
{
builder = new T410Builder();
builder.buildCpu();
builder.buildHardDisk();
builder.BuildOs();
builder.buildRam();
return (T410) builder.getResult();
}
public X201 constructX201()
{
builder = new X201Builder();
builder.buildCpu();
builder.buildHardDisk();
builder.BuildOs();
builder.buildRam();
return (X201) builder.getResult();
}
}
测试:
public class ComputerTest {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
Computer t410 = director.constructT410();
System.out.println(t410);
System.out.println("-------------------------");
Computer x201 = director.constructX201();
System.out.println(x201);
}
}
与抽象工厂的区别
在建造者模式里,有个指导者,由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。
建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心。
7原型模式 创建型
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是选型模式的用意。
原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
类图
代码
以通过群发邮件的例子说明一下:
public class Mail implements Cloneable {
private String receiver;
private String subject;
private String appellation;
private String contxt;
private String tail;
public Mail(String subject, String contxt) {
super();
this.subject = subject;
this.contxt = contxt;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getAppellation() {
return appellation;
}
public void setAppellation(String appellation) {
this.appellation = appellation;
}
public String getTail() {
return tail;
}
public void setTail(String tail) {
this.tail = tail;
}
public String getSubject() {
return subject;
}
public String getContxt() {
return contxt;
}
public Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return mail;
}
}
public class ClientDemo {
private static int MAX_COUNT = 6;
public static void main(String[] args)
{
int i =0;
Mail mail = new Mail("群发标题","群发内容");
mail.setTail("群发结尾");
Random rand = new Random();
while(i < MAX_COUNT)
{
Mail cloneMail = mail.clone();
cloneMail.setAppellation("尊称"+rand.nextInt(100)+":");
cloneMail.setReceiver(rand.nextInt(1000000000)+"@qq.com");
sendMail(cloneMail);
i++;
}
}
public static void sendMail(Mail mail)
{
System.out.println("标题:"+mail.getSubject()+"\t收件人:"+mail.getReceiver()+"\t.....发送成功");
}
}
克隆必须满足的条件
a.对任何的对象x,都有:x.clone() != x,即克隆对象与原对象不是同一个对象。
b.对任何的对象x,都有:x.clone().getClass() == x.get getClass(),即克隆对象与原对象的类型是一样的。
c.如果对象x的equals()方法定义恰当的话,那么x.clone().equals(x)应当是成立的。
浅复制:复制了值类型对象,对于引用类型对象,只复制了引用,它指向原来引用的对象。Java中clone为浅复制。
深复制:对值类型和引用类型的对象都生成一份新的拷贝. Java中可以通过串行化来进行深复制,前提是对象以及对象内部所引用的对象都是可串行化的,否则需要考虑将那些不可串行化的对象可否设为transient,排除在复制过程之外。
//当自定义类的字段的类型不是基本数据类型时,上面实现了clone()方法会导致问题
//我们发现Student的字段如果不是一个引用时,修改clone()得到对象的该字段(name, age)时并不会影响原来的对象,
//但是当字段为一个引用时,修改clone()得到对象的该字段(professor)时并会影响原来的对象。
//上面实现的clone()方法为浅复制(shadow copy)。
//⑴浅复制(浅克隆)
//被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不
//复制它所引用的对象。
//⑵深复制(深克隆)
//被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原
//有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
8 代理模式 结构型
概述
为其他对象提供一种代理以控制对这个对象的访问。
抽象角色:声明真实对象和代理对象的共同接口;
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
类图
代码
IGamePlayer接口对游戏玩家进行抽象:
1 public interface IGamePlayer {
2 public void killBoss();
3 public void UpGrade();
4 }
GamePlayer实现IGamePlayer接口中的方法:
1 public class GamePlayer implements IGamePlayer {
2 private String name = null;
3 public GamePlayer(String name) {
4 super();
5 this.name = name;
6 }
7 @Override
8 public void killBoss() {
9 System.out.println(this.name+"正在killBoss");
10 }
11 @Override
12 public void UpGrade() {
13 System.out.println(this.name+"突然UpGrade");
14 }
15 }
GamePlayerProxy是代理类:
1 public class GamePlayerProxy implements IGamePlayer {
2 private IGamePlayer player = null;
3 public GamePlayerProxy(IGamePlayer player) {
4 super();
5 this.player = player;
6 }
7 // 记录日志
8 private void log() {
9 System.out.println(new Date().toString() + "的时候在打怪");
10 }
11 @Override
12 public void killBoss() {
13 this.log();
14 player.killBoss();
15 }
16 @Override
17 public void UpGrade() {
18 player.UpGrade();
19 this.count();
20 }
21 private void count() {
22 System.out.println("升了1级");
23 }
24 }
测试:
1 public class Main {
2 public static void main(String[] args) {
3 IGamePlayer player = new GamePlayer("张三");
4 IGamePlayer proxy = new GamePlayerProxy(player);
5 proxy.killBoss();
6 proxy.UpGrade();
7 }
8 }
优点
职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了保护了目标对象的作用
高扩展性
9适配器 结构型
概述
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
角色
目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
需要适配的类(Adaptee):需要适配的类或适配者类。
适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。
类图
代码
水饺
package com.yydcdut;
public class ShuiJiao {
public void makeShuiJiao()
{
System.out.println("调制菜肉馅");
System.out.println("包面皮");
System.out.println("包饺子");
}
}
混沌
package com.yydcdut;
public interface HunDun {
public void makeHunDun();
}
食品适配器
package com.yydcdut;
public class FoodAdapter extends ShuiJiao implements HunDun {
@Override
public void makeHunDun() {
super.makeShuiJiao();
System.out.println("混沌和水饺一样的是以面包馅的食品");
}
}
测试
package com.yydcdut;
public class Main {
public static void main(String[] args) {
HunDun h = new FoodAdapter();
h.makeHunDun();
}
}
总结
优点
通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
复用了现存的类,解决了现存类和复用环境要求不一致的问题。
将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
缺点
对于对象适配器来说,更换适配器的实现过程比较复杂。
适用场景
系统需要使用现有的类,而这些类的接口不符合系统的接口。
想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
两个类所做的事情相同或相似,但是具有不同接口的时候。
旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
适配器模式与装饰者模式
它们都可以用来包装对象,本质区别在于
适配器模式:将一个接口转换成另外一个接口。
装饰者模式:不改变接口,只加入职责。
10组合 结构型
概述
在数据结构里面,树结构是很重要,我们可以把树的结构应用到设计模式里面。
例子1:就是多级树形菜单。
例子2:文件和文件夹目录
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
组合模式让你可以优化处理递归或分级数据结构。有许多关于分级数据结构的例子,使得组合模式非常有用武之地。关于分级数据结构的一个普遍性的例子是你每次使 用电脑时所遇到的:文件系统。文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也可以是目录。按照这种方式,计算机的文件系统就 是以递归结构来组织的。如果你想要描述这样的数据结构,那么你可以使用组合模式
分类
将管理子元素的方法定义在Composite类中
将管理子元素的方法定义在Component接口中,这样Leaf类就需要对这些方法空实现。
适用
你想表示对象的部分-整体层次结构
你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
类图
角色
抽象构件角色(component):是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
这个接口可以用来管理所有的子对象。(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
树叶构件角色(Leaf):在组合树中表示叶节点对象,叶节点没有子节点。并在组合中定义图元对象的行为。
树枝构件角色(Composite):定义有子部件的那些部件的行为。存储子部件。在Component接口中实现与子部件有关的操作。
客户角色(Client):通过component接口操纵组合部件的对象。
代码
Company.java
package com.yydcdut;
public interface Company {
public String getInfo();
}
树枝节点ConcreteCompany
package com.yydcdut;
import java.util.ArrayList;
public class ConcreteCompany implements Company {
private ArrayList<Company> companyList = new ArrayList<Company>();
private String name;
private String position;
private int salary;
public ConcreteCompany(String name, String position, int salary) {
super();
this.name = name;
this.position = position;
this.salary = salary;
}
@Override
public String getInfo() {
String info = "";
info = "名称:" + name + "\t职位" + position + "\t薪水" + salary;
return info;
}
public void add(Company company) {
companyList.add(company);
}
public void remove(Company company) {
companyList.remove(company);
}
public ArrayList<Company> getChild() {
return companyList;
}
}
叶子节点Eployee
package com.yydcdut;
public class Employee implements Company {
private String name;
private String position;
private int salary;
public Employee(String name, String position, int salary) {
super();
this.name = name;
this.position = position;
this.salary = salary;
}
@Override
public String getInfo() {
String info = "";
info = "名称:" + name + "\t职位" + position + "\t薪水" + salary;
return info;
}
}
测试:
package com.yydcdut;
public class Main {
public static void main(String[] args) {
//BOSS
ConcreteCompany root = new ConcreteCompany("zhangsan","ceo",10000);
//Manager
ConcreteCompany developDep = new ConcreteCompany("sisi", "yanha", 8000);
ConcreteCompany salesDep = new ConcreteCompany("wangwu", "xiaoshou", 8000);
ConcreteCompany financeDep = new ConcreteCompany("maliu", "caiwu", 8000);
//employee
Employee e1 = new Employee("A", "yanfa", 3000);
Employee e2 = new Employee("B", "yanfa", 4000);
Employee e3 = new Employee("C", "yanfa", 5000);
Employee e4 = new Employee("D", "yanfa", 6000);
Employee e5 = new Employee("E", "xiaoshou", 3000);
Employee e6 = new Employee("F", "xiaoshou", 4000);
Employee e7 = new Employee("G", "xiaoshou", 5000);
Employee e8 = new Employee("H", "xiaoshou", 3000);
Employee e9 = new Employee("I", "caiwu", 3000);
//tree
root.add(developDep);
root.add(salesDep);
root.add(financeDep);
developDep.add(e1);
developDep.add(e2);
developDep.add(e3);
developDep.add(e4);
salesDep.add(e5);
salesDep.add(e6);
salesDep.add(e7);
salesDep.add(e8);
financeDep.add(e9);
System.out.println(root.getInfo());
display(root);
}
public static void display(ConcreteCompany root)
{
for(Company c : root.getChild())
{
if(c instanceof Employee)
{
System.out.println(c.getInfo());
}
else
{
System.out.println("\n"+c.getInfo());
display((ConcreteCompany) c);
}
}
}
}
结果:
名称:zhangsan 职位ceo 薪水10000
名称:sisi 职位yanha 薪水8000
名称:A 职位yanfa 薪水3000
名称:B 职位yanfa 薪水4000
名称:C 职位yanfa 薪水5000
名称:D 职位yanfa 薪水6000
名称:wangwu 职位xiaoshou 薪水8000
名称:E 职位xiaoshou 薪水3000
名称:F 职位xiaoshou 薪水4000
名称:G 职位xiaoshou 薪水5000
名称:H 职位xiaoshou 薪水3000
名称:maliu 职位caiwu 薪水8000
名称:I 职位caiwu 薪水3000
组合模式和其他相关模式
装饰模式(Decorator模式)经常与Composite模式一起使用。当装饰和组合一起使用时,它们通常有一个公共的父类。因此装饰必须支持具有 Add、Remove和GetChild 操作的Component接口。
Flyweight模式让你共享组件,但不再能引用他们的父部件。
(迭代器模式)Itertor可用来遍历Composite。
(观察者模式)Visitor将本来应该分布在Composite和Leaf类中的操作和行为局部化。
总结
组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。
如果你想要创建层次结构,并可以在其中以相同的方式对待所有元素,那么组合模式就是最理想的选择。
11 桥接 结构型
概述
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?
例子:设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:
•第一种设计方案是为每一种形状都提供一套各种颜色的版本。
•第二种设计方案是根据实际需要对形状和颜色进行组合。
方案1:
方案2:
对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
概念
将抽象部分与实现部分分离,使它们都可以独立的变化。
当一个抽象可能有多个实现时,通常用继承来协调他们。抽象类的定义对该抽象的接口。而具体的子类则用不同的方式加以实现,但是此方法有时不够灵活。继承机制将抽象部分与他的实现部分固定在一起,使得难以对抽象部分和实现部分独立地进行修改、扩充和充用。
理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
•抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
•实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
•脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。
适用
你不希望在抽象和他的实现部分之间有一个固定的邦定关系,如在程序的运行时刻实现部分应该可以被选择或者切换。
类的抽象以及他的实现都可以通过生成子类的方法加以扩充。这时bridge模式使你可以对不同的抽象接口和实现部分进行组合,并对他们进行扩充。
对一个抽象的实现部分的修改应该对客户不产生影响,即客户的代码不需要重新编译。
你想对客户完全隐藏抽象的实现部分。
你想在多个实现间共享实现,但同时要求客户并不知道这一点。
类图
组成角色
抽象类(Abstraction):定义抽象类的接口,维护一个指向Implementor类型对象的指针
扩充抽象类(RefinedAbstraction):扩充由Abstraction定义的接口
实现类接口(Implementor):定义实现类的接口,该接口不一定要与 Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲,
Implementor接口仅提供基本操作,而 Abstraction则定义了基于这些基本操作的较高层次的操作。
具体实现类(ConcreteImplementor):实现Implementor接口并定义它的具体实现。
代码
AbstractShape.java
package com.yydcdut;
public abstract class AbstractShape {
Color color;
public AbstractShape(Color color) {
super();
this.color = color;
}
public abstract void draw();
}
颜色接口
package com.yydcdut;
public interface Color {
String getColor();
}
正方形Squre继承图形抽象类
package com.yydcdut;
public class Square extends AbstractShape {
public Square(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("使用"+color.getColor()+"画正方形");
}
}
圆形Cirlce继承图形抽象类
package com.yydcdut;
public class Circle extends AbstractShape {
public Circle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("使用"+color.getColor()+"画圆");
}
}
Red红色实现颜色Color接口
package com.yydcdut;
public class Red implements Color {
@Override
public String getColor() {
return "红色";
}
}
Green绿色实现颜色Color接口
package com.yydcdut;
public class Green implements Color {
@Override
public String getColor() {
return "绿色";
}
}
测试:
package com.yydcdut;
public class Main {
public static void main(String[] args) {
Color color = new Green();
AbstractShape shape = new Square(color);
shape.draw();
}
}
效果:
使用绿色画正方形
总结
Bridge模式有以下一些优点:
分离接口及其实现部分 一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译 Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可。
提高可扩充性 你可以独立地对Abstraction和Implementor层次结构进行扩充。
实现细节对客户透明 你可以对客户隐藏实现细节,例如共享 Implementor对象以及相应的引用计数机制(如果有的话) 。
桥接模式的缺点
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
模拟情景:
现需要提供大中小3种型号的画笔,能够绘制5种不同颜色,如果使用蜡笔,我们需要准备3*5=15支蜡笔,也就是说必须准备15个具体的蜡笔类。而如果使用毛笔的话,只需要3种型号的毛笔,外加5个颜料盒,用3+5=8个类就可以实现15支蜡笔的功能。
实际上,蜡笔和毛笔的关键一个区别就在于笔和颜色是否能够分离。即将抽象化(Abstraction)与实现化(Implementation)脱耦, 使得二者可以独立地变化"。关键就在于能否脱耦。蜡笔的颜色和蜡笔本身是分不开的,所以就造成必须使用15支色彩、大小各异的蜡笔来绘制图画。而毛笔与颜 料能够很好的脱耦,各自独立变化,便简化了操作。在这里,抽象层面的概念是:"毛笔用颜料作画",而在实现时,毛笔有大中小三号,颜料有红绿蓝黑白等5 种,于是便可出现3×5种组合。每个参与者(毛笔与颜料)都可以在自己的自由度上随意转换。
蜡笔由于无法将笔与颜色分离,造成笔与颜色两个自由度无法单独变化,使得只有创建15种对象才能完成任务。
Bridge模式将继承关系转换为组合关系,从而降低了系统间的耦合,减少了代码编写量。
12外观模式 结构型
概述
外观模式,我们通过外观的包装,使应用程序只能看到外观对象,而不会看到具体的细节对象,这样无疑会降低应用程序的复杂度,并且提高了程序的可维护性。
例子:一个电源总开关可以控制四盏灯、一个风扇、一台空调和一台电视机的启动和关闭。该电源总开关可以同时控制上述所有电器设备,电源总开关即为该系统的外观模式设计。
问题
为了降低复杂性,常常将系统划分为若干个子系统。但是如何做到各个系统之间的通信和相互依赖关系达到最小呢?
解决方案
外观模式:为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
适用
在遇到以下情况使用facade模式:
当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性 和可移植性。
当你需要构建一个层次结构的子系统时,使用 facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。
类图
角色
外观角色(Facade):是模式的核心,他被客户client角色调用,知道各个子系统的功能。同时根据客户角色已有的需求预订了几种功能组合\
子系统角色(Subsystem classes):实现子系统的功能,并处理由Facade对象指派的任务。对子系统而言,facade和client角色是未知的,没有Facade的任何相关信息;即没有指向Facade的实例。
客户角色(client):调用facade角色获得完成相应的功能。
代码
package com.yydcdut;
public class Airport {
public void bookTicket(String from, String to)
{
System.out.println("订购了从"+from+"到"+"的机票");
}
}
package com.yydcdut;
public class Restaurant {
public void reserve(int num)
{
System.out.println("定了一桌"+num+"个人的酒席");
}
}
package com.yydcdut;
public class Hotel {
public void reserve(int days)
{
System.out.println("定了"+days+"天的房间");
}
}
package com.yydcdut;
public class Chauffeur {
public void drive(String to)
{
System.out.println("司机开车去"+to);
}
}
秘书类,充当Facade
package com.yydcdut;
public class Secretary {
private Chauffeur cha = new Chauffeur();
private Hotel hotel = new Hotel();
private Restaurant res = new Restaurant();
private Airport air = new Airport();
public void trip(String to,int days)
{
air.bookTicket("上海", to);
cha.drive("机场");
hotel.reserve(days);
}
public void repast(int num)
{
res.reserve(num);
cha.drive("酒店");
}
}
测试:
package com.yydcdut;
public class Boss {
public static void main(String[] arg)
{
Secretary s = new Secretary();
System.out.println("老板告诉秘书要到北京出差10天");
s.trip("北京", 10);
System.out.println("-----------------------");
System.out.println("老板告诉秘书要请8个人吃饭");
s.repast(8);
}
}
结果:
老板告诉秘书要到北京出差10天
订购了从上海到的机票
司机开车去机场
定了10天的房间
-----------------------
老板告诉秘书要请8个人吃饭
定了一桌8个人的酒席
司机开车去酒店
总结
Facade模式有下面一些优点:
对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
Facade模式的缺点
不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
与其他相关模式
抽象工厂模式:Abstract Factory式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。 Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类。
中介模式:Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能。然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道Facade的存在。 通常来讲,仅需要一个Facade对象,因此Facade对象通常属于Singleton模式。
Adapter模式:适配器模式是将一个接口通过适配来间接转换为另一个接口。外观模式的话,其主要是提供一个整洁的一致的接口给客户端。
13 观察者模式 行为型
观察者模式 Observer
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。
这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。
观察者模式的组成
抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
具体主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
Code
//抽象观察者角色
public interface Watcher
{
public void update(String str);
}
定义抽象的主题角色,即抽象的被观察者,在其中声明方法(添加、移除观察者,通知观察者):
//抽象主题角色,watched:被观察
public interface Watched
{
public void addWatcher(Watcher watcher);
public void removeWatcher(Watcher watcher);
public void notifyWatchers(String str);
}
定义具体的观察者:
public class ConcreteWatcher implements Watcher
{
@Override
public void update(String str)
{
System.out.println(str);
}
}
具体的主题角色:
import java.util.ArrayList;
import java.util.List;
public class ConcreteWatched implements Watched
{
// 存放观察者
private List<Watcher> list = new ArrayList<Watcher>();
@Override
public void addWatcher(Watcher watcher)
{
list.add(watcher);
}
@Override
public void removeWatcher(Watcher watcher)
{
list.remove(watcher);
}
@Override
public void notifyWatchers(String str)
{
// 自动调用实际上是主题进行调用的
for (Watcher watcher : list)
{
watcher.update(str);
}
}
}
编写测试类:
public class Test
{
public static void main(String[] args)
{
Watched girl = new ConcreteWatched();
Watcher watcher1 = new ConcreteWatcher();
Watcher watcher2 = new ConcreteWatcher();
Watcher watcher3 = new ConcreteWatcher();
girl.addWatcher(watcher1);
girl.addWatcher(watcher2);
girl.addWatcher(watcher3);
girl.notifyWatchers("开心");
}
}
Observable类
Observable类用于创建可以观测到你的程序中其他部分的子类。当这种子类的对象发生变化时,观测类被通知。
观测类必须实现定义了update()方法的Observer接口。
当一个观测程序被通知到一个被观测对象的改变时,update()方法被调用。
显然,Observable是一个抽象的主题对象。
一个被观测的对象必须服从下面的两个简单规则:
第一,如果它被改变了,它必须调用setChanged()方法。
第二,当它准备通知观测程序它的改变时,它必须调用notifyObservers()方法,这导致了在观测对象中对update()方法的调用。
注意:如果在调用notifyObservers()方法之前没有调用setChanged()方法,就不会有什么动作发生。
notifyObservers()方法中包含clearChanged()方法,将标志变量置回原值。
notifyObservers()方法采用的是从后向前的遍历方式,即最后加入的观察者最先被调用update()方法。
Code
定义一个主题对象进行倒数计数,数字每次改变时,它的观察者收到这个数字。
一个观察者每次收到通知后打印出数字,另一个观察者在数字小于等于5时才开始打印。
import java.util.Observable;
import java.util.Observer;
class WatchedCounter extends Observable
{
public void countdown(int number)
{
for (; number >= 0; --number)
{
// 设置改变变量
setChanged();
// 通知所有观察者,将number作为参数信息传递给观察者
notifyObservers(number);
}
}
}
class Watcher1 implements Observer
{
@Override
public void update(Observable arg0, Object arg1)
{
System.out.println("Watcher1's number: " + arg1);
}
}
class Watcher2 implements Observer
{
@Override
public void update(Observable arg0, Object arg1)
{
if (((Integer) arg1).intValue() <= 5)
{
System.out.println("Watcher2's number: " + arg1);
}
}
}
public class ObserverTest
{
public static void main(String[] args)
{
WatchedCounter watchedCounter = new WatchedCounter();
Watcher1 watcher1 = new Watcher1();
Watcher2 watcher2 = new Watcher2();
//添加观察者
watchedCounter.addObserver(watcher1);
watchedCounter.addObserver(watcher2);
//开始倒数计数
watchedCounter.countdown(10);
}
}
14 单例模式 创建型
懒汉,线程不安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉,线程安全
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
饿汉,变种
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
双重校验锁
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
总结
有两个问题需要注意:
1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
对第二个问题修复的办法是:
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}
15 享元模式 结构型
概述
当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。例如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用较多的内存空间,那么我们如何去避免系统中出现大量相同或相似的对象,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作?享元模式正为解决这一类问题而诞生。享元模式通过共享技术实现相同或相似对象的重用,在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)。我们可以针对每一个不同的字符创建一个享元对象,将其放在享元池中,需要时再从享元池取出。
享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Intrinsic State)和外部状态(Extrinsic State)。
(1) 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。如字符的内容,不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会变成“b”。
(2) 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的“a”是红色的,有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜色和大小是两个独立的外部状态,它们可以独立变化,相互之间没有影响,客户端可以在使用时将外部状态注入享元对象中。
正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
享元模式
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
结构
享元模式结构较为复杂,一般结合工厂模式一起使用,在它的结构图中包含了一个享元工厂类。
在享元模式结构图中包含如下几个角色:
● Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
● ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
● UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
● FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
class FlyweightFactory {
//定义一个HashMap用于存储享元对象,实现享元池
private HashMap
flyweights =
new HashMap();
public
Flyweight getFlyweight(String
key){
//如果对象存在,则直接从享元池获取
if(flyweights.
containsKey(key)){
return
(Flyweight)flyweights.
get(key);
}
//如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
else {
Flyweight fw =
new
ConcreteFlyweight();
flyweights.
put(key,fw);
return fw;
}
}
}
享元类的设计是享元模式的要点之一,在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入的方式添加到享元类中。典型的享元类代码如下所示:
class Flyweight {
//内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
private String
intrinsicState;
public Flyweight(String
intrinsicState) {
this.
intrinsicState=intrinsicState;
}
//外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时也可以传入不同的外部状态
public void operation(String extrinsicState) {
......
}
}
Demo
一个咖啡店有几种口味的咖啡(拿铁、摩卡、卡布奇诺等等),如果这家店接到分订单要几十杯咖啡。那么显然咖啡的口味就可以设置成共享的,而不必为每一杯单独生成。代码实现如下:
public
abstract
class Order {
// 执行卖出动作
public abstract void sell();
}
public
class FlavorOrder extends Order {
public String
flavor;
// 获取咖啡口味
public FlavorOrder(String
flavor) {
this.
flavor = flavor;
}
@Override
public void sell() {
//
TODO Auto-generated method stub
System.
out.
println(
"卖出一份" + flavor +
"的咖啡。");
}
}
public
class FlavorFactory {
private
Map<String, Order> flavorPool =
new
HashMap<String, Order>();
// 静态工厂,负责生成订单对象
private
static
FlavorFactory flavorFactory =
new
FlavorFactory();
private FlavorFactory() {}
public static
FlavorFactory getInstance() {
return
flavorFactory;
}
public
Order getOrder(String flavor) {
Order order =
null;
if
(flavorPool.
containsKey(flavor)) {
// 如果此映射包含指定键的映射关系,则返回
true
order =
flavorPool.
get(flavor);
}
else {
order =
new
FlavorOrder(flavor);
flavorPool.
put(flavor,
order);
}
return order;
}
public int getTotalFlavorsMade() {
return flavorPool.
size();
}
}
public
class Client {
// 客户下的订单
private
static
List<Order> orders =
new
ArrayList<Order>();
// 订单对象生成工厂
private
static
FlavorFactory flavorFactory;
// 增加订单
private static void takeOrders(String
flavor) {
orders.
add(flavorFactory.
getOrder(flavor));
}
public static void main(String[]
args) {
// 订单生成工厂
flavorFactory =
FlavorFactory.
getInstance();
// 增加订单
takeOrders(
"摩卡");
takeOrders(
"卡布奇诺");
takeOrders(
"香草星冰乐");
takeOrders(
"香草星冰乐");
takeOrders(
"拿铁");
takeOrders(
"卡布奇诺");
takeOrders(
"拿铁");
takeOrders(
"卡布奇诺");
takeOrders(
"摩卡");
takeOrders(
"香草星冰乐");
takeOrders(
"卡布奇诺");
takeOrders(
"摩卡");
takeOrders(
"香草星冰乐");
takeOrders(
"拿铁");
takeOrders(
"拿铁");
// 卖咖啡
for (Order
order : orders) {
order.
sell();
}
// 打印生成的订单java对象数量
System.
out.
println(
"\n客户一共买了 " + orders.
size() +
"
杯咖啡!
");
// 打印生成的订单java对象数量
System.
out.
println(
"共生成了 " +
flavorFactory.
getTotalFlavorsMade()
+
"
个
FlavorOrder java对象! ");
}
}
卖出一份摩卡的咖啡。
卖出一份卡布奇诺的咖啡。
卖出一份香草星冰乐的咖啡。
卖出一份香草星冰乐的咖啡。
卖出一份拿铁的咖啡。
卖出一份卡布奇诺的咖啡。
卖出一份拿铁的咖啡。
卖出一份卡布奇诺的咖啡。
卖出一份摩卡的咖啡。
卖出一份香草星冰乐的咖啡。
卖出一份卡布奇诺的咖啡。
卖出一份摩卡的咖啡。
卖出一份香草星冰乐的咖啡。
卖出一份拿铁的咖啡。
卖出一份拿铁的咖啡。
客户一共买了 15 杯咖啡!
共生成了 4 个 FlavorOrder java对象!
单纯享元模式和复合享元模式
单纯享元模式
复合享元模式
将一些单纯享元对象使用组合模式加以组合,还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
通过复合享元模式,可以确保复合享元类CompositeConcreteFlyweight中所包含的每个单纯享元类ConcreteFlyweight都具有相同的外部状态,而这些单纯享元的内部状态往往可以不同。如果希望为多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式。
与其他模式的联用
享元模式通常需要和其他模式一起联用,几种常见的联用方式如下:
(1)在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。
(2)在一个系统中,通常只有唯一一个享元工厂,因此可以使用单例模式进行享元工厂类的设计。
(3)享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态。
享元模式与String类
JDK类库中的String类使用了享元模式,我们通过如下代码来加以说明:
class Demo {
public static void main(String
args[]) {
String str1 =
"abcd";
String str2 =
"abcd";
String str3 =
"ab" +
"cd";
String str4 =
"ab";
str4 +=
"cd";
System.
out.
println(str1 == str2);
System.
out.
println(str1 == str3);
System.
out.
println(str1 == str4);
str2 +=
"e";
System.
out.
println(str1 == str2);
}
}
在Java语言中,如果每次执行类似String str1="abcd"的操作时都创建一个新的字符串对象将导致内存开销很大,因此如果第一次创建了内容为"abcd"的字符串对象str1,下一次再创建内容相同的字符串对象str2时会将它的引用指向"abcd",不会重新分配内存空间,从而实现了"abcd"在内存中的共享。上述代码输出结果如下:
true
true
false
false
可以看出,前两个输出语句均为true,说明str1、str2、str3在内存中引用了相同的对象;如果有一个字符串str4,其初值为"ab",再对它进行操作str4 += "cd",此时虽然str4的内容与str1相同,但是由于str4的初始值不同,在创建str4时重新分配了内存,所以第三个输出语句结果为false;最后一个输出语句结果也为false,说明当对str2进行修改时将创建一个新的对象,修改工作在新对象上完成,而原来引用的对象并没有发生任何改变,str1仍然引用原有对象,而str2引用新对象,str1与str2引用了两个完全不同的对象。
总结
当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案,它通过共享技术实现相同或相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。相比其他结构型设计模式,享元模式的使用频率并不算太高,但是作为一种以“节约内存,提高性能”为出发点的设计模式,它在软件开发中还是得到了一定程度的应用。
1.主要优点
享元模式的主要优点如下:
(1) 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
(2) 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
2.主要缺点
享元模式的主要缺点如下:
(1) 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
(2) 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
3.适用场景
在以下情况下可以考虑使用享元模式:
(1) 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
(2) 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
(3) 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
16 责任链模式 行为型
职责链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。
在职责链模式结构图中包含如下几个角色:
● Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在抽象处理者中定义了一个抽象处理者类型的对象(如结构图中的successor),作为其对下家的引用。通过该引用,处理者可以连成一条链。
●ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。
在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
职责链模式的核心在于抽象处理者类的设计,抽象处理者的典型代码如下所示:
abstract
class Handler {
//维持对下家的引用
protected Handler
successor;
public void setSuccessor(Handler
successor) {
this.
successor=successor;
}
public abstract void handleRequest(String
request);
}
上述代码中,抽象处理者类定义了对下家的引用对象,以便将请求转发给下家,该对象的访问符可设为protected,在其子类中可以使用。在抽象处理者类中声明了抽象的请求处理方法,具体实现交由子类完成。
具体处理者是抽象处理者的子类,它具有两大作用:第一是处理请求,不同的具体处理者以不同的形式实现抽象请求处理方法handleRequest();第二是转发请求,如果该请求超出了当前处理者类的权限,可以将该请求转发给下家。具体处理者类的典型代码如下:
class ConcreteHandler extends Handler {
public void handleRequest(String
request) {
if (
请求满足条件)
{
//处理请求
}
else {
this.
successor.
handleRequest(request);
//转发请求
}
}
}
在具体处理类中通过对请求进行判断可以做出相应的处理。
需要注意的是,职责链模式并不创建职责链,职责链的创建工作必须由系统的其他部分来完成,一般是在使用该职责链的客户端中创建职责链。职责链模式降低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。
Demo
// 全局变量,接口类型
/**
* 使用Java中的interface定义全局变量,可根据具体需要在
* 具体的包中使用静态导入相关的全局变量,语法如下:
* import static package01.package02.*;
*/
interface Levels {
public
static
final
int LEVEL_01 =
1;
public
static
final
int LEVEL_02 =
2;
public
static
final
int LEVEL_03 =
3;
}
// 抽象请求类
abstract
class AbstractRequest {
private String
content =
null;
public AbstractRequest(String
content) {
this.
content =
content;
}
public
String getContent() {
return
this.
content;
}
// 获得请求的级别
public abstract int getRequestLevel();
}
// 具体请求类01
class Request01 extends AbstractRequest {
public Request01(String
content) {
super(content);
}
@Override
public int getRequestLevel() {
return Levels.
LEVEL_01;
}
}
// 具体请求类02
class Request02 extends AbstractRequest {
public Request02(String
content) {
super(content);
}
@Override
public int getRequestLevel() {
return Levels.
LEVEL_02;
}
}
// 具体请求类03
class Request03 extends AbstractRequest {
public Request03(String
content) {
super(content);
}
@Override
public int getRequestLevel() {
return Levels.
LEVEL_03;
}
}
// 抽象处理者类,
abstract
class AbstractHandler {
// 责任链的下一个节点,即处理者
private
AbstractHandler nextHandler =
null;
// 捕获具体请求并进行处理,或是将请求传递到责任链的下一级别
public final void handleRequest(AbstractRequest
request) {
// 若该请求与当前处理者的级别层次相对应,则由自己进行处理
if (
this.
getHandlerLevel() ==
request.
getRequestLevel()) {
this.
handle(request);
}
else {
// 当前处理者不能胜任,则传递至职责链的下一节点
if (
this.
nextHandler !=
null) {
System.
out.
println(
"当前 处理者-0" +
this.
getHandlerLevel()
+
" 不足以处理 请求-0" + request.
getRequestLevel());
// 这里使用了递归调用
this.
nextHandler.
handleRequest(request);
}
else {
System.
out.
println(
"职责链上的所有处理者都不能胜任该请求...");
}
}
}
// 设置责任链中的下一个处理者
public void setNextHandler(AbstractHandler
nextHandler) {
this.
nextHandler =
nextHandler;
}
// 获取当前处理者的级别
protected abstract int getHandlerLevel();
// 定义链中每个处理者具体的处理方式
protected abstract void handle(AbstractRequest
request);
}
// 具体处理者-01
class Handler01 extends AbstractHandler {
@Override
protected int getHandlerLevel() {
return Levels.
LEVEL_01;
}
@Override
protected void handle(AbstractRequest
request) {
System.
out.
println(
"处理者-01 处理 " + request.
getContent() +
"\n");
}
}
// 具体处理者-02
class Handler02 extends AbstractHandler {
@Override
protected int getHandlerLevel() {
return Levels.
LEVEL_02;
}
@Override
protected void handle(AbstractRequest
request) {
System.
out.
println(
"处理者-02 处理 " + request.
getContent()+
"\n");
}
}
// 具体处理者-03
class Handler03 extends AbstractHandler {
@Override
protected int getHandlerLevel() {
return Levels.
LEVEL_03;
}
@Override
protected void handle(AbstractRequest
request) {
System.
out.
println(
"处理者-03 处理 " + request.
getContent()+
"\n");
}
}
// 测试类
public
class Client {
public static void main(String[]
args) {
// 创建指责链的所有节点
AbstractHandler
handler01 =
new
Handler01();
AbstractHandler
handler02 =
new
Handler02();
AbstractHandler
handler03 =
new
Handler03();
// 进行链的组装,即头尾相连,一层套一层
handler01.
setNextHandler(handler02);
handler02.
setNextHandler(handler03);
// 创建请求并提交到指责链中进行处理
AbstractRequest
request01 =
new
Request01(
"请求-01");
AbstractRequest
request02 =
new
Request02(
"请求-02");
AbstractRequest
request03 =
new
Request03(
"请求-03");
// 每次提交都是从链头开始遍历
handler01.
handleRequest(request01);
handler01.
handleRequest(request02);
handler01.
handleRequest(request03);
}
}
处理者-01 处理 请求-01
当前 处理者-01 不足以处理 请求-02
处理者-02 处理 请求-02
当前 处理者-01 不足以处理 请求-03
当前 处理者-02 不足以处理 请求-03
处理者-03 处理 请求-03
纯与不纯的职责链模式
职责链模式可分为纯的职责链模式和不纯的职责链模式两种:
纯的职责链模式
一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。而且在纯的职责链模式中,要求一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。在前面的采购单审批实例中应用的是纯的职责链模式。
不纯的职责链模式
在一个不纯的职责链模式中允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。Java AWT 1.0中的事件处理模型应用的是不纯的职责链模式,其基本原理如下:由于窗口组件(如按钮、文本框等)一般都位于容器组件中,因此当事件发生在某一个组件上时,先通过组件对象的handleEvent()方法将事件传递给相应的事件处理方法,该事件处理方法将处理此事件,然后决定是否将该事件向上一级容器组件传播;上级容器组件在接到事件之后可以继续处理此事件并决定是否继续向上级容器组件传播,如此反复,直到事件到达顶层容器组件为止;如果一直传到最顶层容器仍没有处理方法,则该事件不予处理。每一级组件在接收到事件时,都可以处理此事件,而不论此事件是否在上一级已得到处理,还存在事件未被处理的情况。显然,这就是不纯的职责链模式,早期的Java AWT事件模型(JDK 1.0及更早)中的这种事件处理机制又叫事件浮升(Event Bubbling)机制。从Java.1.1以后,JDK使用观察者模式代替职责链模式来处理事件。目前,在JavaScript中仍然可以使用这种事件浮升机制来进行事件处理。
总结
职责链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。在软件开发中,如果遇到有多个对象可以处理同一请求时可以应用职责链模式,例如在Web应用开发中创建一个过滤器(Filter)链来对请求数据进行过滤,在工作流系统中实现公文的分级审批等等,使用职责链模式可以较好地解决此类问题。
主要优点
职责链模式的主要优点如下:
职责链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度。
请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。
在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。
在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。
主要缺点
职责链模式的主要缺点如下:
由于一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。
如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
适用场景
在以下情况下可以考虑使用职责链模式:
有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。
17 命令模式 行为型
概述
命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
在命令模式结构图中包含如下几个角色:
● Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
● ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。
● Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
● Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的execute()方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。
典型的抽象命令类代码如下所示:
abstract
class Command {
public abstract void execute();
}
对于请求发送者即调用者而言,将针对抽象命令类进行编程,可以通过构造注入或者设值注入的方式在运行时传入具体命令类对象,并在业务方法中调用命令对象的execute()方法,其典型代码如下所示:
class Invoker {
private Command
command;
//构造注入
public Invoker(Command
command) {
this.
command =
command;
}
//设值注入
public void setCommand(Command
command) {
this.
command =
command;
}
//业务方法,用于调用命令类的execute()方法
public void call() {
command.
execute();
}
}
具体命令类继承了抽象命令类,它与请求接收者相关联,实现了在抽象命令类中声明的execute()方法,并在实现时调用接收者的请求响应方法action(),其典型代码如下所示:
class ConcreteCommand extends Command {
private Receiver
receiver;
//维持一个对请求接收者对象的引用
public void execute() {
receiver.
action();
//调用请求接收者的业务处理方法action()
}
}
请求接收者Receiver类具体实现对请求的业务处理,它提供了action()方法,用于执行与请求相关的操作,其典型代码如下所示:
class Receiver {
public void action() {
//具体操作
}
}
Demo
接收者角色,由录音机类扮演
public
class AudioPlayer {
public void play(){
System.
out.
println(
"播放...");
}
public void rewind(){
System.
out.
println(
"倒带...");
}
public void stop(){
System.
out.
println(
"停止...");
}
}
抽象命令角色类
public
interface Command {
/**
* 执行方法
*/
public void execute();
}
具体命令角色类
public
class PlayCommand implements Command {
private AudioPlayer
myAudio;
public PlayCommand(AudioPlayer
audioPlayer){
myAudio =
audioPlayer;
}
/**
* 执行方法
*/
@Override
public void execute() {
myAudio.
play();
}
}
public
class RewindCommand implements Command {
private AudioPlayer
myAudio;
public RewindCommand(AudioPlayer
audioPlayer){
myAudio =
audioPlayer;
}
@Override
public void execute() {
myAudio.
rewind();
}
}
public
class StopCommand implements Command {
private AudioPlayer
myAudio;
public StopCommand(AudioPlayer
audioPlayer){
myAudio =
audioPlayer;
}
@Override
public void execute() {
myAudio.
stop();
}
}
请求者角色,由键盘类扮演
public
class Keypad {
private Command
playCommand;
private Command
rewindCommand;
private Command
stopCommand;
public void setPlayCommand(Command
playCommand) {
this.
playCommand =
playCommand;
}
public void setRewindCommand(Command
rewindCommand) {
this.
rewindCommand =
rewindCommand;
}
public void setStopCommand(Command
stopCommand) {
this.
stopCommand =
stopCommand;
}
/**
* 执行播放方法
*/
public void play(){
playCommand.
execute();
}
/**
* 执行倒带方法
*/
public void rewind(){
rewindCommand.
execute();
}
/**
* 执行播放方法
*/
public void stop(){
stopCommand.
execute();
}
}
客户端角色,由茱丽小女孩扮演
public
class Julia {
public static void main(String[]args){
//创建接收者对象
AudioPlayer
audioPlayer =
new
AudioPlayer();
//创建命令对象
Command
playCommand =
new
PlayCommand(audioPlayer);
Command
rewindCommand =
new
RewindCommand(audioPlayer);
Command
stopCommand =
new
StopCommand(audioPlayer);
//创建请求者对象
Keypad keypad =
new
Keypad();
keypad.
setPlayCommand(playCommand);
keypad.
setRewindCommand(rewindCommand);
keypad.
setStopCommand(stopCommand);
//测试
keypad.
play();
keypad.
rewind();
keypad.
stop();
keypad.
play();
keypad.
stop();
}
}
播放...
倒带...
停止...
播放...
停止...
宏命令
宏命令(Macro Command)又称为组合命令,它是组合模式和命令模式联用的产物。宏命令是一个具体命令类,它拥有一个集合属性,在该集合中包含了对其他命令对象的引用。通常宏命令不直接与请求接收者交互,而是通过它的成员来调用接收者的方法。当调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员可以是简单命令,还可以继续是宏命令。执行一个宏命令将触发多个具体命令的执行,从而实现对命令的批处理,其结构如图所示:
宏命令Demo
系统需要一个代表宏命令的接口,以定义出具体宏命令所需要的接口。
public
interface MacroCommand extends Command {
/**
* 宏命令聚集的管理方法
* 可以添加一个成员命令
*/
public void add(Command
cmd);
/**
* 宏命令聚集的管理方法
* 可以删除一个成员命令
*/
public void remove(Command
cmd);
}
具体的宏命令MacroAudioCommand类负责把个别的命令合成宏命令。
public
class MacroAudioCommand implements MacroCommand {
private
List<Command> commandList =
new
ArrayList<Command>();
/**
* 宏命令聚集管理方法
*/
@Override
public void add(Command
cmd) {
commandList.
add(cmd);
}
/**
* 宏命令聚集管理方法
*/
@Override
public void remove(Command
cmd) {
commandList.
remove(cmd);
}
/**
* 执行方法
*/
@Override
public void execute() {
for(Command cmd
: commandList){
cmd.
execute();
}
}
}
客户端类Julia
public
class Julia {
public static void main(String[]args){
//创建接收者对象
AudioPlayer
audioPlayer =
new
AudioPlayer();
//创建命令对象
Command
playCommand =
new
PlayCommand(audioPlayer);
Command
rewindCommand =
new
RewindCommand(audioPlayer);
Command
stopCommand =
new
StopCommand(audioPlayer);
MacroCommand
marco =
new
MacroAudioCommand();
marco.
add(playCommand);
marco.
add(rewindCommand);
marco.
add(stopCommand);
marco.
execute();
}
}
播放...
倒带...
停止...
总结
命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,请求发送者通过命令对象来间接引用请求接收者,使得系统具有更好的灵活性和可扩展性。在基于GUI的软件开发,无论是在电脑桌面应用还是在移动应用中,命令模式都得到了广泛的应用。
主要优点
命令模式的主要优点如下:
降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
可以比较容易地设计一个命令队列或宏命令(组合命令)。
为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
主要缺点
命令模式的主要缺点如下:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
适用场景
在以下情况下可以考虑使用命令模式:
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
系统需要将一组操作组合在一起形成宏命令。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用