行业知识
JAVA设计模式——创建型模式
JAVA设计模式——创建型模式
- 一、创建型模式
- 1.单例模式(Singleton Pattern)
- 1.1 饿汉式
- 1.2 懒汉式
- 1.3 双重检验锁(double check lock)(DCL)
- 1.4 静态内部类
- 1.5 枚举
- 1.6 破坏单例的几种方式与解决方法
- 1.6.1 反序列化
- 1.6.2 反射
- 1.7 容器式单例
- 1.8 ThreadLocal单例
- 1.9 总结
- 2.工厂方法模式(Factory Method)
- 2.1 简单工厂模式
- 2.1.1 基础版
- 2.1.2 升级版
- 2.1.3 总结
- 2.2 工厂方法模式
- 2.2.1 代码实现
- 2.2.2 总结
- 3.抽象工厂模式(Abstract Factory)
- 3.1 代码实现
- 3.2 总结
- 4.原型模式(Prototype)
- 4.1 浅克隆
- 4.2 深克隆
- 4.3 克隆破坏单例与解决办法
- 4.4 总结
- 5.建造者模式(Builder)
- 5.1 常规写法
- 5.2 简化写法
- 5.3 链式写法
- 5.4 ���结
一、创建型模式
1.单例模式(Singleton Pattern)
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1.1 饿汉式
特点:类加载时就初始化,线程安全(在本类中创建本类对象)
// 构造方法私有化private Singleton() {} // 饿汉式创建单例对象private static Singleton singleton = new Singleton(); public static Singleton getInstance() {return singleton;}1.2 懒汉式
特点:第一次调用才初始化,避免内存浪费。
/* * 懒汉式创建单例模式 由于懒汉式是非线程安全, 所以加上线程锁保证线程安全 */private static Singleton singleton;public static synchronized Singleton getInstance() {if (singleton == null) {singleton = new Singleton();}return singleton;}1.3 双重检验锁(double check lock)(DCL)
特点:安全且在多线程情况下能保持高性能
private volatile static Singleton singleton;private Singleton (){}public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton;}但是在多线程的情况下,可能出现空指针的问题——volatile
1.4 静态内部类
特点:效果类似DCL,只适用于静态域
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; }1.5 枚举
特点:自动支持序列化机制,绝对防止多次实例化
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一种不会被破坏的单例实现模式。
public enum Singleton { INSTANCE;}1.6 破坏单例的几种方式与解决方法
1.6.1 反序列化
Singleton singleton = Singleton.getInstance();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/test.txt"));oos.writeObject(singleton);oos.flush();oos.close();ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt"));Singleton singleton1 = (Singleton)ois.readObject();ois.close();System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@50134894System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@5ccd43c2可以看到反序列化后,两个对象的地址不一样了,那么这就是违背了单例模式的原则了,解决方法只需要在单例类里加上一个readResolve()方法即可,原因就是在反序列化的过程中,会检测readResolve()方法是否存在,如果存在的话就会反射调用readResolve()这个方法。
private Object readResolve() { return singleton; }//com.ruoyi.base.mapper.Singleton@50134894//com.ruoyi.base.mapper.Singleton@501348941.6.2 反射
Singleton singleton = Singleton.getInstance();Class singletonClass = Singleton.class;Constructor constructor = singletonClass.getDeclaredConstructor();constructor.setAccessible(true);Singleton singleton1 = constructor.newInstance();System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@32a1bec0System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@22927a81
同样可以看到,两个对象的地址不一样,这同样是违背了单例模式的原则,解决办法为使用一个布尔类型的标记变量标记一下即可,代码如下:
private static boolean singletonFlag = false; private Singleton() { if (singleton != null || singletonFlag) { throw new RuntimeException("试图用反射破坏异常"); } singletonFlag = true; }但是这种方法假如使用了反编译,获得了这个标记变量,同样可以破坏单例,代码如下:
Class singletonClass = Singleton.class;Constructor constructor = singletonClass.getDeclaredConstructor();constructor.setAccessible(true);Singleton singleton = constructor.newInstance();System.out.println(singleton); // com.ruoyi.base.mapper.Singleton@32a1bec0Field singletonFlag = singletonClass.getDeclaredField("singletonFlag");singletonFlag.setAccessible(true);singletonFlag.setBoolean(singleton, false);Singleton singleton1 = constructor.newInstance();System.out.println(singleton1); // com.ruoyi.base.mapper.Singleton@5e8c92f4如果想使单例不被破坏,那么应该使用枚举的方式去实现单例模式,枚举是不可以被反射破坏单例的。
1.7 容器式单例
当程序中的单例对象非常多的时候,则可以使用容器对所有单例对象进行管理,如下:
public class ContainerSingleton { private ContainerSingleton() {} private static Map singletonMap = new ConcurrentHashMap(); public static Object getInstance(Class clazz) throws Exception { String className = clazz.getName(); // 当容器中不存在目标对象时则先生成对象再返回该对象 if (!singletonMap.containsKey(className)) { Object instance = Class.forName(className).newInstance(); singletonMap.put(className, instance); return instance; } // 否则就直接返回容器里的对象 return singletonMap.get(className); } public static void main(String[] args) throws Exception { SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class); SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class); System.out.println(instance1 == instance2); // true }}1.8 ThreadLocal单例
不保证整个应用全局唯一,但保证线程内部全局唯一,以空间换时间,且线程安全。
public class ThreadLocalSingleton { private ThreadLocalSingleton(){} private static final ThreadLocal threadLocalInstance = ThreadLocal.withInitial(() -> new ThreadLocalSingleton()); public static ThreadLocalSingleton getInstance(){ return threadLocalInstance.get(); } public static void main(String[] args) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); }).start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); }).start();// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc }}可以看到上面线程0和1他们的对象是不一样的,但是线程内部,他们的对象是一样的,这就是线程内部保证唯一。
1.9 总结
适用场景:
- 需要确保在任何情况下绝对只需要一个实例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。
优点:
- 在内存中只有一个实例,减少了内存开销。
- 可以避免对资源的多重占用。
- 设置全局访问点,严格控制访问。
缺点:
- 没有接口,扩展困难。
- 如果要扩展单例对象,只有修改代码,没有其它途径。
2.工厂方法模式(Factory Method)
2.1 简单工厂模式
简单工厂模式不是23种设计模式之一,他可以理解为工厂模式的一种简单的特殊实现。
2.1.1 基础版
// 工厂类public class CoffeeFactory { public Coffee create(String type) { if ("americano".equals(type)) { return new Americano(); } if ("mocha".equals(type)) { return new Mocha(); } if ("cappuccino".equals(type)) { return new Cappuccino(); } return null; }}// 产品基类public interface Coffee {}// 产品具体类,实现产品基类接口public class Cappuccino implements Coffee {}基础版是最基本的简单工厂的写法,传一个参数过来,判断是什么类型的产品,就返回对应的产品类型。但是这里有一个问题,就是参数是字符串的形式,这就很容易会写错,比如少写一个字母,或者小写写成了大写,就会无法得到自己想要的产品类了,同时如果新加了产品,还得在工厂类的创建方法中继续加if,于是就有了升级版的写法。
2.1.2 升级版
// 使用反射创建对象// 加一个static变为静态工厂public static Coffee create(Class
- 需要确保在任何情况下绝对只需要一个实例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。
上一篇:微网页怎么做的?
下一篇:怎么远程访问服务器管理口?