JAVA设计模式总结之23种设计模式
什么是设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。简单说:
模式:在某些场景下,针对某类问题的某种通用的解决方案。
场景:项目所在的环境
问题:约束条件,项目目标等
解决方案:通用、可复用的设计,解决约束达到目标。
设计模式的三个分类
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
结构型模式:把类或对象结合在一起形成一个更大的结构。
行为型模式:类和对象如何交互,及划分责任和算法。
各分类中模式的关键点
单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰模式:动态的给对象添加新的功能。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
观察者模式:对象间的一对多的依赖关系。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
中介者模式:用一个中介对象来封装一系列的对象交互。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
1.单例模式
单例模式,它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
单例模式具备典型的3个特点:1、只有一个实例。 2、自我实例化。 3、提供全局访问点。
因此当系统中只需要一个实例对象或者系统中只允许一个公共访问点,除了这个公共访问点外,不能通过其他访问点访问该实例时,可以使用单例模式。
单例模式的主要优点就是节约系统资源、提高了系统效率,同时也能够严格控制客户对它的访问。也许就是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,所以扩展起来有一定的困难。其UML结构图非常简单,就只有一个类,如下图:
懒汉式
延迟加载,只有真正使用的时候,才开始实例化
1、线程安全问题
2、加锁优化
3、防止指令重排
package com.itdemo.lazySingletonTest;
public class lazysingleton {
public static void main(String[] args) {
LazySingleton instance= LazySingleton.getInstance();
LazySingleton instance1=LazySingleton.getInstance();
System.out.println(instance==instance1);
}
}
class LazySingleton{
private volatile static LazySingleton instance;//volatile防止cpu对指令重排序
private LazySingleton(){//无参构造
}
public static LazySingleton getInstance(){
if (instance==null){
synchronized (LazySingleton.class){//优化锁
if (instance==null){
instance=new LazySingleton();
}
}
}
return instance;
}
}
饿汉式
类加载的初始化阶段就完成了实例的初始化。本质上就是借助于JVM类加载机制,保证实例的唯一性。
类的加载过程:
1,加载二进制数据到内存中,生成对应的class文件
2,连接:a.验证 b.准备(给类的静态成员变量赋默认值) c.解析
3,初始化:给类的静态变量赋初始值
只有在真正使用对应的类时,才会触发初始化 如(当前类是启动类即main函数所在类,直接new操作,访问静态属性、访问静态属性、用反射访问类,初始化一个类的子类等)
public class hungrysingleton {
public static void main(String[] args) {
HungrySingleton instance=HungrySingleton.getInstance();
HungrySingleton instance1=HungrySingleton.getInstance();
System.out.println(instance==instance1);
}
}
class HungrySingleton{
private static HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
静态内部类
1、本质上是利用类的加载机制来保证线程安全
2、只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。
class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance=new InnerClassSingleton();
}
private InnerClassSingleton(){
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
枚举类(安全)
枚举类没有无参构造,不能通过反射来创建对象
public enum EnumSingle{
instance;
public Enumsingle getInstance(){
return instance;
}
}
2.工厂方法模式
应用场景:
1、当你不知道该使用对象的确切类型的时候
2、当你希望为库或者框架提供扩展其内部组件的方法时
主要优点:
将具体产品和创建者解耦 、符合单一职责原则、符合开闭原则
作为抽象工厂模式的孪生兄弟,工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,也就是说工厂方法模式让实例化推迟到子类。
工厂方法模式非常符合“开闭原则(对拓展开放,对修改关闭)”,当需要增加一个新的产品时,我们只需要增加一个具体的产品类和与之对应的具体工厂即可,无须修改原有系统。同时在工厂方法模式中用户只需要知道生产产品的具体工厂即可,无须关系产品的创建过程,甚至连具体的产品类名称都不需要知道。虽然他很好的符合了“开闭原则”,但是由于每新增一个新产品时就需要增加两个类,这样势必会导致系统的复杂度增加。其UML结构图:
源码中的应用:
//java API
// 静态工厂方法
Calender.getInstance()
java.text.NumberFormat.getInstance()
java.util.ResourceBundle.getBundle()
//工厂方法
java.net.URLStreamHandlerFactory
javax.xml.bind.JAXBContext.createMarshaller
3.抽象工厂模式
所谓抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。他允许客户端使用抽象的接口来创建一组相关的产品,而不需要关系实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。它的优点是隔离了具体类的生成,使得客户端不需要知道什么被创建了,而缺点就在于新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更加需要更改接口及其下所有子类。其UML结构图如下:
工厂模式
核心本质:
- 实例化对象不使用new,用工厂方法代替
- 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
三种模式
简单工厂模式
用来生产同一等级结构中的任意产品(对于增加新的产品,需要覆盖已有代码)
工厂方法模式
用来生产同一等级结构中的固定产品(支持增加任意产品)
抽象工厂模式
围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂
4.建造者模式
建造者模式属于创建型模式,它提供了一种创建对象的最佳方式
定义:将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
主要作用:在用户不知道对象的创建过程和细节的情况下可以直接创建复杂对象
用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
例子:
工厂(建造者模式):负责制造汽车(组装过程和细节在工厂内)
汽车购买者(用户):只需说出想要的型号(对象的类型和内容),购买后即可使用(用户对汽车制造是未知的)
应用场景:
◆ 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
◆隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。◆适合于一个具有较多的零件(属性)的产品(对象)的创建过程。
建造者与抽象工厂模式的比较:
◆与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
◆在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一 个复杂对象, 返回一个完整的对象。
◆如果将抽象工厂模式看成汽车配件生产工厂,生产-一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车!
代码演示
Builder类
package com.itdemo.BuilderSingleton;
public abstract class Builder {
abstract Builder builderA(String msg);
abstract Builder builderB(String msg);
abstract Builder builderC(String msg);
abstract Builder builderD(String msg);
abstract Product getProduct();
}
//-----------------------------------------------------------------------------
Product类
package com.itdemo.BuilderSingleton;
public class Product {
private String BuildA="A";
private String BuildB="B";
private String BuildC="C";
private String BuildD="D";
public void setBuildA(String buildA) {
BuildA = buildA;
}
public void setBuildB(String buildB) {
BuildB = buildB;
}
public void setBuildC(String buildC) {
BuildC = buildC;
}
public void setBuildD(String buildD) {
BuildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"BuildA='" + BuildA + '\'' +
", BuildB='" + BuildB + '\'' +
", BuildC='" + BuildC + '\'' +
", BuildD='" + BuildD + '\'' +
'}';
}
}
//-----------------------------------------------------------------------------
Worker类
package com.itdemo.BuilderSingleton;
public class Worker extends Builder{
private Product product;
public Worker(){
product=new Product();
}
@Override
Builder builderA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder builderB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder builderC(String msg) {
product.setBuildC(msg);
return this;
}
@Override
Builder builderD(String msg) {
product.setBuildD(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
//-----------------------------------------------------------------------------
Test类
package com.itdemo.BuilderSingleton;
public class Test {
public static void main(String[] args) {
Worker worker=new Worker();
Product product = worker.builderA("全家桶")
.getProduct();
System.out.println(product.toString());
}
}
5.原型模式
原型模式的定义与特点
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。
原型模式的结构与实现
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
1. 模式的结构
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
其结构图如图 1 所示。
图1 原型模式的结构图
2. 模式的实现
原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。其代码如下:
//具体原型类
class Realizetype implements Cloneable{
Realizetype() {
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype)super.clone();
}}
//原型模式的测试类
public class PrototypeTest{
public static void main(String[] args)throws CloneNotSupportedException {
Realizetype obj1=new Realizetype();
Realizetype obj2=(Realizetype)obj1.clone();
System.out.println("obj1==obj2?"+(obj1==obj2));
}}
程序的运行结果如下:
具体原型创建成功!
具体原型复制成功!
obj1==obj2?false
原型模式的应用场景
原型模式通常适用于以下场景。
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 对象的创建过程比较麻烦,但复制比较简单的时候。
6、适配器模式
在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。
在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
模式的定义与特点
适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要优点如下。
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
其缺点是:对类适配器来说,更换适配器的实现过程比较复杂。
模式的结构与实现
类适配器模式可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。现在来介绍它们的基本结构。
模式的结构
适配器模式(Adapter)包含以下主要角色。
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
模式的应用场景
适配器模式(Adapter)通常适用于以下场景。
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
代码演示
package com.itdemo.adapter;
//要被适配的类:网线
public class Adaptee {
public void request(){
System.out.println("连接网线上网");
}
}
//-----------------------------------------------------------------------------
package com.itdemo.adapter;
//真正的适配器,需要连接网线和usb
public class Adapter extends Adaptee implements NetToUsb{
//将网线和适配器组合绑定在一起
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee=adaptee;
}
@Override
public void handleRequest() {
adaptee.request();
}
}
//-----------------------------------------------------------------------------
package com.itdemo.adapter;
//接口转换器的抽象实现
public interface NetToUsb {
//作用:处理请求,网线->usb
public void handleRequest();
}
//-----------------------------------------------------------------------------
package com.itdemo.adapter;
//客户端类:想上网,插不上网线
public class Conputer {
//我们的电脑需要连接转接器才能上网
public void net(NetToUsb adapter){
//上网的具体实现,找一个转接口
adapter.handleRequest();
}
public static void main(String[] args) {
Conputer conputer=new Conputer();//电脑
Adaptee adaptee=new Adaptee();//网线
Adapter adapter=new Adapter(adaptee);//转接器
conputer.net(adapter);
}
}