Java开发中23种设计模式详细介绍
- 设计模式介绍
- 设计模式分类
- 设计模式六大原则
- 开闭原则(Open Close Principle)
- 里氏代换原则(Liskov Substitution Principle)
- 依赖倒转原则(Dependence Inversion Principle)
- 接口隔离原则(Interface Segregation Principle)
- 迪米特法则(最少知道原则)(Demeter Principle)
- 合成复用原则(Composite Reuse Principle)
- Java中23种设计模式
- 创建型模式
- 工厂方法模式(Factory Method)
- 普通工厂模式
- 多个工厂方法模式
- 静态工厂方法模式
- 总结
- 抽象工厂模式(Abstract Factory)
- 单例模式(Singleton)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 结构型模式
- 适配器模式(Adapter Pattern)
- 类的适配器模式
- 对象的适配器模式
- 接口的适配器模式
- 装饰器模式(Decorator)
- 代理模式(Proxy)
- 外观模式(Facade)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 享元模式
- 行为型模式
- 策略模式(Strategy)
- 模板方法模模式(Template Method)
- 观察者模式(Observer)
- 迭代子模式(Iterator)
- 责任链模式(Chain of Responsibility)
- 命令模式(Command)
- 备忘录模式(Memento)
- 状态模式(State)
- 访问者模式(Visitor)
- 中介者模式(Mediator)
- 解释器模式(Interpreter)
设计模式介绍
- 设计模式(Design Patterns):一套被反复使用,多数人知晓,经过分类编目,代码设计的总结使用设计模式是为了可重用代码,让代码更容易理解,保证代码可靠性
- 项目中合理运用设计模式可以完美的解决很多问题,每种模式都有相应的原理与之对应,
- 每个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案
设计模式分类
- 总体来说,设计模式分为三大类:
- 创建型模式(5种):工厂方法模式抽象工厂模式单例模式建造者模式原型模式
- 结构型模式(7种):适配器模式装饰器模式代理模式外观模式桥接模式组合模式享元模式
- 行为型模式(11种):策略模式模板方法模式观察者模式迭代子模式责任链模式命令模式备忘录模式状态模式访问者模式中介者模式解释器模式
- 其余两类模式:
- 并发型模式
- 线程池模式
设计模式六大原则
单一职责原则(Single Responsibility Principle) - 这里的设计模式原则,主要讨论的是Java面向对象编程设计中设计原则,单一职责原则由于其适用的普遍性,个人认为不放在六大原则之中
单一职责原则:一个类只负责一项职责不能存在多于一个导致类变更的原因单一职责原则符合“高内聚,低耦合”的思想单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则
开闭原则(Open Close Principle)
- 开闭原则:对扩展开放,对修改关闭
- 程序进行扩展的时候,不能修改原有的代码,实现一个热插拔的效果
- 为了使程序扩展性好,易于维护和升级:需要使用接口和抽象类
里氏代换原则(Liskov Substitution Principle)
- 里氏代换原则:任何基类可以出现的地方,子类一定可以出现
- LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受影响时,基类才能真正被复用,衍生类也能够在基类的基础上增加新的行为
- 里氏代换原则是对实现抽象化的具体步骤的规范:里氏代换原则是对开闭原则的补充实现开闭原则的关键步骤就是抽象化基类与子类的继承关系就是抽象化的具体实现
依赖倒转原则(Dependence Inversion Principle)
- 依赖倒转原则:针对接口编程,依赖于抽象而不依赖于具体
- 依赖倒转原则是开闭原则的基础
接口隔离原则(Interface Segregation Principle)
- 接口隔离原则:使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度
- 从接口隔离原则可以看出:设计模式就是一个软件的设计思想
- 从大型软件架构出发,为了升级和维护方便:降低依赖,降低耦合
迪米特法则(最少知道原则)(Demeter Principle)
- 迪米特法则:最少知道原则,一个实体应当尽量少的与其它实体发生相互作用,使得功能模块相互独立
合成复用原则(Composite Reuse Principle)
- 合成复用原则:尽量使用合成或者聚合的方式,而不是使用继承
Java中23种设计模式
创建新模式
工厂方法模式(Factory Method)
工厂方法模式分为三种:普通工厂模式,多个工厂方法模式,静态工厂方法模式
普通工厂模式
- 建立一个工厂类,对实现了同一接口的一些类进行实例的创建
多个工厂方法模式
- 多个工厂方法模式是对普通工厂方法模式的改进
- 普通工厂方法模式中,如果传递的字符串出错,则不能创建对象
- 多个工厂方法模式提供多个工厂方法,分别创建对象
静态工厂方法模式
- 将多个工厂方法模式中的方法设置为静态方法,不需要创建实例,直接调用即可
总结
- 工厂模式适合出现大量的产品需要创建,并且具有共同的接口,可以通过工厂方法模式创建:普通工厂模式:如如果传入字符串有误,就不能创建对象静态工厂方法模式相对于多个工厂方法模式,不需要实例化工厂类大多数情况下,采用静态工厂方法模式
抽象工厂模式(Abstract Factory)
- 工厂方法模式问题:类的创建依赖工厂类。如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则
- 抽象工厂模式:创建多个工厂类,一旦需要增加新的功能,直接增加工厂类就可以,不需要修改之前的工厂类
- 抽象工厂模式的优点就是拓展性强:如果需要增加一个功能,例如:发及时信息只需做一个实现类,实现Sender接口做一个工厂类,实现Provider接口
单例模式(Singleton)
- 单例模式:保证在一个JVM中,一个单例对象只有一个实例存在
- 单例模式的优点:某些类创建比较繁琐,对于一些大型对象,可以减少很大的系统开销省去了new操作符,降低了系统内存的使用频率,减轻GC(Garbage Collection-垃圾回收)压力有些类比如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个,系统会完全混乱,所有只有使用单例模式,才能保证核心交易服务器独立控制整个流程
- 考虑到多线程安全,首先会想到对getInstance方法加synchronized
由于synchronized锁住的是这个对象,这样的用法,每次调用getInstance(),都要对对象上锁,在性能上会有所下降。
- 只有在第一次创建对象的时候需要加锁,之后就不需要了
这样似乎解决了问题,将synchronized关键字加入内部,这样在调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要的加锁,性能得到了提升,但是这样的情况还是有问题的
- 存在这样的情况:在Java中创建对象和赋值操作是分开进行的即instance=new Singleton()是分两步执行的JVM并不保证这两个操作的先后顺序:有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员然后再去初始化这个Singleton实例这样就可能会出错
- 示例:A,B两个线程A,B线程同时进入第一个if判断A首先进入synchronized块,由于instance为null,执行instance=new Singleton()由于JVM内部的优化机制,JVM先划出一些分配给Singleton的空白内存,并赋值给instance成员,此时还没有开始初始化这个实例,然后A离开了synchronized块B进入synchronized,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序此时B线程打算使用Singleton实例,发现它还没有初始化,于是产生错误
- 代码需要进一步优化
- 实际情况是:
- 单例模式使用内部类来维护单例的实现
- JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是互斥的
- 当第一次调用getInstance时,JVM能够保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕
- 该方法也只会在第一次调用的时候采用互斥机制,可以完美解决低性能的问题
这种方法,如果在构造函数中抛出异常,实例将永远不会创建,也会出错。
只能根据实际场景,选择最适合应用场景的实现方法
- 因为只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的
- 采用“影子实例”的方法为单例对象的属性同步更新
- 单例模式的特点:单例模式理解起来简单,具体实现起来是有一定难度的\同步异步synchronized关键字锁定的是对象,使用的时候要在恰当的地方使用:注意需要使用锁的对象和过程,有时候不是整个对象及整个过程都需要锁
- 采用类的静态方法,可以实现单例模式的效果类的静态方法和单例模式的区别:静态类不能实现接口:从类的角度说,是可以的,但是这样就会破坏静态了接口中不允许有static修饰的方法,即使实现了也是非静态的单例可以被延迟启动:静态类在第一次加载时初始化单例延迟加载,是因为有些类比较庞大,延迟加载有助于提升性能单例可以被继承:单例中的方法可以被重写静态类内部方法都是static,无法重写单例比较灵活:从实现上讲,单例只是一个普通的Java类,只要满足单例的基本要求,可以随心所欲地实现其它功能静态类不行单例模式内部可以就是用一个静态类实现
建造者模式(Builder)
- 工厂模式提供的是创建单个类的模式
- 建造者模式:将各种产品集中起来进行管理,用来创建复合对象复合对象:指某个类具有不同的属性
- 建造者模式就是抽象工厂类模式和Test类结合起来得到的
- 代码实现:一个Sender接口,两个实现类MailSender和SmsSender
- 建造者模式将很多功能集成到一个类里,这个类就可以创造出比较复杂的模块
- 建造者模式和工厂模式的区别:工厂模式关注的是创建单个产品建造者模式关注的是创建符合对象,多个部分
原型模式(Prototype)
- 原型模式:将一个对象作为原型,进行复制,克隆,产生一个和原型对象类似的新对象
- 原型模式虽然是创建型模式,但是与工厂模式没有关系
- 在Java中,复制对象是通过clone()实现的
- 一个原型类,只需要实现Cloneable接口,重写clone()方法
- clone方法可以改写为任何名称,因为Cloneable接口是个空接口,可以任意定义实现类的方法名
- 重点是super.clone():super.clone()方法调用的是Object的**clone() ** 方法在Object类中,clone()方法是native的
- 对象的深复制和浅复制:深复制:将一个对象复制后,不论是基本类型还是引用类型,都是重新创建的深复制会进行完全彻底的复制浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型指向的还是原对象的引用
- 要实现深复制:
- 要采用流的形式读入当前对象的二进制输入
- 再写出二进制数据对应的对象
结构型模式
适配器模式(Adapter Pattern)
- 对象的适配器模式是各种结构型模式的起源
- 适配器模式:将某个类的接口转换成客户端期望的另一个接口表示
- 目的:消除由于接口不匹配所造成的类的兼容性问题
- 适配器模式主要分为三类:类的适配器模式对象的适配器模式接口的适配器模式
类的适配器模式
- 核心思想:有一个Source类,拥有一个方法待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里
- Adapter类继承Source类,实现Targetable接口
- 这样Targetable接口的实现类就具有Source类的功能
对象的适配器模式
- 基本思路和类的适配器相同,只是将Adapter类作修改,不继承Source类,而是持有Source类的实例,以达到解决兼容性问题
接口的适配器模式
- 一个接口中有多个抽象方法,当写该接口的实现类时,必须实现该接口的所有方法,这样明显比较浪费,因为并不是所有的方法都是需要用到的,有时只要引入一些即可。为了解决这样的问题,引入了接口适配器模式
- 接口适配器模式:借助于一个抽象类,该抽象类实现了该接口以及所有的方法,只需要和该抽象类进行联系即可。
- 只需要写一个类,继承该抽象类,重写需要用到的方法
- 三种适配器模式的应用场景:类的适配器模式:当希望一个类转换成满足另一个新接口的类时,可以使用类的适配器模式创建一个新类,继承原有的类,实现新的接口即可对象的适配器模式:当希望一个对象转换成满足另一个新接口的对象时,可以使用对象的适配器模式创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法即可接口的适配器模式:当不希望实现一个接口中所有的方法时,可以使用接口的适配器模式创建一个抽象类Wrapper,实现所有方法,写其它类时,只要继成抽象类即可
装饰器模式(Decorator)
- 装饰器模式:给一个对象动态地增加一些新的功能
- 装饰器模式要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例
- Source类是被装饰类,Decorator类是装饰类,可以为Source类动态地增加一些功能
- 装饰器模式应用场景:需要扩展一个类的功能动态地为一个对象增加功能,而且还能动态地撤销(继承的功能是静态的,不能动态增删)
- 装饰器模式的缺点:产生过多类似的对象,不易排错
代理模式(Proxy)
- 代理模式:创建一个代理类,替原对象进行一些操作
- 代理模式的应用场景:已有的方法在使用的时候需要对原有的方法进行改进,有两种方法:修改原有的方法来适应:这样违反了“对扩展开放,对修改关闭”的原则。不推荐使用采用一个代理类调用原有的方法,且对产生的结果进行控制。即代理模式
- 使用代理模式,可以将功能划分的更加清晰,有助于后期维护
外观模式(Facade)
- 在Spring中,可以将类与类之间的关系配置到配置文件中
- 外观模式:为了解决类与类之间的依赖关系,将类与类之间的关系放到一个Facade类中,降低类与类之间的耦合度,该模式中没有涉及到接口
- 如果没有Computer类,CPU,Memory,Disk之间会互相持有实例,产生关系,这样会造成严重依赖
- 修改一个类,可能会带来其它类的修改
- 有了Computer类,各个类之间的关系就放在类Computer类里,这样就起到解耦的作用
桥接模式(Bridge)
- 桥接模式:将事物和具体实现分开,二者可以各自独立的变化
- 将抽象化与实现化解耦,使得二者可以独立变化:JDBC桥DriverManager:JDBC连接数据库的时候,在各个数据库之间进行切换,基本不需要改动太多的代码,甚至一点不用改动原因在于JDBC提供统一接口,每个数据库提供各自实现,用一个叫做数据库驱动的程序来桥接即可
- 通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用
- 示例:JDBC连接原理
组合模式(Composite)
- 组合模式:部分-整体模式,在处理类似树形结构的问题时比较方便
- 组合模式使用场景:
- 将多个对象组合在一起进行操作
- 常用于表示树形结构中:二叉树
享元模式
- 享元模式:主要目的是实现对象共享,即共享池
- 当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用
- FlyWeightFactory:负责创建和管理共享元单元当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象如果有,就返回已经存在的对象如果没有,就创建一个新对象
- FlyWeight:超类
- 共享的对象的特点:共享对象有一些共同的属性这些属性对于每个连接来说都是一样的
- 基于共享对象的特点,可以用共享元模式处理共享对象:
- 将类似属性作为内部数据
- 其它的属性作为外部数据
- 在方法调用时,当作参数传进来
- 这样可以节省内存空间,减少实例的数量
- 示例:数据库连接池
- 通过连接池的连接,实现数据库连接的共享:不需要每一次重新创建连接,节省数据库重新创建的开销,提升了系统系能
行为型模式
- 11种行为模式的关系:
- 第一类: 通过父类与子类的关系进行实现
- 第二类: 通过两个类之间的关系进行实现
- 第三类: 通过类的状态进行实现
- 第四类: 通过中间类进行实现
模式策略(Strategy)
- 策略模式:
- 定义了一系列算法,并将每个算法封装起来,可以相互替换,算法的变化不会影响到使用算法的用户
- 设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口
- 设计一个抽象类(选用,作为辅助类),提供辅助函数
- ICalculator接口提供统一的方法
- AbstractCalculator是抽象辅助类,提供辅助方法
- 策略模式的决定权在于用户:系统本身提供不同算法的实现新增或删除算法对各种算法做封装
- 策略模式多用在算法决策系统中,外部用户只需要决定使用哪一个算法即可
模板方法模式(Template Method)
- 模板方法模式:
- 一个抽象类中,定义一个主方法
- 再定义无数个方法,可以是抽象的,也可以是实际的方法
- 定义一个子类,继承抽象类,重写抽象类中的方法
- 通过调用抽象类,实现对子类的调用
- AbstractCalculator类中定义一个主方法calculate()
- calculate()调用split()等
- Plus和Minus分别继承AbstractCalculator类
- 通过对AbstractCalculator的调用实现对子类的调用
Test的执行过程:
- 首先将exp和“\ \ +”做参数,调用AbstractCalculator类里的**calculate(String,String)**方法
- 在calculate(String,String)里调用同类的split()
- 然后再调用calculate(int,int)方法
- 从这个方法进入到子类中
- 执行完return num1+num2之后,将值返回到AbstractCalculator类,赋值给result,打印出来
观察者模式(Observer)
- 观察者模式是类与类之间的关系,不涉及继承
- 观察者模式类似邮件订阅和RSS订阅:
- 当你订阅了该内容,如果后续有更新,会及时接收到通知
- 观察者模式:当一个对象变化时,依赖该对象的对象都会接收到通知,并且随着变化。对象之间是一对多的关系
- MySubject类是主对象
- Observer1和Observer2是依赖于MySubject的对象
- 当MySubject变化时,Observer1和Observer2必然变化
- AbstractSubject类中定义者需要监控的对象列表,可以对这些对象进行修改:增加或者删除被监控对象
- 当MySubject变化时,AbstractSubject类负责通知在列表内存在的对象
- 观察者模式根据关系图,新建项目,使用代码按照总体思路走一遍,这样容易理解观察者模式的思想
迭代子模式(Iterator)
- 迭代子模式是类与类之间的关系,不涉及继承
- 迭代子模式:顺序访问聚集中的对象。包含两层意思:聚集对象:需要遍历的对象迭代器对象:用于对聚集对象进行遍历访问
- MyCollection中定义了集合的一些操作
- MyIterator中定义了一系列迭代操作,并且持有Collection实例
- JDK中各个类都是这些基本的集合,加上一些设计模式,再加一些优化组合到一起的,只要掌握这些,可以写出自定义的集合类,甚至框架
责任链模式(Chain of Responsibility)
- 类与类之间的关系,不涉及继承
- 责任链模式:
- 有多个对象,每个对象持有对下一个对象的引用,这样形成一条链,直到某个对象决定处理该请求
- 请求发出者并不清楚到底最终哪个对象会处理该请求
- 责任链模式可以实现:在隐瞒客户端的情况下,对系统进行动态调整
- Abstracthandler类提供了get和set方法,方便MyHandler类设置和修改引用对象
- MyHandler类是核心,实例化后生成一系列相互持有的对象,构成一条链
- 链接上的请求可以是一条链,可以是一个树,还可以是一个环
- 模式本身不受这个约束,需要自定义实现
- 在同一个时刻,命令只允许由一个对象传给另一个对象,不允许传给多个对象
命令模式(Command)
- 类与类之间的关系,不涉及继承
- 命令模式理解示例:司令员的作用是: 发出口令口令经过传递,传到士兵耳中,士兵去执行这个过程好在:司令,口令,士兵三者相互解藕任何一方都不用去依赖其它方,只需要做好自身的事即可司令员要的是结果,不会去关注士兵到底怎么实现的
- Invoker是调用者(司令员)
- Receiver是被调用者(士兵)
- MyCommand是命令,实现了Command接口,持有接收对象
- 命令模式的目的:达到命令的发出者和执行者之间的解耦,实现请求和执行分开
- Struts其实就是一种将请求和呈现分离的技术,使用了命令模式的设计思想
备忘录模式(Memento)
- 备忘录模式:主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象
- 备忘录模式理解:假设有原始类A,A中有各种属性,A可以决定需要备份的属性备忘录类B用来存储A的一些内部状态类C用来存储备忘录,并且只能存储,不能进行修改等操作
- Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值
- Memento类是备忘录类
- Storage类是存储备忘录的类,持有Memento类的实例
- 新建原始类时,value被初始化为egg,后经过修改,将value值修改为bulk,最后进行恢复状态,结果成功恢复
状态模式(State)
- 状态模式:当对象的状态改变时,同时改变对象的行为
- 状态模式理解示例:QQ有几种不同的状态:在线,隐身,忙碌等每个状态对应不同的操作,而且好友也能看到相关的状态
- 状态模式包括两层含义:
- 可以通过改变状态来获得不同的行为
- 对象状态的变化可以被发现
- State类是个状态类
- Context类可以实现切换
- 状态模式的应用场景十分广泛:在做网站的时候,希望根据对象的属性,区别一些功能等,比如说权限控制等等
访问者模式(Visitor)
- 访问者模式将数据结构和作用于结构上的操作解耦,使得操作集合可以相对自由地进行演化
- 访问者模式适用于数据结构相对稳定,算法容易变化的系统:访问者模式使得算法操作增加变得更加容易
- 若系统数据结构对象易于变化,经常有新的对象增加进来,则不适合使用访问者模式
- 访问者模式的特点:优点:增加操作容易增加操作意味着增加新的访问者访问者模式将有关行为集中到一个访问者对象中, 这些行为的改变不影响系统数据结构缺点:增加新的数据结构很困难
- 访问者模式:是一种分离对象数据结构和行为的方法,通过这种分离,可以达到为一个被访问者动态添加新的操作而无需做任何修改的效果
- 一个Visitor类,存放要访问的对象
- Subject类中有accept方法,接收将要访问的对象,getSubject()获取将要被访问的属性
- 访客模式适用场景:如果要为一个现有的类增加新功能:新功能是否会与现有功能出现兼容性问题以后会不会还有新功能需要添加如果类不允许修改代码怎么处理这些问题最好的解决办法就是访客模式访问者模式适用于数据结构相对稳定的系统,将数据结构和算法解耦
中介者模式(Mediator)
- 中介者模式是用来降低类与类之间的耦合的:类与类之间有依赖关系的话,不利于功能的拓展和维护因为只要修改一个对象,其它关联的对象都要进行修改
- 中介者模式:只需要关心Mediator类的关系,具体类与类之间的关系及调度交给Mediator,与Spring容器的作用类似
- User类统一接口
- User1和User2分别是不同的对象:二者之间有关联如果不采用中介者模式。则需要二者相互持有引用,这样二者的耦合度很高为了解耦,引入了Mediator类,提供统一接口
- MyMediator为实现类:持有User1和User2的实例,用来实现对User1和User2的控制这样User1和User2两个对象就可以相互独立,只需保持与Mediator之间的关系就可以
- Mediator类用来维护
解释器模式(Interpreter)
- 解释器模式一般主要应用在OOP开发中的编译器开发中,适用面比较窄
- Context类是一个上下文环境类
- Plus和Minus分别是计算的实现
- 解释器模式是来用作各种各样的解释器