【张六儿大话设计模式】——单例模式和原型模式

一、单例模式

1.单例模式概念

    我们可以把单例模式理解为男/女朋友,一个人只能有一个男/女朋友(你放荡不羁当我没说~)。小西是张六儿的女朋友,当张六儿想要出去看电影吃饭需要女朋友陪的时候,小西就会来陪着张六儿;当张六儿生病了需要女朋友照顾的时候,小西就会来照顾他;当张六儿想要和女朋友结伴去旅行的时候,小西就会和他一起去旅行;当张六儿想要。。。的时候,嘿嘿(害羞)~也就是说,当张六儿需要和女朋友一起做某事的时候,这个女朋友一定是小西不会是别人,因为张六儿是一个专一(遵循单例模式)的人。

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

public class Girlfriend {
	private String name;
	//创建一个女朋友实例
	private static Girlfriend girlfriend = new Girlfriend("小西");
	//构造方法设为私有,保证女朋友不能再次被实例化
	private Girlfriend(String name) {
		this.name = name;
	}
	public static Girlfriend getInstance() {
		return girlfriend;
	}
	public String getName() {
		return name;
	}
}
public class Main {
	public static void main(String args[]) {
		Girlfriend gf1 = Girlfriend.getInstance();
		Girlfriend gf2 = Girlfriend.getInstance();
		Girlfriend gf3 = Girlfriend.getInstance();
		System.out.println("我要去看电影,"+gf1.getName()+"陪我去~");
		System.out.println("我生病了,"+gf2.getName()+"来照顾我。");
		System.out.println("我要去旅行,"+gf3.getName()+"和我一起去!");
	}
}

优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。


2.单例模式几种实现方法

1)饿汉模式

上面的实现方法就是饿汉模式,没有使用同步锁机制,在类第一次加载的时候就创建了单例类实例,避免了多线程环境下的线程不安全问题,执行效率会提高。但是由于类加载的时机有很多种,所以它不能实现Lazy Loading效果,并且过早的创建单例类的实例会浪费内存空间。

2)懒汉模式

    懒汉模式顾名思义就是不着急创建单例类的实例,直到要使用这个实例的时候才创建。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

    缺点是由于没有使用synchronized关键字来修饰单例类的实例,所以不能保证在多线程环境下确保单例类的唯一性。若是要在懒汉模式的情况下保证单例,则只要把getInstance()方法设为synchronized同步方法即可。

3)双检锁/双重校验锁(DCL,即 double-checked locking)

    由于创建单例实例的时候要保证线程安全,如果使用线程安全的懒汉模式,要将getInstance()方法设为同步方法,这样当多个线程访问此方法的时候就会排队依次等待class锁的释放,这样会大大降低效率。而且在getInstance()中可能还会有一些单例实例创建之前的准备工作,这些工作是可以同步进行的,所以我们只需要把创建单例的关键代码部分设置为同步代码块,并且在进入同步代码块之前进行实例是否存在的判断即可,这就是所谓的DCL机制。代码如下:

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;  
    }  
}  

    性能较高,保证线程安全但是实现较复杂。

4)登记式/静态内部类

    这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟饿汉模式不同的是:饿汉模式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance,并且静态内部类只被加载一次,所以一直会创建一个单例类实例。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 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;  
    }  
}   

5)枚举

    这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

    这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

    枚举类enum和static静态代码块类似,在首次使用枚举类的时候会自动调用相应枚举的构造函数,并且只在第一次使用此枚举类时调用,我们也可以用枚举类的这个特性来实现单例模式。

public class EnumSingleton {
	public enum MyEnumSingleton{
		Factory;
		private Singleton singleton;
		private MyEnumSingleton() {
			singleton = new Singleton();
		}
		public Singleton getInstance() {
			return singleton;
		}
	}
	public static Singleton getSingleton() {
		return MyEnumSingleton.Factory.getInstance();
	}
}

二、原型模式

    张六儿是一个交友广泛并且人缘不错的人,每到各种节假日总会收到各式各样的节日祝福,而每次回复别人的祝福都让张六儿觉得十分苦恼,因为要把祝福语一字一字地输入到手机上,太麻烦了。于是张六儿了一个办法,他创建了一个备忘录,把各种各样的祝福语都输入进去并且加好备注。这样,每当过节收到朋友节日祝福的时候,他只要打开这个备忘录,根据需要把适当的祝福语copy过来再发送给朋友就好了,完全节省了思考和打字输入的时间~

    与单例模式相对应的是原型模式,和单例模式保证实例唯一性不同的是,原型模式用来创建重复对象。原型模式要提供一个复制实例化对象的方法,使得要使用对象的时候,不用每次都执行一遍复杂的对象实例化过程,而是可以通过clone的方法迅速获得一摸一样的实例化对象。

//节日祝福抽象类,实现cloneable接口
public abstract class Blessing implements Cloneable {
	private String type;
	private String content;
	public abstract void printBlessing();
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getContent() {
		return content;
	}
	public Object clone() {
		Object clone = null;
		try {
			clone = super.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return clone;
	}
}
public class Christmas extends Blessing{
	public Christmas() {
		this.setType("XMAS");
	}
	@Override
	public void printBlessing() {
		System.out.println("Merry Christmas!");
	}
}
public class NewYear extends Blessing{
	public NewYear() {
		this.setType("NY");
	}
	@Override
	public void printBlessing() {
		System.out.println("Happy New Year!");
	}
}
public class Halloween extends Blessing{
	public Halloween() {
		this.setType("HLW");
	}
	@Override
	public void printBlessing() {
		System.out.println("Happy Halloween!");
	}
}
//相当于祝福语备忘录
public class BlessingCache {
	private static Hashtable<String, Blessing> blessingMap = new Hashtable<String, Blessing>();	
	public static Blessing getBlessing(String type) {
		Blessing cacheBlessing = blessingMap.get(type);
		return (Blessing)cacheBlessing.clone();
	}
	//模拟创建对象的过程,假设每个对象创建都要进行复杂的数据库访问操作
	//将创建好的对象缓存在Hashtable中
	public static void loadCache() {
		Blessing newYear = new NewYear();
		blessingMap.put(newYear.getType(), newYear);
		
		Blessing christmas = new Christmas();
		blessingMap.put(christmas.getType(), christmas);
		
		Blessing halloween = new Halloween();
		blessingMap.put(halloween.getType(), halloween);
	}
}
public class Main {
	public static void main(String args[]) {
		//事先加载一遍对象集合,之后只需要clone
		BlessingCache.loadCache();
		
		Blessing clone1 = (Blessing)BlessingCache.getBlessing("NY");
		System.out.print("新年祝福语:");
		clone1.printBlessing();
		Blessing clone2 = (Blessing)BlessingCache.getBlessing("HLW");
		System.out.print("万圣节祝福语:");
		clone2.printBlessing();
		Blessing clone3 = (Blessing)BlessingCache.getBlessing("XMAS");
		System.out.print("圣诞节祝福语:");
		clone3.printBlessing();
	}
}

原型模式有以下使用场景:

 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。

优点: 1、性能提高。 2、逃避构造函数的约束。

缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。


猜你喜欢

转载自blog.csdn.net/u012198209/article/details/80651748