> 文章列表 > 设计模式--创建型模式

设计模式--创建型模式

设计模式--创建型模式

软件设计原则

设计模式--创建型模式

1.工厂模式(Factory)

1.1 简单工厂模式

关键字new
public class CourseFactory {public ICourse create(String name){if("java".equals(name)){return new JavaCourse();}else if("python".equals(name)){return new PythonCourse();}else {return null;}}
}
反射 - 按类名全路径反射
public class CourseFactory {public ICourse create(String className){try {if (!(null == className || "".equals(className))) {return (ICourse) Class.forName(className).newInstance();}}catch (Exception e){e.printStackTrace();}return null;}
}
反射 - class
public class CourseFactory {public ICourse create(Class<? extends ICourse> clazz){try {if (null != clazz) {return clazz.newInstance();}} catch (Exception e) {e.printStackTrace();}return null;}
}

1.2工厂方法模式

设计模式--创建型模式

/*定义一个接口*/
public interface ICourseFactory {ICourse create();
}/*实现类*/
public class JavaCourseFactory implements ICourseFactory {public ICourse create() {return new JavaCourse();}
}/*实现类*/
public class PythonCourseFactory implements ICourseFactory {public ICourse create() {return new PythonCourse();}
}

1.3 抽象工厂模式

设计模式--创建型模式
产品族:Java、Python ……不同语言
产品等级结构:笔记、视频(每个语言都有对应的笔记、视频等共性的东西)
思路:先创建笔记、视频接口,不同语言的笔记、视频交由子类具体实现,最后将各个语言的笔记、视频整合,交由工厂统一实现。

设计模式--创建型模式

public interface INote {void edit();
}public class JavaNote implements INote {public void edit() {System.out.println("编写Java笔记");}
}public class PythonNote implements INote {public void edit() {System.out.println("编写Python笔记");}
}
public interface IVideo {void record();
}public class JavaVideo implements IVideo {public void record() {System.out.println("录制Java视频");}
}public class PythonVideo implements IVideo {public void record() {System.out.println("录制Python视频");}
}
public interface CourseFactory {INote createNote();IVideo createVideo();
}public class JavaCourseFactory implements CourseFactory {public INote createNote() {return new JavaNote();}public IVideo createVideo() {return new JavaVideo();}
}public class PythonCourseFactory implements CourseFactory {public INote createNote() {return new PythonNote();}public IVideo createVideo() {return new PythonVideo();}
}

2.单例模式(Singleton)

确保一个类有且仅有一个实例,并全局调用。
应用场景:ServletContext、ServletContextConfig、ApplicationContext、数据库连接池

2.1 饿汉式单例

类加载时,创建单例对象

优点:
线程安全,没有任何形式的锁,执行效率高。
缺点:
类加载时就初始化,不管用不用都占用内存空间。
应用:
Spring IOC容器中ApplicationContext就是典型的饿汉式单例。

public class HungrySingleton {private static final HungrySingleton hungrySingleton = new HungrySingleton();private HungrySingleton(){}	// private:防止外部new获取对象实例public static HungrySingleton getInstance(){return  hungrySingleton;}
}
public class HungryStaticSingleton {private static final HungryStaticSingleton hungrySingleton;static {		//静态代码块hungrySingleton = new HungryStaticSingleton();}private HungryStaticSingleton(){}		// private:防止外部new获取对象实例public static HungryStaticSingleton getInstance(){return  hungrySingleton;}
}

2.2 懒汉式单例

外部类调用时内部类才会加载

volatile + synchronized双重检查锁
public class LazyDoubleCheckSingleton {private volatile static LazyDoubleCheckSingleton lazy = null;	// volatileprivate LazyDoubleCheckSingleton(){}public static LazyDoubleCheckSingleton getInstance(){if(lazy == null){synchronized (LazyDoubleCheckSingleton.class){if(lazy == null){lazy = new LazyDoubleCheckSingleton();}}}return lazy;}
}

第一个if(lazy == null),只有在一开始lazy尚未初始化时要抢锁,lazy初始化之后,就不存在抢锁的情况,性能得以保证。
第二个if(lazy == null),在一开始lazy尚未初始化时,会有大量线程进入第一个if(lazy == null),并等待锁,为了保证只有第一个抢到锁的线程才能初始化lazy,所以其他线程抢到锁之后还要再判断一次。

为什么要使用volatile?

new LazyDoubleCheckSingleton()的过程包含内存分配并初始化零值、设置对象头、init方法初始化(详见JVM文档),内存分配并初始化零值时就可以将分配的内存地址赋值给lazy,然而对象可能还没有初始化,若此时另外一个线程进来,调用getInstance方法,可能获取的状态就是错的。问题的根本在于将分配的内存地址赋值给lazy和对象初始化的重排序导致,所以要依赖volatile,防止指令重排序。

静态内部类

内部类在被调用之前初始化,避免了线程安全的问题,也解决了饿汉式内存浪费的问题。

public class LazyInnerClassSingleton {private LazyInnerClassSingleton(){}// static 是为了使单例的空间共享// final保证这个方法不会被重写,重载public static final LazyInnerClassSingleton getInstance(){return LazyHolder.LAZY;}private static class LazyHolder{private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();                                             }
}

2.3 反射破坏单例

构造方法private,虽然可以防止new,但如果通过反射调用构造方法,再getInstance(),还是会产生两个不同实例。在构造方法中判断,抛异常。

public class LazyInnerClassSingleton {private LazyInnerClassSingleton(){if(LazyHolder.LAZY != null){		// 防止反射破坏单例            throw new RuntimeException("不允许创建多个实例");}}// static 是为了使单例的空间共享,final保证这个方法不会被重写,重载public static final LazyInnerClassSingleton getInstance(){return LazyHolder.LAZY;}private static class LazyHolder{private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();                                             }
}

2.4 序列化破坏单例

当对象序列化到磁盘,下次用时再反序列时,会因为内存的重新分配而产生一个新的对象。
实现Serializable 接口,重写readResolve()。

public class SeriableSingleton implements Serializable {public final static SeriableSingleton INSTANCE = new SeriableSingleton();private SeriableSingleton(){}public static SeriableSingleton getInstance(){return INSTANCE;}private  Object readResolve(){return  INSTANCE;}
}

在ObjectInputStream类的readResolve()中,如果readResolve()重写了,则调用readResolve()。

缺陷:
其实通过源码了解到,对象实例化了两次,只不过新创建的对象没有被调用而已,所以如果需要大量的创建对象,内存开销必然很大。

2.5 枚举式单例

通过枚举的唯一标识获取实例,枚举类的源码中针对反射、序列化破坏已经做了如上处理。

public enum EnumSingleton {INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static EnumSingleton getInstance(){return INSTANCE;}
}

枚举类单例如何避免反射破坏?
反射的源码中,有针对类的类型判断,如果是枚举类,则抛异常。
枚举类单例如何避免序列化破坏?
序列化的 readObject()方法中,针对枚举类的读方法中,是基于注册式单例(类+name)保证了唯一。

2.6 容器缓存式单例

通过反射获取类对象,适用于很多实例需要创建的情况。
非线程安全。

public class ContainerSingleton {private ContainerSingleton(){}private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();                                          public static Object getInstance(String className){synchronized (ioc) {if (!ioc.containsKey(className)) {Object obj = null;try {obj = Class.forName(className).newInstance();ioc.put(className, obj);} catch (Exception e) {e.printStackTrace();}return obj;} else {return ioc.get(className);}}}
}

2.7 ThreadLocal线程单例

ThreadLocal创建的对象不是全局唯一的。但能保证单个线程内唯一,线程安全。

public class ThreadLocalSingleton {private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =new ThreadLocal<ThreadLocalSingleton>(){@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};private ThreadLocalSingleton(){}public static ThreadLocalSingleton getInstance(){return threadLocalInstance.get();}
}

3.原型模式(Prototype)

拷贝对象

常用于:
1.类初始化消耗资源较多。Clone相比new的好处就在于避免了对象的初始化。
2.New 一个对象的过程非常繁琐(考虑数据、权限等)。
3.构造函数复杂
4.循环体中生产大量对象。

3.1 浅克隆

对象中引用类型clone的是地址引用,也就是说所有对象共用一个引用类型。

public interface Prototype{Prototype clone();
}public class ConcretePrototypeA implements Prototype {private int age;private String name;private List hobbies;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public List getHobbies() {return hobbies;}public void setHobbies(List hobbies) {this.hobbies = hobbies;}@Overridepublic ConcretePrototypeA clone() {ConcretePrototypeA concretePrototype = new ConcretePrototypeA();concretePrototype.setAge(this.age);concretePrototype.setName(this.name);concretePrototype.setHobbies(this.hobbies);return concretePrototype;}
}
public class Client {private Prototype prototype;public Client(Prototype prototype){this.prototype = prototype;}public Prototype startClone(Prototype concretePrototype){return (Prototype)concretePrototype.clone();}
}
public class PrototypeTest {public static void main(String[] args) {// 创建一个具体的需要克隆的对象ConcretePrototypeA concretePrototype = new ConcretePrototypeA();// 填充属性,方便测试concretePrototype.setAge(18);concretePrototype.setName("prototype");List hobbies = new ArrayList<String>();concretePrototype.setHobbies(hobbies);System.out.println(concretePrototype);// 创建Client对象,准备开始克隆Client client = new Client(concretePrototype);ConcretePrototypeA concretePrototypeClone =(ConcretePrototypeA) client.startClone(concretePrototype);System.out.println(concretePrototypeClone);System.out.println("克隆对象中的引用类型地址值:" + 			 					concretePrototypeClone.getHobbies());                                  System.out.println("原对象中的引用类型地址值:" +       				concretePrototype.getHobbies());                                          System.out.println("对象地址比较:"+(concretePrototypeClone.getHobbies() == concretePrototype.getHobbies()));}
}

3.2 深克隆

通过序列化,重写引用类型。
继承Cloneable接口,重写clone()。
继承Serializable 接口,序列化。

public class QiTianDaSheng implements Cloneable,Serializable {public JinGuBang jinGuBang;public  QiTianDaSheng(){this.jinGuBang = new JinGuBang();}@Overrideprotected Object clone() {return this.deepClone();}public Object deepClone(){try{ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return ois.readObject();}catch (Exception e){e.printStackTrace();return null;}}

3.3 源码

scope=’prototype’   // 作用域
JSON.parseObject()

4.构造者模式(Builder)

将一个复杂对象的构建与它的表示分离,同样的构建过程可以创建不同的表示。用户只需要指定建造类型,无需了解建造的过程和细节。
适用于:
1、对象的构建需要很多步骤且顺序不固定
2、对象的构建结构非常复杂
3、将复杂对象的创建和使用分离

public class File {private String name;private String type;private String url;private String lastMotifile;public void setName(String name) {this.name = name;}public void setType(String type) {this.type = type;}public void setUrl(String url) {this.url = url;}public void setLastMotifile(String lastMotifile) {this.lastMotifile = lastMotifile;}@Overridepublic String toString() {return "File{" +"name='" + name + '\\'' +", type='" + type + '\\'' +", url='" + url + '\\'' +", lastMotifile='" + lastMotifile + '\\'' +'}';}}   
public static class FileBuilder{private File file = new File();public FileBuilder setName(String name) {file.setName(name);return this;}public FileBuilder setType(String type) {file.setType(type);return this;}public FileBuilder setUrl(String url) {file.setUrl(url);return this;}public FileBuilder setLastMotifile(String lastMotifile) {file.setLastMotifile(lastMotifile);return this;}public File build(){return file;}
}
public class Client {public static void main(String[] args) {File file = new File.FileBuilder().setLastMotifile("last").setName("fileName").setType("doc").setUrl("http://").build();System.out.println(file);}
}