创建型模式
1、创建型模式的关注点是“怎/样创建出对象”
2、“将对象的创建与使用分离”
3、降低系统耦合度
4、使用者无需关注对象的创建细节
1)对象的创建由相关的工厂完成 (各种工厂模式)
2)对象的创建由一个建造者来完成(建造者模式)
3)对象的创建由原来对象克隆完成(原型模式)
4)对象是在在系统中只有一个实力(单例模式)
单例模式
单例模式是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局的访问点。单例模式在现实生活中的应用也非常的广泛,例如,一个公司的CEO、部门经理等等。J2EE标准中ServletContext、ServletContextConfig等,Spring框架应用中的ApplicationContext、数据库连接池等也都是单例形式。
一个单一的类,负责创建自己的对象,同时确保系统中只有单个对象被创建。
单例特点:
1、某个类只能有一个实例(构造器私有)
2、它必须自行创建这个实例(自己编写实例化逻辑)
3、它必须自行向整个系统提供这个实例(对外提供实例化方法)
懒汉模式1(线程不安全)
懒汉单例模式的特点是:被外部类调用的时候,内部类才会加载
package lori.sp;
public class LazySimpleSingleton {
private static LazySimpleSingleton singleton= null;
//私有构造方法
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if (singleton==null){
singleton = new LazySimpleSingleton();
}
return singleton;
}
}
package lori.sp;
public class ExectorThread implements Runnable{
@Override
public void run() {
LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+ ":" +singleton);
}
}
package lori.sp;
import java.lang.reflect.Constructor;
public class Test11 {
public static void main(String[] args) {
Thread thread1 = new Thread(new ExectorThread());
Thread thread2 = new Thread(new ExectorThread());
thread1.start();
thread2.start();
}
}
输出结果:
> Task :spring-sourcesTest:Test11.main()
Thread-1:lori.sp.LazySimpleSingleton@2c798596
Thread-0:lori.sp.LazySimpleSingleton@23c89a4e
上面的代码有一定概率会出现两种不同的结果,这意味着上面的单例存在线程安全隐患。我们通过调试运行再具体看一下。用线程模式调试,将debug调到Thread模式(鼠标放在断点上点击右键,弹出图1所示框选择Thread),如下图1
在空判断处打断点,
首先让线程1进入空判断里面,紧接着,切换线程2,再让线程2进入空判断里面。
然后线程1走出空判断,得到的singleton是489
紧接着线程2走出空判断,得出的singleton是490,并且将线程1的singleton覆盖
此时,再返回线程1 和线程2,得到的是相同的singleton。
懒汉模式2(在方法上加锁)
package lori.sp;
public class LazySimpleSingleton {
private static LazySimpleSingleton singleton= null;
//私有构造方法
private LazySimpleSingleton(){}
public synchronized static LazySimpleSingleton getInstance(){
if (singleton==null){
singleton = new LazySimpleSingleton();
}
return singleton;
}
}
首先让线程1走进方法内,此时切换到线程2,线程2显示监听状态,不能进入方法
直到线程1走出方法,紧接着线程2进入方法空判断处显示false,跳出方法。
线程1,线程2返回相同的singleton。
但是给整个方法加锁,还是存在一定的性能问题,于是,我们有了下面一种
懒汉模式4(双重检查锁)
上面完美的展现了synchronized监视锁的运行状态,线程安全的问题解决了,但是用synchronized加锁时,线程数量比较多的情况下,如果CPU分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅下降,那么有没有一种更好地方式,既能兼顾线程安全,又能提升程序性能呢?下面,我们来看一下双重检查锁的单例模式
package lori.sp;
public class LazySimpleSingleton {
private static LazySimpleSingleton singleton= null;
//私有构造方法
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if (singleton==null){
synchronized (LazySimpleSingleton.class) {
if (singleton==null) {
singleton = new LazySimpleSingleton();
//CPU执行时候会转换成JVM指令执行
//1、分配内存给这个对象
//2、初始化对象
//3、将初始化好的对象和内存地址建立关联。赋值
//由于CPU是抢时间片,可能第二步和第三步执行顺序会颠倒
}
}
}
return singleton;
}
}
懒汉模式5(内部类)
大类加载的时候,首先会加载内部类,LazyHolder里面的逻辑,需要等到外部方法调用时,才执行,巧妙地利用了内部类的特性。
JVM底层执行逻辑,完美的避免了线程安全问题
package lori.sp;
//这种模式兼顾饿汉式单例模式的内存浪费问题和synchronized 性能问题
//完美的屏蔽了这两个缺点
public class LazySimpleSingleton {
//在使用LazySimpleSingleton的时候,会默认先初始化内部类
//如果没有使用,内部类则不加载
private LazySimpleSingleton(){}
//每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不被重写、重载
public static LazySimpleSingleton getInstance(){
//在返回结果前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazySimpleSingleton LAZY=new LazySimpleSingleton();
}
}
这种方式兼顾了饿汉单例模式的内存浪费问题和synchronized 的性能问题。内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。
饿汉模式(线程安全)
饿汉单例模式在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,线程还没出现以前就实例化了,不可能存在访问安全问题。
优点:没有加任和锁,执行效率比较高,用户体验比懒汉单例模式更好
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费内存,有可能‘占着茅坑不拉屎’
饿汉单例模式适用于单例对象较少的情况
package lori.spe;
public class HungrySingleton {
//先静态,后动态
//先属性,后方法
//先上后下
private static final HungrySingleton hu = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hu;
}
}
还有另外一种写法,利用静态代码块的机制:
package lori.spe;
//饿汉静态块单例模式
public class HungrySingleton {
//先静态,后动态
//先属性,后方法
//先上后下
private static final HungrySingleton hu;
static {
hu =new HungrySingleton();
}
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hu;
}
}
反射破坏单例模式
大家有没有发现,上面介绍的单例模式的构造方法除了加上private关键字,没有做任何的处理。如果我们使用反射来调用其构造方法,再调用getInstance()方法,应该有两个不同的实例。现在来看一段测试代码:
package lori.sp;
import java.lang.reflect.Constructor;
public class Test11 {
public static void main(String[] args) {
try {
//在很无聊的情况下,进行破坏
Constructor<LazySimpleSingleton> constructor = LazySimpleSingleton.class.getDeclaredConstructor();
//强制访问
constructor.setAccessible(true);
//暴力初始化
LazySimpleSingleton s3 = constructor.newInstance();
//调用了2次构造方法,相当于 new 了两次,犯了原则性错误
LazySimpleSingleton s4 = constructor.newInstance();
System.out.println(s3==s4);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
运行结果如下:
> Task :spring-sourcesTest:Test11.main()
false
显然,创建了两个不同的实例。现在,我们在其构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常。
package lori.sp;
//这种模式兼顾饿汉式单例模式的内存浪费问题和synchronized 性能问题
//完美的屏蔽了这两个缺点
public class LazySimpleSingleton {
//在使用LazySimpleSingleton的时候,会默认先初始化内部类
//如果没有使用,内部类则不加载
private LazySimpleSingleton(){
if (LazyHolder.LAZY!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
//每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不被重写、重载
public static LazySimpleSingleton getInstance(){
//在返回结果前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazySimpleSingleton LAZY=new LazySimpleSingleton();
}
}
输出:
> Task :spring-sourcesTest:Test11.main() FAILED
Exception in thread "main" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at lori.sp.Test11.main(Test11.java:19)
Caused by: java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at lori.sp.Test11.main(Test11.java:14)
Caused by: java.lang.RuntimeException: 不允许创建多个实例
at lori.sp.LazySimpleSingleton.<init>(LazySimpleSingleton.java:10)
... 6 more
FAILURE: Build failed with an exception.
序列化破坏单例模式
一个单例对象创建后,有时候需要将对象序列化后然后写入磁盘,下次使用时再从磁盘中读取对象,并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,下面来看一段代码:
package lori.sp;
import java.io.Serializable;
//反序列化,导致破坏单例模式
@SuppressWarnings("serial") //此注解避免警告
public class LazySimpleSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式,从而转换成一个IO流,写入其他地方(可以是磁盘,网络IO)
//内存中的状态会永久保存下来
//反序列化就是将已经持久化的字节码内容转换为IO流,通过IO流的读取,进而将读取的内容转化为java对象,在转换过程中会重新创建对象
public final static LazySimpleSingleton INSTANCE= new LazySimpleSingleton();
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
return INSTANCE;
}
}
package lori.sp;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test11 {
public static void main(String[] args) {
LazySimpleSingleton singleton1 =null ;
LazySimpleSingleton singleton2 =LazySimpleSingleton.getInstance();
FileOutputStream fos =null;
try {
fos =new FileOutputStream("LazySimpleSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("LazySimpleSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
singleton1 = (LazySimpleSingleton)ois.readObject();
ois.close();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton1==singleton2);
}catch (Exception e){
e.printStackTrace();
}
}
}
输出:
> Task :spring-sourcesTest:Test11.main()
lori.sp.LazySimpleSingleton@22927a81
lori.sp.LazySimpleSingleton@7aec35a
false
从运行结果可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了2次,违背了单例模式的设计初衷。那么,我们如何保证在序列化的情况下也能够实现单例模式呢?其实很简单,只需要增加readResvloe()方法即可,我们来看一下代码:
package lori.sp;
import java.io.Serializable;
//反序列化,导致破坏单例模式
@SuppressWarnings("serial")
public class LazySimpleSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式,从而转换成一个IO流,写入其他地方(可以是磁盘,网络IO)
//内存中的状态会永久保存下来
//反序列化就是将已经持久化的字节码内容转换为IO流,通过IO流的读取,进而将读取的内容转化为java对象,在转换过程中会重新创建对象
public final static LazySimpleSingleton INSTANCE= new LazySimpleSingleton();
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
package lori.sp;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test11 {
public static void main(String[] args) {
LazySimpleSingleton singleton1 =null ;
LazySimpleSingleton singleton2 =LazySimpleSingleton.getInstance();
FileOutputStream fos =null;
try {
fos =new FileOutputStream("LazySimpleSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("LazySimpleSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
singleton1 = (LazySimpleSingleton)ois.readObject();
ois.close();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton1==singleton2);
}catch (Exception e){
e.printStackTrace();
}
}
}
输出:
> Task :spring-sourcesTest:Test11.main()
lori.sp.LazySimpleSingleton@7aec35a
lori.sp.LazySimpleSingleton@7aec35a
true
我们一定会想,这是什么原因呢?为什么要这样写?我们来看一下JDK的源码实现以了解清楚。我们进入ObjectInputStream类的readObject()方法,代码如下:
private final Object readObject(Class<?> type)
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
if (! (type == Object.class || type == String.class))
throw new AssertionError("internal error");
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(type, false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
freeze();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
我们发现,在readObject方法中,又调用了重写的readObject0()方法,代码如下
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
...
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
...
}
我们看到TC_OBJECT中调用了ObjectInputStream的readOrdinaryObject()方法。代码如下:
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
return obj;
}
我们发现调用了ObjectStreamClass的isInstantiable()方法,而isInstantiable()方法的代码如下:
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
上述代码非常简单,就是判断一下构造方法是否为空,构造方法不为空就返回true,这就意味着,只要有无参构造方法就会实例化。
这个时候其实还没有找到加上readResolve()方法就避免了单例模式被破坏的真正原因,再回到ObjectInputStream的readOrdinaryObject()方法继续往下看:
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
判断无参构造方法是否存在之后,又调用了hasReadResolveMethod()方法,代码如下
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
上述代码非常简单,就是判断readResolveMethod 是否为空,不为空就返回true。那么readResolveMethod 是在哪里赋的值呢?通过全局查找知道,在私有方法ObjectStreamClass()中给readResolveMethod 进行赋值。代码如下:
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
上面逻辑其实就是通过反射找到一个无参的readResolve()方法,并且保存下来。现在回到ObjectInputStream的readOrdinaryObject()方法继续往下看,如果readResolve()方法存在,则调用invokeReadResolve()方法,代码如下:
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (readResolveMethod != null) {
try {
return readResolveMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getCause();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
throw new InternalError(th); // never reached
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
我们可以看到,在invokeReadResolve()方法中用反射调用了readResolveMethod()方法。
通过JDK源码分析我们可以看出,虽然增加readResolve()方法返回实例解决了单例模式被破坏的问题,但实际上实例化了2次,只不过新创建的对象没有被返回而已。如果创建对象的动作发生频率加快,就意味着内存分配开销也会随之增大,难道就真的没办法从根本上解决问题吗?
下面讲的注册式单例也许就能解决呢。
注册式单例模式
注册式单例模式又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例模式有两种,一种为枚举式单例模式,另一种为容器式单例模式
枚举式单例模式
先看一下枚举式单例模式的写法:
package lori.sp;
public enum Enumsingleton {
INSTANCE;
//在枚举类中申明一个新的成员变量,才能实现扩展
private Object data;
public void setData(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public static Enumsingleton getInstance(){return INSTANCE;}
}
package lori.sp;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test11 {
public static void main(String[] args) {
Enumsingleton singleton1 =null ;
Enumsingleton singleton2 =Enumsingleton.getInstance();
singleton2.setData(new Object());
FileOutputStream fos =null;
try {
fos =new FileOutputStream("Enumsingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("Enumsingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
singleton1 = (Enumsingleton)ois.readObject();
ois.close();
System.out.println(singleton1.getData());
System.out.println(singleton2.getData());
System.out.println(singleton1.getData()==singleton2.getData());
}catch (Exception e){
e.printStackTrace();
}
}
}
输出:
> Task :spring-sourcesTest:Test11.main()
java.lang.Object@1b2c6ec2
java.lang.Object@1b2c6ec2
true
没有做任何处理,我们发现运行结果和预期的一样,那么枚举单例模式为什么如此神奇,它的神秘之处又体现在哪里呢?
首先,我们来下载一个非常好用的反编译工具Jad(下载地址:https://varaneckas.com/jad/)
jad的安装以及集成IDEA见:https://blog.51cto.com/u_15410237/5444988
我们来看如下代码:
public static final Enumsingleton INSTANCE = new Enumsingleton("INSTANCE", 0);
private static Enumsingleton[] $values()
{
return (new Enumsingleton[] {
INSTANCE
});
}
原来,枚举单例模式在静态代码块中就给INSTANCE 进行了赋值,是饿汉单例模式的实现,至此,我们还可以设想,序列化能否破坏枚举式单例模式呢?不妨再来看一下JDK源码,还是回到ObjectInputStream的readObject0()方法:
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
...
case TC_ENUM:
if (type == String.class) {
throw new ClassCastException("Cannot cast an enum to java.lang.String");
}
return checkResolve(readEnum(unshared));
...
}
我们看到,在readObject0()中调用了readEnum()方法的代码实现:
private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}
int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
我们发现,枚举类型其实通过类名和类对象找到唯一一个枚举对象。因此,枚举对象不可能被类加载器加载多次。那么反射是否能破坏枚举式单例模式呢?来看一段测试代码:
package lori.sp;
import java.lang.reflect.Constructor;
public class Test11 {
public static void main(String[] args) {
try {
Constructor<Enumsingleton> constructor = Enumsingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
} catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
> Task :spring-sourcesTest:Test11.main()
java.lang.NoSuchMethodException: lori.sp.Enumsingleton.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3585)
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2754)
at lori.sp.Test11.main(Test11.java:10)
结果中报的是java.lang.NoSuchMethodException异常,意思是没找到无参的构造方法。这时候,我们打开java.lang.Enum的源码,查看他的构造方法,只有一个protected类型的构造方法,代码如下:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
我们再来做下面这样一个测试:
package lori.sp;
import java.lang.reflect.Constructor;
public class Test11 {
public static void main(String[] args) {
try {
Constructor<Enumsingleton> constructor = Enumsingleton.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Enumsingleton enumsingleton = constructor.newInstance("Tom",666);
} catch (Exception e){
e.printStackTrace();
}
}
}
输出:
> Task :spring-sourcesTest:Test11.main()
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:492)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at lori.sp.Test11.main(Test11.java:12)
这时的错误已经非常明显了,“Cannot reflectively create enum objects”,即不能用反射来创建枚举类型,我们再来看一下JDK源码,进入Constructor的newInstance()方法:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
Class<?> caller = override ? null : Reflection.getCallerClass();
return newInstanceWithCaller(initargs, !override, caller);
}
/* package-private */
T newInstanceWithCaller(Object[] args, boolean checkAccess, Class<?> caller)
throws InstantiationException, IllegalAccessException,
InvocationTargetException
{
if (checkAccess)
checkAccess(caller, clazz, clazz, modifiers);
ConstructorAccessor ca = constructorAccessor; // read @Stable
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(args);
return inst;
}
private ConstructorAccessor acquireConstructorAccessor() {
// First check to see if one has been created yet, and take it
// if so.
Constructor<?> root = this.root;
ConstructorAccessor tmp = root == null ? null : root.getConstructorAccessor();
if (tmp != null) {
constructorAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
// Ensure the declaring class is not an Enum class.
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
tmp = reflectionFactory.newConstructorAccessor(this);
// set the constructor accessor only if it's not using native implementation
if (VM.isJavaLangInvokeInited())
setConstructorAccessor(tmp);
}
return tmp;
}
从上述代码可以看到,在newInstance()方法调用的newInstanceWithCaller方法中调用的acquireConstructorAccessor做出了强制的判断,如果修饰符是Modifier.ENUM 枚举类型,直接抛出异常。
到此为止,我们已经非常清晰明了了吗?JDK枚举的语法特殊性及反射也为枚举保驾护航,让枚举单例模式成为一种比较优雅的实现。
容器式单例模式
接下来看注册式单例模式的另一种写法,如下:
package lori.sp;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc =new ConcurrentHashMap<String,Object>();
public static Object getBean(String className){
synchronized(ioc){
if(!ioc.containsKey(className)){
Object obj =null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className,obj);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return obj;
}else {
return ioc.get(className);
}
}
}
}
容器单例模式适用于实例非常多的情况,便于管理。但他式非线程安全的。
原型模式
原型模式是用于创建重复的对象,同时又能保证性能。
【本体给外部提供一个克隆体进行使用】
原型模式的克隆分为深克隆和浅克隆
浅克隆:创建一个新对象,新的对象属性和原来完全相同,对于非基本类型属性,仍然指向原有属性所指向的对象的内存地址
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有地址
cloneable接口
实现原型模式需要实现标记性接口Cloneable,只有实现这个接口,然后在类中重写Object类中的clone()方法,然后通过类调用clone()方法,才能克隆成功,如果不实现这个标记型接口,会抛出CloneNotSupportedException(克隆不被支持)异常。
Object中的clone()方法
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
这里有一个问题,clone()方法是一个空方法,那么,它是如何判断类是否实现cloneable接口呢?
原因是在于这个方法上有一个native关键字修饰
native修饰的方法都是空方法,但这些方法都是有实现体的(这里也就间接说明,native 关键字不能与abstract同时使用,abstract与java中接口的方法类似,它显式的说明了修饰的方法,但是当前是没有实现体的,abstract的实现体都由他的子类实现了),只不过native的实现体都是由非java代码实现的,每一个Native修饰的方法在JVM中都有一个同名的实现体。Native方法的判断都是由JVM'中的实现体实现的,另外,Native修饰的方法,对返回类型,异常控制等都没有约束。
由此可见,判断是否实现Cloneable接口,实在Native的实现体JVM中实现的。
深克隆与浅克隆
首先,看一下java中几种创建对象的方式:
1、一种是new,通过new关键字在堆中为对象开辟新空间,在执行new时,首先会看要创建对象的类型,知道了类型,才能知道需要给这个对象分配多大的内存区域,分配内存后,调用对象的构造函数,填充对象中各种变量的值,将对象初始化,然后通过构造方法返回对象的地址。
2、另一种是clone,clone也是首先分配内存,这里分配的内存,与调用clone方法对象的内存相同,然后将原对象中各个变量的值填充到新的对象中,填充完成后,clone方法返回一个新的地址,这个新地址的对象与原对象相同,只是地址不同。
浅克隆
package com.lori.spring.prototype.v1;
public class Person implements Cloneable{
private int age;
private int score;
private Location location;
public Person(){
this.age = 8;
this.score = 100;
this.location = new Location("bj",22);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public int getAge() {
return age;
}
public int getScore() {
return score;
}
public Location getLocation() {
return location;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", score=" + score +
", location=" + location +
'}';
}
public void setAge(int age) {
this.age = age;
}
public void setScore(int score) {
this.score = score;
}
public void setLocation(Location location) {
this.location = location;
}
}
package com.lori.spring.prototype.v1;
public class Location {
private String street;
private int roomNo;
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
public Location(String street,int roomNo){
this.street = street;
this.roomNo = roomNo;
}
public void setRoomNo(int roomNo) {
this.roomNo = roomNo;
}
public void setStreet(String street) {
this.street = street;
}
}
package com.lori.spring.prototype.v1;
public class Test1 {
public static void main(String[] args) throws Exception{
Person person1 = new Person();
Person person2=(Person)person1.clone();
System.out.println(person2.getAge()+" "+person2.getScore());
System.out.println(person2.getLocation());
System.out.println(person1.getAge() == person2.getAge());
System.out.println(person1.getScore() == person2.getScore());
System.out.println(person1.getLocation() == person2.getLocation());
System.out.println("修改前:person1.hashCode:::::"+person1.hashCode());
System.out.println("修改前:person2.hashCode:::::"+person2.hashCode());
System.out.println("++++++++++++++++++++++++修改Person的Local中的属性");
person2.getLocation().setStreet("sh");
System.out.println("person1:::::"+person1.getLocation());
System.out.println("person2:::::"+person2.getLocation());
System.out.println(person1.getLocation() == person2.getLocation());
System.out.println(person1.getAge());
System.out.println(person2.getAge());
System.out.println("++++++++++++++++++++++++修改Person的年龄");
person2.setAge(20);
System.out.println(person1.getAge());
System.out.println(person2.getAge());
}
}
输出:
> Task :loris-spring-test:Test1.main()
8 100
Location{street='bj', roomNo=22}
true
true
true
修改前:person1.hashCode:::::664740647
修改前:person2.hashCode:::::804564176
++++++++++++++++++++++++修改Person的Local中的属性
person1:::::Location{street='sh', roomNo=22}
person2:::::Location{street='sh', roomNo=22}
true
8
8
++++++++++++++++++++++++修改Person的年龄
8
20
从上面结果我们可以看出,person2的hashcode和person1的hashCode是不一样的,也就是clone方法并不是把person1的方法赋予了person2,而是在堆中新开辟了一个空间,将person1复制过去,将新地址返回给Person2。
我们可以看出,修改person2的age,不会导致person1的age改变。而修改person2的Location却可以让person1的Location也发生改变,这样会存在问题,所以,有了我们的深克隆,如下
package com.lori.spring.prototype.v1;
public class Person implements Cloneable{
private int age;
private int score;
private Location location;
public Person(){
this.age = 8;
this.score = 100;
this.location = new Location("bj",22);
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
person.location = (Location) location.clone();
return person;
}
public int getAge() {
return age;
}
public int getScore() {
return score;
}
public Location getLocation() {
return location;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", score=" + score +
", location=" + location +
'}';
}
public void setAge(int age) {
this.age = age;
}
public void setScore(int score) {
this.score = score;
}
public void setLocation(Location location) {
this.location = location;
}
}
package com.lori.spring.prototype.v1;
public class Location implements Cloneable{
private String street;
private int roomNo;
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
public Location(String street,int roomNo){
this.street = street;
this.roomNo = roomNo;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void setRoomNo(int roomNo) {
this.roomNo = roomNo;
}
public void setStreet(String street) {
this.street = street;
}
}
package com.lori.spring.prototype.v1;
public class Test1 {
public static void main(String[] args) throws Exception{
Person person1 = new Person();
Person person2=(Person)person1.clone();
System.out.println(person2.getAge()+" "+person2.getScore());
System.out.println(person2.getLocation());
System.out.println(person1.getAge() == person2.getAge());
System.out.println(person1.getScore() == person2.getScore());
System.out.println(person1.getLocation() == person2.getLocation());
System.out.println("修改前:person1.hashCode:::::"+person1.hashCode());
System.out.println("修改前:person2.hashCode:::::"+person2.hashCode());
System.out.println("++++++++++++++++++++++++修改Person的Local中的属性");
person2.getLocation().setStreet("sh");
System.out.println("person1:::::"+person1.getLocation());
System.out.println("person2:::::"+person2.getLocation());
System.out.println(person1.getLocation() == person2.getLocation());
System.out.println(person1.getAge());
System.out.println(person2.getAge());
System.out.println("++++++++++++++++++++++++修改Person的年龄");
person2.setAge(20);
System.out.println(person1.getAge());
System.out.println(person2.getAge());
}
}
输出:
> Task :loris-spring-test:Test1.main()
8 100
Location{street='bj', roomNo=22}
true
true
false
修改前:person1.hashCode:::::664740647
修改前:person2.hashCode:::::804564176
++++++++++++++++++++++++修改Person的Local中的属性
person1:::::Location{street='bj', roomNo=22}
person2:::::Location{street='sh', roomNo=22}
false
8
8
++++++++++++++++++++++++修改Person的年龄
8
20
我们可以看出,深克隆在Person克隆的时候,在克隆一遍此person'的location即可,从结果可以看出,此时再重新给person2的location赋值时,person1的location不会发生任何变化。
小知识
String类型不需要进行深克隆,String类型直接指向一个常量池,所以它本来就是共用的,String类型的数据发生变化时,会新建一个字符串,原有字符串不会发生变化,依然在常量池中存在。比如person1克隆一个person2,此时,person1的String参数name指向张三,person2的name也指向张三,若person2修改name为李四,只会person2的name指向新的引用李四,person1的name还是指向它原本的张三
工厂模式
现实生活中我们都知道,原始社会自给自足(没有工厂)、农耕社会有了小作坊(简单工厂,如民间酒坊)、工业革命后有了流水线(工厂方法,自产自销)、现代产业链中有代工厂(抽象工厂,如富士康)
我们的项目代码同样也是由简到繁一步步而来的,但对于调用者来说却越来越简单化了
简单工厂
简单工厂模式是指由一个工厂对象决定创建哪一种产品类的实例,但它不属于GoF的23种设计模式。简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心
简单工厂带来的问题:更多的产品就会违反开闭原则
package com.example.designPatterns.factory.simple;
/**
* 工厂的产品
*/
public abstract class AbstractCar {
String engine;
public abstract void run();
}
package com.example.designPatterns.factory.simple;
/**
* 简单工厂
* 1、产品数量极少
*/
public class WulingSimpleFactory {
public AbstractCar newCar(String type){
if("van".equals(type)){
return new Vancar();
}else if("mini".equals(type)){
return new MiniCar();
}
return null;
}
}
package com.example.designPatterns.factory.simple;
public class MiniCar extends AbstractCar{
public MiniCar(){
this.engine ="四缸水平对置发动机";
}
@Override
public void run() {
System.out.println(engine+"dududu~~");
}
}
package com.example.designPatterns.factory.simple;
/**
* 具体产品
*/
public class Vancar extends AbstractCar{
public Vancar(){
this.engine="单杠柴油机";
}
@Override
public void run() {
System.out.println(engine+"dadada~~~");
}
}
package com.example.designPatterns.factory.simple;
public class MainTest {
public static void main(String[] args) {
WulingSimpleFactory wulingSimpleFactory=new WulingSimpleFactory();
AbstractCar van = wulingSimpleFactory.newCar("van");
van.run();
AbstractCar mini = wulingSimpleFactory.newCar("mini");
mini.run();
AbstractCar okk = wulingSimpleFactory.newCar("okk");
System.out.println(okk);
}
}
输出:
Connected to the target VM, address: '127.0.0.1:50832', transport: 'socket'
单杠柴油机dadada~~~
四缸水平对置发动机dududu~~
null
Disconnected from the target VM, address: '127.0.0.1:50832', transport: 'socket'
Process finished with exit code 0
我们再举一个例子,以课程为例。某学员目前开设有java架构、大数据、人工智能等课程,已经形成一个生态。我们可以定义一个课程标准ICourse接口:
package lori.sfp;
public interface ICourse {
/**
* 录制视频
*/
public void record();
}
创建一个java课程的实践类:
package lori.sfp;
public class JavaCourse implements ICourse{
@Override
public void record() {
System.out.println("录制java课程");
}
}
我们会这样写客户端的调用代码:
package lori.sfp;
public class Test06 {
public static void main(String[] args) {
ICourse course = new JavaCourse();
course.record();
}
}
输出:
> Task :spring-sourcesTest:Test06.main()
录制java课程
在上面的代码中,父类ICourse指向子类JavaCourse的引用,应用层代码需要依赖JavaCourse,如果业务扩展,继续增加PythonCourse甚至更多课程,那么客户端的依赖会变得越来越臃肿。因此,我们要想办法把这种依赖减弱,把创建细节隐藏起来。虽然在目前的代码中,创建对象的过程并不复杂,但从代码设计的角度来讲不易于扩展。现在,我们用简单工厂模式对代码进行优化。先增加课程类PythonCourse:
package lori.sfp;
public class PythonCourse implements ICourse{
@Override
public void record() {
System.out.println("录制Python课程");
}
}
创建工厂类CourseFactory:
package lori.sfp;
public class CourseFactory {
public void create(String name){
if ("java".equals(name)){
JavaCourse javaCourse = new JavaCourse();
javaCourse.record();
}else if("python".equals(name)){
PythonCourse pythonCourse = new PythonCourse();
pythonCourse.record();
}
}
}
修改客户端代码如下:
package lori.sfp;
public class Test06 {
public static void main(String[] args) {
CourseFactory factory = new CourseFactory();
factory.create("java");
}
}
当然,为了方便调用,可将工厂中的create()方法修改为静态方法
客户端调用变简单了,但如果我们的业务扩展,要增加前端课程,那么工厂中的create()方法就要每次都根据产品的增加修改代码逻辑,不符合开闭原则。因此,我们还可以对简单工厂模式继续优化,采用反射技术:
package lori.sfp;
import java.lang.reflect.InvocationTargetException;
public class CourseFactory {
public ICourse create(String name){
if(!(null ==name || "".equals(name))){
try {
return (ICourse)Class.forName(name).getDeclaredConstructor().newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
return null;
}
}
客户端调用代码:
package lori.sfp;
public class Test06 {
public static void main(String[] args) {
CourseFactory courseFactory = new CourseFactory();
ICourse course = courseFactory.create("lori.sfp.JavaCourse");
course.record();
}
}
优化之后,产品不断丰富的过程中不需要修改CourseFactory中的代码。但还是有个问题是,方法参数是字符串,可控性有待提升,而且还需要强制转型。再修改一下代码:
package lori.sfp;
import java.lang.reflect.InvocationTargetException;
public class CourseFactory {
public ICourse create(Class<? extends ICourse> clazz) {
if (null != clazz) {
try {
return clazz.getDeclaredConstructor().newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
return null;
}
}
优化客户端代码:
package lori.sfp;
public class Test06 {
public static void main(String[] args) {
CourseFactory courseFactory = new CourseFactory();
ICourse course = courseFactory.create(JavaCourse.class);
course.record();
}
}
简单工厂模式在jdk源码中也无处不在,现在我们来举个例子。例如Calendar类
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
cal = switch (caltype) {
case "buddhist" -> new BuddhistCalendar(zone, aLocale);
case "japanese" -> new JapaneseImperialCalendar(zone, aLocale);
case "gregory" -> new GregorianCalendar(zone, aLocale);
default -> null;
};
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
简单工厂模式也有他的缺点:工厂类的职责相对过重,不易于扩展过于复杂的产品结构
工厂方法模式
工厂方法模式是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法模式让类的实例化推迟到子类中进行。在工厂方法模式中用户只需要关心所需要的产品对应的工厂,无需关心创建细节,而且加入新的产品时符合开闭原则。
工厂方法模式主要解决产品的扩展问题。在简单工厂模式中,随着产品链的丰富,如果每个课程的创建逻辑有区别,则工厂的职责会变得越来越多,有点像万能工厂,不便于维护。根据单一职责原则我们将职能继续拆分,专人干专事。java课程由java工厂创建,Python课程由Python工厂创建,对工厂本身也做一个抽象。
/**
*四个角色
*1、抽象产品
*2、具体产品
*3、抽象工厂
*4、具体工厂
*/
package com.example.designPatterns.factory.factorymethod;
/**
* 工厂的产品
* 怎么把一个功能提升一个层次,定义抽象(抽象类,接口)
* 抽象类,接口 就会有多实现,多实现自然就有多功能
*/
//抽象产品
public abstract class AbstractCar {
String engine;
public abstract void run();
}
package com.example.designPatterns.factory.factorymethod;
/**
* 抽象工厂的层级
*/
public abstract class AbstractCarFactory {
public abstract AbstractCar newCar();
}
package com.example.designPatterns.factory.factorymethod;
public class MiniCar extends AbstractCar {
public MiniCar(){
this.engine ="四缸水平对置发动机";
}
@Override
public void run() {
System.out.println(engine+"dududu~~");
}
}
package com.example.designPatterns.factory.factorymethod;
//具体产品
public class RacingCar extends AbstractCar{
public RacingCar(){
this.engine="V8发动机";
}
@Override
public void run() {
System.out.println(engine+"嗖嗖~~");
}
}
package com.example.designPatterns.factory.factorymethod;
/**
* 具体产品
*/
public class Vancar extends AbstractCar {
public Vancar(){
this.engine="单杠柴油机";
}
@Override
public void run() {
System.out.println(engine+"dadada~~~");
}
}
package com.example.designPatterns.factory.factorymethod;
/**
* 具体工厂
*/
public class WulingMiniCarFactory extends AbstractCarFactory{
@Override
public AbstractCar newCar() {
return new MiniCar();
}
}
package com.example.designPatterns.factory.factorymethod;
/**
* 具体工厂
*/
public class WulingRacingCarFactory extends AbstractCarFactory{
@Override
public AbstractCar newCar() {
return new RacingCar();
}
}
package com.example.designPatterns.factory.factorymethod;
/**
* 具体工厂
*/
public class WulingVanCarFactory extends AbstractCarFactory{
@Override
public AbstractCar newCar() {
return new Vancar();
}
}
package com.example.designPatterns.factory.factorymethod;
public class MainTest {
public static void main(String[] args) {
AbstractCarFactory abstractCarFactory=new WulingRacingCarFactory();
AbstractCar abstractCar = abstractCarFactory.newCar();
abstractCar.run();
System.out.println("==================");
abstractCarFactory=new WulingMiniCarFactory();
AbstractCar abstractCar1 = abstractCarFactory.newCar();
abstractCar1.run();
}
}
我们再来看一个例子,先创建ICourseFactory接口:
package lori.fmp;
import lori.sfp.ICourse;
public interface ICourseFactory {
ICourse create();
}
在分别创建子工厂,JavaCourseFactory类的代码入下:
package lori.fmp;
import lori.sfp.ICourse;
import lori.sfp.JavaCourse;
public class JavaCourseFactory implements ICourseFactory{
@Override
public ICourse create() {
return new JavaCourse();
}
}
PythonCourseFactory类的代码如下:
package lori.fmp;
import lori.sfp.ICourse;
import lori.sfp.PythonCourse;
public class PythonCourseFactory implements ICourseFactory{
@Override
public ICourse create() {
return new PythonCourse();
}
}
测试代码如下:
package lori.fmp;
import lori.sfp.ICourse;
public class Test08 {
public static void main(String[] args) {
ICourseFactory javaCourseFactory = new JavaCourseFactory();
ICourse javaCourse = javaCourseFactory.create();
javaCourse.record();
PythonCourseFactory pythonCourseFactory = new PythonCourseFactory();
ICourse pythonCourse = pythonCourseFactory.create();
pythonCourse.record();
}
}
工厂方法模式适用于以下场景:
1、创建对象需要大量重复的代码。
2、客户端(应用层)不依赖于产品类实例如何被创建、如何被实现等细节
3、一个类通过其子类来指定创建哪个对象
工厂方法模式也有缺点:
1、类的个数容易过多,增加复杂度
2、增加了系统的抽象性和理解难度
抽象工厂
抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们的具体类。客户端(应用层)不依赖于产品类和实例如何被创建、如何被实现等细节,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
讲解抽象工厂模式之前,我们要了解两个概念:产品等级结构和产品族,来看一下下面这张图:
上图有正方形,圆形、菱形三种图形,相同颜色深浅的代表同一个产品族,相同形状的代表同一个产品等级结构。同样可以做个类比,比如说美的的电器生产多种家用电器,上图中颜色最深的正方形代表美的的洗衣机、颜色最深的圆圈代表美的的空调、颜色最深的菱形代表美的的热水器,颜色最深的一排都属于美的品牌,都属于美的电器这个产品族。再看最右侧的菱形,颜色最深的我们指定它为美的热水器,第二排颜色稍浅一点的菱形可代表海信热水器。同理,同一产品结构下还有格力热水器,格力空调,格力洗衣机。
再看下面这张图片
通过这两张图的对比,相信大家对抽象工厂模式有了非常形象的理解。接下来我们来看一个具体的业务场景并用代码来实现。还是以课程为例,某学院第三期课程有了新的标准,每个课程不仅要提供课程的录播视频,还要提供老师的课堂笔记。相当于现在的业务变更为同一课程不单纯包含一个课程信息,同时要包含录播视频、课堂笔记,甚至还要提供源码才能构成一个完整的课程。在产品等级中增加两个产品:IVideo录播视频和INote课堂笔记。
IVideo接口如下:
package lori.afp;
public interface IVideo {
void record();
}
INote接口如下:
package lori.afp;
public interface INote {
void edit();
}
然后创建一个抽象工厂类CourseFactory:
package lori.afp;
/**
* 抽象工厂是用户的主入口
* 是Spring中应用的最广泛的一种设计设计模式
* 易于扩展
*/
public interface CourseFactory {
INote createNote();
IVideo createVideo();
}
接下来,创建java产品族的java视频类JavaVideo:
package lori.afp;
public class JavaVideo implements IVideo{
@Override
public void record() {
System.out.println("录制java视频");
}
}
扩展产品等级java课堂笔记类JavaNote
package lori.afp;
public class JavaNote implements INote{
@Override
public void edit() {
System.out.println("编写java笔记");
}
}
创建java产品族的具体工厂JavaCourseFactory
package lori.afp;
public class JavaCourseFactory implements CourseFactory{
@Override
public INote createNote() {
return new JavaNote();
}
@Override
public IVideo createVideo() {
return new JavaVideo();
}
}
然后创建Python产品的Python视频类PythonVideo
package lori.afp;
public class PythonVideo implements IVideo{
@Override
public void record() {
System.out.println("录制Python视频");
}
}
扩展产品等级Python课堂笔记类PythonNote
package lori.afp;
public class PythonNote implements INote{
@Override
public void edit() {
System.out.println("编写Python笔记");
}
}
创建Python产品族的具体工厂PythonCourseFactory
package lori.afp;
public class PythonCourseFactory implements CourseFactory{
@Override
public INote createNote() {
return new PythonNote();
}
@Override
public IVideo createVideo() {
return new PythonVideo();
}
}
来看客户调用代码:
package lori.afp;
public class Test10 {
public static void main(String[] args) {
JavaCourseFactory javaCourseFactory = new JavaCourseFactory();
javaCourseFactory.createNote().edit();
javaCourseFactory.createVideo().record();
PythonCourseFactory pythonCourseFactory = new PythonCourseFactory();
pythonCourseFactory.createNote().edit();
pythonCourseFactory.createVideo().record();
}
}
上面的代码完整的描述了两个族:java课程和python课程,也描述了两个产品等级视频和笔记。抽象工厂模式非常完美清晰地描述了这样一层复杂的关系。但是不知道大家有没有发现,如果我们再继续扩展产品等级,将源码Sourse也加入课程,那么我们的代码从抽象工厂到具体工厂全部都要调整,很显然不符合开闭原则,由此可知,抽象工厂模式也是优缺点的,
1、规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
2、增加了系统的抽象性和理解难度
但在实际应用中,我们千万不能‘犯强迫症’或者‘有洁癖’。在实际需求中,产品等级结构升级是一件非常正常的事情。只要不频繁升级,根据实际情况可以不遵循开闭原则。代码每半年或一年升级一次又有何不可呢?
我们再来看一个例子
产品等级结构:产品等级结构即产品继承结构,如抽象类是手机,其子类有小米手机,华为手机
产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的位于不同产品等级结构中的一组产品
package com.example.designPatterns.factory.abstractfactory;
import java.util.List;
/**
* 五菱总厂
*/
public abstract class WulingFactory {
List<String> rules;
abstract AbstractCar newCar();
abstract AbstractMask newMask();
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 工厂的产品
* 怎么把一个功能提升一个层次,定义抽象(抽象类,接口)
* 抽象类,接口 就会有多实现,多实现自然就有多功能
*/
public abstract class AbstractCar {
String engine;
public abstract void run();
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 抽象产品
*/
public abstract class AbstractMask {
Integer price;
public abstract void protectedMe();
}
package com.example.designPatterns.factory.abstractfactory;
/**
* wuling汽车集团
*/
public abstract class WulingCarFactory extends WulingFactory{
@Override
abstract AbstractCar newCar();
@Override
AbstractMask newMask() {
return null;
}
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 五菱口罩集团
*/
public abstract class WulingMaskFactory extends WulingFactory{
@Override
AbstractCar newCar() {
return null;
}
@Override
abstract AbstractMask newMask();
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 分厂,只造MiniCar
*/
public class WulingMiniCarFactory extends WulingCarFactory{
@Override
AbstractCar newCar() {
return new MiniCar();
}
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 分厂,只造RacingCar
*/
public class WulingRacingCarFactory extends WulingCarFactory{
@Override
AbstractCar newCar() {
return new RacingCar();
}
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 只造口罩
*/
public class WulingHangZhouMaskFactory extends WulingMaskFactory{
@Override
AbstractMask newMask() {
return new CommonMask();
}
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 只造口罩
*/
public class WulingWuHanMaskFactory extends WulingMaskFactory{
@Override
AbstractMask newMask() {
return new N95mask();
}
}
package com.example.designPatterns.factory.abstractfactory;
public class RacingCar extends AbstractCar {
public RacingCar(){
this.engine="V8发动机";
}
@Override
public void run() {
System.out.println(engine+"嗖嗖~~");
}
}
package com.example.designPatterns.factory.abstractfactory;
public class MiniCar extends AbstractCar {
public MiniCar(){
this.engine ="四缸水平对置发动机";
}
@Override
public void run() {
System.out.println(engine+"dududu~~");
}
}
package com.example.designPatterns.factory.abstractfactory;
public class CommonMask extends AbstractMask{
public CommonMask(){
this.price=1;
}
@Override
public void protectedMe() {
System.out.println("普通口罩简单保护,请及时更换~~~");
}
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 具体产品
*/
public class N95mask extends AbstractMask{
public N95mask(){
this.price=5;
}
@Override
public void protectedMe() {
System.out.println("N95口罩超级防护");
}
}
package com.example.designPatterns.factory.abstractfactory;
/**
* 抽象工厂为了满足更多产品线
* 虽然写起来有很多类,但是用起来很清晰简单
*/
public class MainTest {
public static void main(String[] args) {
WulingFactory wulingFactory=new WulingWuHanMaskFactory();
AbstractCar abstractCar = wulingFactory.newCar();
AbstractMask abstractMask = wulingFactory.newMask();
abstractMask.protectedMe();
wulingFactory=new WulingHangZhouMaskFactory();
AbstractMask abstractMask1 = wulingFactory.newMask();
abstractMask1.protectedMe();
}
}
输出:
N95口罩超级防护
普通口罩简单保护,请及时更换~~~
Disconnected from the target VM, address: '127.0.0.1:51165', transport: 'socket'
Process finished with exit code 0
建造者模式
建造者模式:分离复杂对象的构建和表示。同样的构建过程可以创建不同的表示。
产品角色:要造的东西
抽象建造者
具体建造者
创建的东西细节复杂,还必须暴露给使用者。屏蔽过程而不屏蔽细节
package com.example.designPatterns.builder;
import lombok.Builder;
@Builder
public class Phone {
protected String cpu;
protected String mem;
protected String disk;
protected String cam;
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", mem='" + mem + '\'' +
", disk='" + disk + '\'' +
", cam='" + cam + '\'' +
'}';
}
public String getCpu() {
return cpu;
}
public String getMem() {
return mem;
}
public String getDisk() {
return disk;
}
public String getCam() {
return cam;
}
}
package com.example.designPatterns.builder;
/**
* 抽象建造者
*/
public abstract class AbstractBuilder {
Phone phone;
abstract AbstractBuilder customCpu(String cpu);
abstract AbstractBuilder customMem(String mem);
abstract AbstractBuilder customDisk(String disk);
abstract AbstractBuilder customCam(String cam);
Phone getPhone(){
return phone;
}
}
package com.example.designPatterns.builder;
public class XiaomiBuilder extends AbstractBuilder{
public XiaomiBuilder(){
phone=Phone.builder().build();
}
/**
*
* @param cpu
*/
@Override
AbstractBuilder customCpu(String cpu) {
phone.cpu=cpu;
return this;
}
@Override
AbstractBuilder customMem(String mem) {
phone.mem=mem;
return this;
}
@Override
AbstractBuilder customDisk(String disk) {
phone.disk=disk;
return this;
}
@Override
AbstractBuilder customCam(String cam) {
phone.cam=cam;
return this;
}
}
package com.example.designPatterns.builder;
public class MainTest {
public static void main(String[] args) {
AbstractBuilder builder=new XiaomiBuilder();
//方法一:
builder.customCam("2亿");
builder.customCpu("AAAA");
builder.customDisk("2T");
builder.customMem("16G");
Phone phone = builder.getPhone();
System.out.println(phone);
System.out.println("====================================");
//方法二:链式调用
Phone aaaa = builder.customCam("2亿")
.customCpu("AAAA")
.customDisk("2T")
.customMem("16G")
.getPhone();
System.out.println(aaaa);
System.out.println("====================================");
//方法三:Lombok
Phone aaaa1 = Phone.builder().cam("2亿")
.cpu("AAAA")
.disk("2T")
.mem("16G")
.build();
System.out.println(aaaa1);
}
}