java设计模式的简单实现(1) - 创建型模式

参考文章:

  1. 总体:https://www.cnblogs.com/adamjwh/p/9033545.html
  2. 单例模式:https://www.cnblogs.com/ygj0930/p/10845530.html

创建型模式分为以下几种。
单例(Singleton)模式:保证多线程同时对一个对象和方法访问时,只能操作一个实例
原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
工厂方法(FactoryMethod)模式:定义一个用于创建产品的工厂接口,由子类决定生产一个产品。
抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

一,单例模式

  • 定义:单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例

  • 特点
    1):只能由一个实例
    2):必须自己创建自己的唯一实例
    3):必须给所有其他对象提供这一实例
    4):除了构造方法,其他都是static

1. 懒汉式(在第一次调用的时候实例化自己)

public class Singleton{
	//将构造函数设置为私有,避免类在外部实例化
	private Singleton(){}
	//设置为静态,这样就可以每一次调用都是同一个对象
	private static Singleton single=null;
	public static Singleton getInstance(){
		//只有在没有实例的情况下才创建实例,保证只有一个实例
		if(single==null){
			single=new Singleton();
		}
		return single;
	} 
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题。

  1. 在getInstance方法上加同步
public static synchronized Singleton getInstance(){
	if(single == null){
		single=new Singleton();
	}
	return single;
}
  1. 双重检查锁定

一. 注意这里的两个判断null是有很大的意义的

  1. 第一个null是使他每次只有创建对象的时候才加锁
  2. 第二个判断null是为了防止当两个线程同时访问该方法,都进入了第一个判断空方法,一个进入,一个阻塞,当第一个线程创建了实例后释放锁,第二个也进入锁,也创建了实例的情况。

二. 在实例对象那边需要加上volatile关键字

  1. 这里如果不加volatile可能会出现一个错误,即当代码读取到第11行的判断语句时,如果instance不为null时,但instance引用的对象可能还没有完成初始化,线程将访问到一个还未初始化的对象。究其原因是因为代码第14行,创建了一个对象,此代码可分解为三行伪代码,即分配对象的内存空间、初始化对象、设置instance指向刚分配的内存地址,分别记为1、2、3,在2和3之间,可能会被重排序,重排序后初始化就变为了最后一步。因此,线程A的intra-thread semantics(所有线程在执行Java程序时必须遵守intra-thread semantics,它保证重排序不会改变单线程内的程序执行结果)没有改变,但A2和A3的重排序将导致线程B判断出instance不为空,线程B接下来将访问instance引用的对象,此时,线程B将会访问到一个还未初始化的对象。而使用volatile就可以实现线程安全的延迟初始化,本质时通过禁止2和3之间的重排序,来保证线程安全的延迟初始化
public class Singleton {

    private volatile static Singleton instance;
    private static Object syncRoot = new Object();
    
    private Singleton() {
    }

    public static Singleton getInstance() {
        //双重锁定
        if(instance == null) {
            synchronized (syncRoot) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        
        return instance;
    }
    
}

  1. 静态内部类(既实现了线程安全,又避免了同步带来的性能影响。)

利用类加载来保证只创建一个instance实例
缺点:无法做到延迟创建对象,在类加载时进行创建会导致初始化时间变长

public class Singleton {

    //静态内部类
    private static class SingletonHolder {
        public static Singleton instance = new Singleton();
    }
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
    
}

2.饿汉式单例(在类的初始化时,就自行实例化)

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

public class Singleton{
	private Singleton(){}
	//定义为final和static,无法修改
	private static final Singleton single = new Singleton().;
	public static Singleton getInstance(){
		return single;
	}
}

上述4种方法实现单例的缺点

1. 反序列化对象时会破坏单例

反序列化对象时不会调用getXX()方法,于是绕过了确保单例的逻辑,直接生成了一个新的对象,破环了单例。

解决办法:

  1. 重写类的反序列化方法,在反序列化方法中返回单例而不是创建一个新的对象。
public class Singleton implements Serializable{
	//当前实例
	public static Singleton INSTANCE = new Singleton();     
 
//双重锁定部分代码
	。。。

	//重写反序列化,使其直接返回当前实例
	   private Object readResolve() {     
            return INSTANCE;     
      }   
}
  1. 重写私有构造方法
//维护一个volatile的标志变量在第一次创建实例时置为false;重写构造函数,根据标志变量决定是否允许创建。
private static volatile  boolean  flag = true;
private Singleton(){
    if(flag){
    flag = false;   //第一次创建时,改变标志
    }else{
        throw new RuntimeException("The instance  already exists !");
    }

最好用的方法-使用枚举模式实现单例

public enum singletonEnum{
	INSTANCE;
	private String name;
	public String getName(){
		return name;
	}
	public void setName(String name){
		this.name=name;
	}
}

原因

  1. 使用SingletonEnum.INSTANCE进行访问,无需再定义getInstance方法和调用该方法
  2. JVM对枚举实例的唯一性,避免了上面提到的反序列化和反射机制破坏单例的情况出现,每一个枚举类型和定义的枚举变量再JVM中都是唯一的。
    ( 枚举类型在序列化时仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象)
  3. 注意:编译器需要禁止重写枚举类型的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。)

原型模式

  1. 适用场景:
  • 对象之间相同或者相似,只有个别的几个属性不同的时候
  • 对象的创建麻烦,但是复制方便时
  1. 复制出来的对象不是原对象,属性值相同,但是对象的地址不同,可以创建出大量相似的对象。
  2. 代码实现(利用java实现Cloneable接口就行)
//具体原型类
class A implements Cloneable{
	//写一个自定的构造方法,方便理解
	A(){
		System.out.println("具体原型创建成功");
	}
	public Object clone throws CloneNotSupportedException{
		System.out.println(“具体原型复制成功”);
		return (A)super.clone();
	}
}
//原型模式的测试类
public class B{
	 public static void main(String[] args)throws CloneNotSupportedException
    {
        A obj1=new A();
        A obj2=(A)obj1.clone();
        System.out.println("obj1==obj2?"+(obj1==obj2));
    }
}
  1. 运行结果如下

具体原型创建成功!
具体原型复制成功!
obj1==obj2?false

工厂模式

  1. 适用场景:
  • 对方不清楚具体的产品名,只清楚产品的生产工厂名
  • 产品对象的创建由某一个具体工厂生成,抽象工厂只负责提供创建对象的接口
  • 客户只关心产品的品牌,不关心产品的细节
  1. 一般结构
  • 抽象工厂:提供创建对象的方法接口
  • 具体工厂:实现创建对象的方法,不同的工厂创建不同产品的对象
  • 抽象产品:定义产品的规范,描述产品的主要特性
  • 具体产品:实现抽象产品,由具体工厂创建,与具体工厂一一对应。
  1. 模式的实现
/**
 *产品类
 **/
//抽象产品
interface product{
    public void show();
}
//具体产品1
class proA implements product{

    @Override
    public void show() {
        System.out.println("A");
    }
}
//具体产品2
class proB implements product{
    @Override
    public void show(){
        System.out.println("B");
    }
}

/**
 *工厂类
 **/
//抽象工厂
interface factory {
    product getPro();
}

//具体工厂1
class facA implements factory{
    @Override
    public product getPro(){
        return new proA();
    }
}

//具体工厂2
class facB implements factory {
    @Override
    public product getPro() {
        return new proB();
    }
}

抽象工厂模式

  1. 特点:当具体工厂不只生产某一个特定产品时
  2. 实现(修改工厂类)
    ···
    interface AbstractFactory
    {
    public Product1 newProduct1();
    public Product2 newProduct2();
    }

class ConcreteFactory1 implements AbstractFactory
{
public Product1 newProduct1()
{
System.out.println(“具体工厂 1 生成–>具体产品 11…”);
return new ConcreteProduct11();
}
public Product2 newProduct2()
{
System.out.println(“具体工厂 1 生成–>具体产品 21…”);
return new ConcreteProduct21();
}
}
···

建造者模式

  1. 适用场景
  • 创建的对象较复杂,有多个部件构成,各部件会有复杂的变化,但是构件间的建造顺序时稳定的。
  • 产品的构建过程和最终的表示时独立
  1. 个人理解: 就是产品的各部件实现和建造过程和建造实现顺序分割出来,当一件产品的建造过程改变时,只需要改变建造者,而当产品的某个部件有相对的变化时,就改变部件的具体实现,各自分开,并且将建造者和指挥者分开,当建造过程改变时,只需要改变指挥者,而建造的其中一个过程的细节发生改变时,只需要改建造者的具体建造方法即可

  2. 一般结构

  • 产品角色:包含多个组成部件的复杂对象,也就是各个部件的实现
  • 抽象建造者:包含创建产品的各个子部分的抽象方法
  • 具体建造者:实现了抽象建造者的接口
  • 指挥者:调用建造者中的方法完成复杂对象的建造

也就类似于建造大楼的过程,建造者是工人,指挥者是包工头,包工头通过调度各个个人完成大楼不同部分的建造,完成一栋大楼的建造

  1. 实现代码
public class Abc {
    public static void main(String[] args) {
        //创建一个建造者
        ture_builder ture_builder=new ture_builder();
        //创建一个指挥者,并且给他建造者
        boss boss=new boss(ture_builder);
        product product=boss.build();
        System.out.println();
    }
}

//产品角色
class product{
    //部分1
    String pat1;
    String pat2;
    public void show(){
        System.out.println("aaa");
    };
}

//抽象建造者
abstract class builder{
    abstract void getpat1();
    abstract void getpat2();
    abstract product getres();
}

//具体建造者
class ture_builder extends builder{
    product pro1=new product();

    @Override
    void getpat1() {
        pro1.pat1="1";
        System.out.println("建造pat1部分");
    }

    @Override
    void getpat2() {
        pro1.pat2="2";
        System.out.println("建造pat2部分");
    }

    @Override
    product getres() {
        return pro1;
    }
}

//指挥者
class boss{
    private ture_builder ture_builder;
    
    boss(ture_builder ture_builder){
        this.ture_builder=ture_builder;
    }
    
    product build(){
        ture_builder.getpat1();
        ture_builder.getpat2();
        return ture_builder.getres();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值