创建型设计模式
文章目录
单例模式:
单例模式
介绍:所谓的单例设计模式就是采取一定的方法保证在整个软件系统中,对某个类只能纯在一个对象实例,并且该类只提供一个取得对象实例的方法(静态方法)
饿汉式
步骤:
-
构造器私有化
-
类的内部创建对象
-
向外暴露一个静态的公共方法
-
代码
-
public class HungryStyle { //构造器私有化 private HungryStyle(){ } //本类中创建对象实例 private final static HungryStyle HUNGRY_STYLE=new HungryStyle(); //提供一个共有的静态方法,返回实例对象 public HungryStyle getHungryStyle(){ return HUNGRY_STYLE; } }
-
//静态代码块实现饿汉式 public class HungryStyle { private HungryStyle(){ } private static HungryStyle HUNGRY_STYLE; static { HUNGRY_STYLE=new HungryStyle(); } public HungryStyle getHungryStyle(){ return HUNGRY_STYLE; } }
-
优缺点:
- 优点:写法简单,就是在类装载的时候就完成了实例化,避免了线程同步问题
- 缺点:在类装载的时候就完成了实例化,没有达到lazy loading的效果。如果从始至终从未使用该实例,就会造成内存的浪费
- 这种方式基于classloader机制避免了多线程的同步问题,不过,instance在类装载的时候就实例化,在单例模式中大对数都是调用getInstance方法,但是,导致类装载的原因有多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果
- 结论:可用,但是可能造成内存浪费
懒汉式(线程不安全)
代码:
public class LazyStyle {
private static LazyStyle lazyStyle;
private LazyStyle(){
}
//提供一个静态方法,当使用到该方法时,才回去创建实例
public static LazyStyle getLazyStyle(){
if(lazyStyle==null){
lazyStyle=new LazyStyle();
}
return lazyStyle;
}
}
优缺点:
- 起到了lazy loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入到
if(lazyStyle==null)
判断语句块,还未来得及往下执行,另一个线程也通过这个判断语句,这是便会产生多个实例。所以在多线程环境下不可使用这种方法 - 结论:在实际开发中,不要使用这种方法
懒汉式(线程安全,同步方法)
代码:
public class LazyStyle {
private static LazyStyle lazyStyle;
private LazyStyle(){
}
//提供一个静态方法,当使用到该方法时,才回去创建实例
//加入了同步处理,解决线程安全的问题
public static synchronized LazyStyle getLazyStyle(){
if(lazyStyle==null){
lazyStyle=new LazyStyle();
}
return lazyStyle;
}
}
优缺点:
- 解决了线程不安全的问题
- 效率太低了,每个线程在想要获得类的实例时,执行getInstace方法都要进行同步。而这个方法只执行一次实例化就够了,后面的想要获取该实例,直接return即可。方法进行通过不效率太低
- 结论:在实例开发中不建议使用这种方法。
双重检查(推荐)
代码:
public class DoubleCheckTest {
/*
volatile关键字的作用:保证可见性,不保证原子性
(1)当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
(2)这个写会操作会导致其他线程中的volatile变量缓存无效。
*/
private static volatile DoubleCheckTest doubleCheckTest;
private DoubleCheckTest(){}
public static DoubleCheckTest getDoubleCheckTest(){
if(doubleCheckTest==null){
synchronized (DoubleCheckTest.class){
if(doubleCheckTest==null){
doubleCheckTest=new DoubleCheckTest();
}
}
}
return doubleCheckTest;
}
}
优缺点说明:
- Double check概念是多线程开发中经常使用到的,如代码所示,我们进行了两次
if
判断检查,这样就保证线程安全了。 - 这样实例化代码只用执行一次,后面再次访问时,判断
if
时直接返回实例化对象 - 线程安全,延迟加载,效率较高
- 结论:在实际开发中推荐使用这种单例设计模式
静态内部类实现(推荐)
代码
public class Singleton {
private Singleton(){
}
//写一个静态内部类,改类中有一个静态属性
private static class SingletonInstance{
private final static Singleton Singleton=new Singleton();
}
//提供一个静态共有方法,直接返回静态内部类的属性
public static Singleton getInstance(){
return SingletonInstance.Singleton;
}
}
优缺点:
- 这种方式采用了类装载的机制来报证初始化实例时只有一个线程
- 静态内部类方式在singleton类被装载时并不会立即实例化,而是再需要实例化时,调用geiInstance方法才会装载SingletonInstance类,从而完成singleton的实例化。
- 类的静态属性只会在第一次加载类中初始化,所以这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 避免了线程不安全,利用静态内部类的特定实现延迟加载,效率高
- 结论:推荐使用
使用枚举实现(推荐)
代码:
enum Single{
INSTANCE;
public void method(){
}
}
优缺点:
- 借助JDK1.5中添加的枚举类来实现单例模式。不仅能避免多线程问题,而且还能防止反序列化重新创建新的对象
- 结论推荐使用
注意事项和细节
- 单例模式保证了系统内存中只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类时候,必须要记住使用相应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁创建和销毁的对象,创建对线耗时过长或耗费资源过多(即:重量级对象)但又经常使用到的对象,对象类工具,频繁访问数据库文件的对象(比如数据源,session工厂等)
简单工厂模式(静态工厂模式)
基本介绍:
- 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个对象决定创建出那一种产品的实例。简单工厂模式是工厂模式家族种最简单实用的模式
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为
- UML:
工厂方法模式
介绍:定义一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例推迟到子类。
UML:
抽象工厂模式:
基本介绍:
- 定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模可以将简单工厂模式和工厂方法模式进行整合
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者进一步抽象)
- 将工厂模式抽象成两层,AbsFactory(抽象工厂)和具体的工厂实现子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂变成了工厂簇,更利于代码的维护和拓展。
工厂模式的意义:
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的可拓展性。
依赖抽象原则
- 创建对象实例时不要直接new,而是把这个new类的动作放在一个工厂的方法种,并返回。
- 不要让类继承具体类,而是继承抽象类或者实现接口
- 不要覆盖基类中以及实现的方法。
原型模式:
传统拷贝的优缺点:
- 优点是比较好理解,简单易操作。
- 在创建新的对象时,总时需要重新获得原始对象的属性,如果创建的对象比较复杂,效率较低
- 总是需要重新初始化对象,而不是动态的获取到对象运行时的状态,不够灵活
- 改进:Java中object类是所有类的根类,object类提供一个clone的方法,该方法可以将一个java对象复制一份,但是需要实现clone的java类必须要实现一个cloneable接口,该接口表示该类能够复制且具有复制的能力 ==>原型模式
基本介绍:
- 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 原型模式是一种创建型的设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理就是:通过将一个原型对象传给哪个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建即,object.clone();
浅拷贝:
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是说将该属性值复制一份给新对象
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组,某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改成员变量会影响到另一个对象的该成员变量值
- 浅拷贝默认使用object类的clone方法实现
深拷贝:
-
复制对象的所有基本数据类型的成员变量
-
为所有引用数据类型的成员变量申请存储空间,并默认每个引用数据类型成员变量所引用的对象,知道该对象可达所有的对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
-
实现:
-
重写clone方法
-
通过对象序列化(推荐)
-
@Override protected Object clone() throws CloneNotSupportedException { //使用序列化化的方式深克隆 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); DeepClone deepClone= (DeepClone) ois.readObject(); return deepClone; }catch (Exception e){ System.out.println(e.getMessage()); }finally { try { ois.close(); bis.close(); oos.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }
-
-
注意事项和细节
- 创建新的对象比较复杂的时候,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用初始化对象,而是动态的获得对象的运行状态
- 如果原始对象发生变化(增加或者减少属性),其他克隆对象也会发生相应的变化,无需修改代码
- 在实现深克隆是可能需要比较复杂的代码
- 缺点:需要每一类配备一个克隆方法,这对全新的类来说不是很难实现,但对已有的类进行改造时,需要修改其源代码,违背了ocp(开闭原则)