图解Java设计模式-23种设计模式

本文详细介绍了Java中的23种设计模式,包括创建型模式(单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式)、结构型模式和行为型模式。特别讨论了单例模式的8种实现方式及其优缺点,并对比了工厂模式的简单工厂、工厂方法和抽象工厂模式。此外,还提到了原型模式、建造者模式的概念和在JDK中的应用,以及这些模式在实际开发中的注意事项和选择建议。

设计模式分为三种类型,共 23 种

1) 创建型模式 :单例模式 、抽象工厂模 式原型式、建 造者模式、工厂模 式。
2) 结构型模式 :适配器模式、桥接装饰模式 、组合模式、外观模式、享元模式、代理模式 。
3) 行为型模式 :模 版方法式、命令模式、访问者式、迭代器模式、观察者模式 、中介者模式、备忘录模式、解释器( Interpreter模式)、状态模式 、策略模式、职责链模式(责任链模式 )。

 

1.单例模式

所谓类的单例设计模式,就是采取一定方法保证在整个软件系统中对某只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法(静态方法 )。

比如 Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建 Session对象。SessionFactory 并不是轻量级的,一般情况下一个项目通常只需要一个SessionFactory就够,这是会使用到单例模式。


方单例模式有八种方式:
1) 饿汉式 (静态常量)
2) 饿汉式(静态代码块)
3) 懒汉式 (线程不安全)
4) 懒汉式 (线程安全,同步方法 )
5) 懒汉式 (线程安全,同步代码块 )
6) 双重检查
7) 静态内部类

8)枚举

1.1饿汉式(静态常量)
步骤如下 :
1) 构造器私有化 (防止 new )
2) 类的内部创建对象
3) 向外暴露一个静态的公共方法。 getInstance
4) 代码实现

//恶汉式(静态变量)
class Singleton{
    //1.私有化构造器
    private Singleton(){}
    //2.本类内部创建对象实例
    private final static Singleton instance = new Singleton();
    //3.提供一个公用的静态方式,返回实例对象
    public static Singleton getSingleton(){
        return instance;
    }
}

优缺点说明:
1) 优点:这种写法比较简单,就是在类装载的时候完成实例化。避免了线程同步问题
2) 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading的效果。如从始至终从未使用过这个实例,则会造成内存的浪费
3) 这种方式基于 classloder机制避免了多线程的同步问题, 不过,instance在类装载时就实例化 ,在单例模式中大多数都是调用 getInstance方法, 但是导致类装载的原因有很多种, 因此不能确定有其他的方式(或者静态法)导致类 装载,这时候初始化 instance就没有达到 lazy loading的效果
4) 结论:这种单例模式可用 ,可能造成内存浪费。

1.2饿汉式(静态代码块)

//1.构造器私有化,外部能new
    private Singleton(){
    }
    //2.本类内部创建对象实例
    private static Singleton instance;
    
    static{ //在静态代码块中,创建单例对象
        instance = new Singleton();
    }

    //3.提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }

优缺点说明 :
1) 这种方式和上面的其实类似,只不过将例化程放在了静态代码块中,也是在类装载的时候就执行静态代码块初始化实例。优缺点和上面是一样的 。
2) 结论: 这种单例模式可用 ,但是可能造成内存浪费

 

2.懒汉式

2.1懒汉式(线程不安全)

class Singleton{
    //1.构造器私有化
    private Singleton(){
    }
    
    //2.本类内部创建对象实例
    private static Singleton instance ;
    
    //3.提供一个静态的公有方法,当使用到该方法时,才去创建instance
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    
}

优缺点说明 :
1) 起到了 Lazy Loading的效果,但是只能在单线程下使用 。
2) 如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这判断语句时便会产生多实例。所以在多线程环境下不可使用这种方式。
3) 结论:在实际开发中,不要使用这种方式 .

2.2懒汉式(线程安全,同步方法)

class Singleton{
    //1.私有化构造器
    private Singleton(){
    }
    //2.本类内部创建对象实例
    private static Singleton instance;
    //3.提供一个静态公有方法,加入同步处理的代码
    public static synchronized Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优缺点说明 :
1) 解决了线程不安全问题
2) 效率太低了,每个线程在想获得类的实例时候执行 getInstance()方法都要进行同步。而其实这个方法只执行一次例化代码就够了,后面的想获得该类直接 return就行了。方法进同步效率太低;
3) 结论: 在实际开发中, 不推荐使用这种方式

2.3懒汉式(线程安全,同步代码块)

优缺点说明 :
1) 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低, 改为同步产生实例化的代码块
2) 但是这种同步并不能起到线程同步的作用。跟第 3种实现方式遇到的情形一 致,假如一个线程进入了 if (singleton == null)判断语句块, 还未来得及往下执行,另一个线程也通过了这判断语句,这时便会产生多实例
3) 结论:在实际开发中,不能使用这种方式

 

3.双重检查

两次检查,一次获得锁;

以后只需第一次检测,就返回了;

优缺点说明 :
1) Double -Check 概念是多线程开发中常使用到的, 如代码中所示,我们进行了两 次if (singleton == null)检查,这样就可以保证线程安全了 。
2) 这样,实例化代码只用执行一次,后面再访问时判断 if (singleton == null), 直接 return实例化对象,也避免的反复进行方法同步 .
3) 线程安全;延迟加载效率较高
4) 结论:在实际开发中,推荐用这种单例模式;

4.静态内部类

静态内部类:

1.主类被装载时,静态内部类不会被加载;

2.当使用到静态变量时,导致静态类被加载;

3.类加载时,是线程安全的;

优缺点说明 :
1) 这种方式采用了类装载的机制来保证初始化实例时只有一个线程
2) 静态内部类方式在 Singleton 类被装载时并不会立即实例化 ,而是在需要时,调用getInstance 方法,才会装载 SingletonInstance类,从而完成 Singleton 的实例化 。
3) 类的静态属性只会在第一次加载类时候初始化, 所以这里JVM帮助我们,保证了线程的安全性,在类进行初始化时别是无法入。
4) 优点:避免了 线程不安全 ,利用静态内部类特点实现延迟加载,效率高
5) 结论:推荐使用 .

5.枚举

例子:

枚举可以避免多线程同步问题,而且可以防止反序列化重新创建新的对象。

 

推荐使用的单例模式

1.饿汉式:虽然浪费空间,初始化就创建对象,而不是等到使用的时候再创建,但是安全的。

2.双重检测:保证懒加载,线程安全;

3.静态内部类:类加载机制保证只创建一次,且线程安全;懒加载;

4.枚举:线程安全,懒加载,同时避免反序列化重新创建新的对象;

 

懒汉式不推荐使用,线程不安全;

 

JDK中使用:java.lang.Runtime 就是经典的单例模式(饿汉式 )

单例模式 注意事项和细节说明

1) 单例模式保证了系统内存中该类只在一个对象,节省了系资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2) 当想实例化一个单例类的时候,必须要记住使用相应获取对象方法,而不是用new一个对象;
3) 单例模式使用的场景:需要频繁进行创建和销毁对象、创建对象时耗过多或耗费资源过多(即:重量级对象 ),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象 (比如数据源、session 工厂等)

 

2.工厂设计模式

2.1未使用工厂模式

看一个具体的需求
看一个披萨的项目:要便于种类扩展,要便于维护
1) 披萨的种类很多 (比如 GreekPizz 、CheesePizz等)
2) 披萨的制作有 prepare,bake, cut, box
3) 完成披萨店订购功能

传统方法的优缺点:
1) 优点是比较好理解,简单易操作。
2) 缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭 。即当我们给类增加新功能的时候,尽量不修改代码或者可少.修改代码;
3) 比如我们这时要新增加一个 Pizza的种类 (Pepper披萨 ),需要修改很多类;

改进的思路分析:
分析: 修改代码可以接受, 但是如果我们在其它的地方也有创建Pizza的代码,就意味着,也需要修改,而创建 Pizza的代码, 往往有多处 。
思路 :把创建Pizza对象封装到一个类中,这样我们有新的 PizzaPizzaPizza Pizza种类时,只需要修改该类就可, 其它有创建到Pizza对象的代码就不需要修改了 .-> 简单工厂模式

 

2.2简单工厂模式(静态工厂模式)

基本介绍:

1) 简单工厂模式 是属于创建型,是工厂模式的一种。 简单工厂模式是由一个工厂对象决定创建出哪一 种产品类的实例 。简单工厂模式是家族中最简单实用的模 
2) 简单工厂模式:定义了一个创建对象的类,由这来 封装实例化对象的行 为(代码 )
3) 在软件开发中,当我们会用到大量的创建某种、类或者批对象时就使用到工厂模式

 

2.3工厂方法模式

工厂方法模式设计案 :将披萨项目的实例化功能抽象成方法,在不同口味点餐子类中具体实现。
工厂方法模式 :定义了一个创建对象的抽方 法,由子类决要实例化。工厂法模式将对象的实例化推迟到子类

 

披萨项目新的需求 :客户在点披萨时,可以不同口味的披萨,比如北京的奶酪pizza 、北京的胡椒 pizza或者是伦敦的奶酪 pizza 、伦敦的胡椒pizza

由于分类增加北京、伦敦;使用简单工厂模式,创建不同的简单工厂类会过多;所以使用工厂方法模式;


 

OrderPizza为工厂,定义生成Pizza的方法:抽象方法createPizza;

BJOrderPizza具体工厂,继承OrderPizza类,生成pizza;

BJCheessPizza、BJPepperPizza、LDCheessPizza、LDPepperPizza具体的Pizza类

 

Pizza为产品,定义Pizza:抽象类prepare,每种口味的pizza不同原料;

 

2.4抽象工厂模型

1) 抽象工厂模式:定义了一个 interface用于创建相关或有依赖系的对象簇,而无需指明具体的类
2) 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3) 从设计层面看,抽象工厂模式就是对简单的改进 (或者称为进一步的抽象 )。
4) 将工厂抽象成两层:AbsFactory((抽象工厂 )具体实现的工厂子类。程序员可以根据创建对象类型使用应的工厂子。 这样将单个的简工厂类变成了工厂簇 , 更利于代码的维护和扩展。

public class OderPizza {
    AbsFactory factory;
    
    public OderPizza(AbsFactory factory){
        setFactory(factory);
    }
    
    private void setFactory(AbsFactory factory){
        Pizza pizza = null;
        String orderTyoe = "";
        this.factory = factory;
        
        do{
            String orderType = getType();
            //factory可能是北京工厂子类,也可能是伦敦工厂子类
            pizza = factory.createPizza(orderType);
            if (null != pizza){
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else{
                System.out.println("订购失败....");
                break;
            }
            
        }while(true);
    }
    
    public String getType(){
        BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("输入pizza种类..... ");
        
        String orderType = "";
        try {
            orderType = strin.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return orderType;
    }
}

工厂模式在JDK中的使用

 

工厂模式小结


1) 工厂模式的意义将实例化对象的代码提取出来,放到一个类中统管理和维护,达主项目依赖关系的解耦。从而提高项目扩展和维护性。

2) 三种工厂模式 (简单工厂模式 、工厂方法模式、抽象工厂模式)
3) 设计模式的依赖抽象原则
创建对象实例时,不要直接new类, 而是把这个new 类的动作放在一个工厂方法中,并返回。
有的书上说,变量不要直接持具体类引用。

不要让类继承具体类,而是继承抽象类或者实现interface接口。

不要覆盖基类中已经实现的方法。

 

3.原型模式(赋值对象)

传统的方式的优缺点
1) 优点是比较好理解,简单易操作。
2) 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建比较复杂时,效率较低
3) 总是需要重新初始化对象,而不动态地获得运行时的状, 不够灵活
4) 改进的思路分析
思路:Java 中Object 类是所有的根,Object 类提供了一个 clone()方法,该可以将一 个Java 对象复制一份 ,但是需要实现 clone的Java类必须要实现一个接口 Cloneable,该接口表示类能够复制且 具有复制的能力 => 原型模式

 

3.1 基本介绍

1) 原型模式 (Prototype模式 )是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
2) 原型模式是一种创建设计式,允许一个对象再创建另外可定制的对象, 无需知道如何创建的细节
3) 工作原理是 :通过将一个原型对象传给那要发动创建的对象,这个要发动创建的对象通过请求原型拷贝它们自己来实施创建,即 对象 .clone() .

 

原理结构图说明
1) Prototype :  原型类,声明一个克隆自己的接口;
2) ConcretePrototype: 具体的原型类 , 实现一个克隆自己的操作;
3) Client: 让一个原型对象克隆自己,从而创建新的对象(属性一样 );

3.2 深拷贝和浅拷贝原理

浅拷贝的介绍
1) 对于数据类型是基本的成员变量,浅拷贝会直接进行值传递,也就将该属性值复制一份给新的对象。
2) 对于数据类型是引用的成员变量,比如说某个组、的对象等,那么浅拷贝会进行引用传递也就是只将该成员变量值(内存地址)复制一份给新的对象。因为实际上两个该成员变量都指向同实例。在这种情况下,一个对象中修改该成员变量会影响到另的员变量值
3) 前面我们克隆羊就是浅拷贝
4) 浅拷贝是使用默认的 clone()方法来实现

sheep = (Sheep) super.clone();

深拷贝基本介绍
1) 复制对象的所有基本数据类型成员变量值
2) 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要整个对象进行拷贝;
3) 深拷贝实现方式 1:重 写clone 方法来实现深拷贝
4) 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐 )

public class DeepCloneableTarget implements Serializable, Cloneable {

	private static final long serialVersionUID = 1L;

	private String cloneName;

	private String cloneClass;

	//构造器
	public DeepCloneableTarget(String cloneName, String cloneClass) {
		this.cloneName = cloneName;
		this.cloneClass = cloneClass;
	}

	//因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}
public class DeepProtoType implements Serializable, Cloneable{
	public String name; //String 属性
	public DeepCloneableTarget deepCloneableTarget;// 引用类型
	public DeepProtoType() {
		super();
	}
	
	//深拷贝 - 方式 1 使用clone 方法
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object deep = null;
		//这里完成对基本数据类型(属性)和String的克隆
		deep = super.clone(); 
		//对引用类型的属性,进行单独处理
		DeepProtoType deepProtoType = (DeepProtoType)deep;
		deepProtoType.deepCloneableTarget  = (DeepCloneableTarget)deepCloneableTarget.clone();
		

		return deepProtoType;
	}
	
	//深拷贝 - 方式2 通过对象的序列化实现 (推荐)
	public Object deepClone() {
		
		//创建流对象
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream ois = null;
		
		try {
			//序列化
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			oos.writeObject(this); //当前这个对象以对象流的方式输出
			
			//反序列化
			bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);
			DeepProtoType copyObj = (DeepProtoType)ois.readObject();
			
			return copyObj;
			
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			//关闭流
			try {
				bos.close();
				oos.close();
				bis.close();
				ois.close();
			} catch (Exception e2) {
				System.out.println(e2.getMessage());
			}
		}
	}
}
public class Client {
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		DeepProtoType p = new DeepProtoType();
		p.name = "宋江";
		p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");
		
		//方式1 完成深拷贝
		
//		DeepProtoType p2 = (DeepProtoType) p.clone();
//		
//		System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
//		System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
	
		//方式2 完成深拷贝
		DeepProtoType p2 = (DeepProtoType) p.deepClone();
		
		System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
		System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
	
	}

}

DeepProtoType实现Serializable和Cloneable接口;有一个引用属性DeepCloneableTarget;两个深拷贝方法,一个方法是序列化实现;

DeepCloneableTarget实现Serializable和Cloneable接口;有一个普通clone方法;

 

原型模式的注意事项和细节
1) 创建新的对象比较复杂时,可以利用原型模式简化创过程同也能够提高效率
2) 不用重新初始化对象,而是动态地获得对象运行时的状态
3) 如果原始对象发生变化 (增加或者减少属性 ),其它克隆对象的也会发生相应变化 ,无需修改代码
4) 在实现深克隆的时候可能需要比较复杂代码
5) 缺点:需要为每一个类配备克隆方法,这对全新的类来说不是很难,但已的类进行改造时,需要修改其源代码 ,违背了 ocp原则,这点请注意。

 

4.建造者模式

4.1传统盖房模式

缺点:没有涉及缓存层对象,程序的扩展和维护不好,把产品(房子)和创建产品的过程封装在一起,耦合性增强了。

解决方案:将产品和产品建造过程解耦===>建造者模式

4.2 建造者模式

基本介绍
1) 建造者模式( Builder Pattern ) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽出来(抽象类别),使这个抽象过程不同实现方法可以构造出不同表现(属性)的对象。
2) 建造者模式是一步一步创建一个复杂的对象 ,它允许用户只通过指定的复杂对象类型和内容就可以构建它们,用户不需要知道内部的具体构建细节 。

4.3  建造者模式的四个角色

1) Product (产品角色): 一个具体的产品对象 。
2) Builder (抽象建造者):创建一个 Product 对象的各个部件指定的接口 /抽象类 。
3) ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件 。
4) Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用:

1.隔离了客户与对象生产过程;

2.负责控制产品对象的产生过程;

4.4 构建者模式

1) Product (产品角色): 一个具体的产品对象 ---House(产品的属性)
2) Builder (抽象建造者):创建一个 Product 对象的各个部件指定的接口 /抽象类,---HouseBuilder(抽象方法,由具体的建造者实现,builder方法返回产品)
3) ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件 ;---CommonHouse,HighBuilding(实现抽象建造者Builder中的抽象方法)
4) Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用:1.隔离了客户与对象生产过程;2.负责控制产品对象的产生过程; --- HouseDirector(调用抽象构造者中的抽象方法,构造产品返回产品;通过构造器或Setter方法传递抽象建造者)

 

传递引用参数的两种方式:

1.通过构造器传递引用;

2.通过setter方法传递引用;

4.5 建造者模式在JDK中的使用

    3) 源码中建造者模式角色分析
Appendable 接口定义了多个 append 方法 (抽象方法 ), 即Appendable 为抽象建造者 , 定义了 抽象方法
AbstractStringBuilder 实现了Appendable 接口方法,这里的 AbstractStringBuilder 已经是建造者,只不能实例化;
StringBuilder 即充当了指挥者角色,  同时具体的建造即充当了指挥者角色, 建造方法的实现是由 AbstractStringBuilder 完成 , 而StringBuilder 继承了 AbstractStringBuilder 

 

4.6建造者模式的注意事项和细节 

1) 客户端(使用程序 )不必知道产品内部组成的细节,将产品本身与创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
2) 每一个具体建造者都相对独立,而与其他的无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
3) 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同方法中,使得创建过程更加清晰,也方便使用程序来控制创建过程。
4) 增加新的具体建造者无须修改原有类库代码 , 指挥者类针对抽象建造者编程,系统扩展方便,符合 “开闭原则 ”

5) 建造者模式所创建的产品一般具有较多共同点, 其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式 ,因此其使用范围受到一定的限制 。
6) 如果产品的内部变化复杂, 可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大, 因此在这种情况下,要考虑是否选择建造者模式 .。
7) 抽象工厂模式 VS 建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只什么由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它主要目的是通过组装零配件而产生一个新产品。

 

 

 

 

 

 

 

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值