03-创建型模式(5)
- 创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建、使用
- 单例模式:用来创建全局唯一对象
- 工厂模式:用来创建不同但是相关类型的对象(继承同一父类、接口的一组子类),由给定的参数来决定创建哪种类型的对象
- 建造者模式:用来创建复杂对象,可以通过设置不同的可选参数,定制化地创建不同的对象
- 原型模式:针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的
创建型模式提供创建对象机制,提升已有代码的灵活性、复用性
- 常用的:单例模式、工厂模式(工厂方法、抽象工厂)、建造者模式
- 不常用的:原型模式
1. 单例模式
- 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类
- 保证一个类只有一个实例
- 为该实例提供一个全局访问节点
2. 饿汉式
- 在类加载期间,初始化静态实例,保证
instance
实例的创建是线程安全的(实例在类加载时实例化,有JVM保证线程安全) - 特点:不支持延迟加载实例(懒加载),类加载比较慢,但是获取实例对象比较快
- 问题:该对象足够大的话,而一直没有使用就会造成内存的浪费
/*
* 单例模式 - 饿汉式
* 在类加载期间初始化私有的静态实例,保证instance实例创建过程是线程安全的.
* 特点: 不支持延时加载,获取实例对象的速度比较快,但是如果对象比较大,而且一直没有使用就会造成内存的浪费.
*/
public class Singleton_01 {
// 1. 私有构造方法
private Singleton_01() {
}
// 2. 在本类中创建私有静态的全局对象
private static Singleton_01 instance = new Singleton_01();
// 3. 提供一个全局访问点, 供外部获取单例对象
public static Singleton_01 getInstance() {
return instance;
}
}
3. 懒汉式(线程不安全)
- 实现了懒加载,只有调用
getInstance()
时,才创建对象。但是如果是多线程情况,出现线程不安全
- 在单例类被实例化之前,有两个线程同时在获取单例对象,线程A在执行完
if (instance == null)
后,线程调度机制将CPU资源分配给线程B,此时线程B在执行if (instance == null)
时也发现单例类还没有被实例化,这样就会导致单例类被实例化两次- 为了防止这种情况发生,需要对
getInstance()
方法同步处理。改进后的懒汉模式
/*
* 单例模式 - 懒汉式
* 特点: 支持延时加载, 只有调用getInstance()时, 才会创建对象
*/
public class Singleton_02 {
// 1. 私有构造方法
private Singleton_02() {
}
// 2. 在本类中创建私有静态的全局对象
private static Singleton_02 instance;
// 3. 通过判断对象是否被初始化, 来选择是否创建对象
public static Singleton_02 getInstance() {
if (instance == null) {
instance = new Singleton_02();
}
return instance;
}
}
4. 懒汉式(线程安全)
原理:使用同步锁synchronized
锁住创建单例的方法,防止多个线程同时调用,从而避免造成单例被多次创建
getInstance()
方法块只能运行在1个线程中。其他线程会被阻塞而一直等待。保证了多线程模式下单例对象的唯一性- 频繁地用到。频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈
/*
* 单例模式 - 懒汉式(线程安全)
* 使用synchronized锁, 锁住创建单例对象的方法,防止多个线程同时调用
* 缺点: 因为对getInstance()加了锁, 导致这个函数的并发度很低
*/
public class Singleton_03 {
// 1. 私有构造方法
private Singleton_03() {
}
// 2. 在本类中创建私有静态的全局对象
private static Singleton_03 instance;
// 3. 通过添加synchronize, 保证多线程模式下的单例对象的唯一性
public static synchronized Singleton_03 getInstance() {
if (instance == null) {
instance = new Singleton_03();
}
return instance;
}
}
5. 双重校验
- 饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发
- 声明变量时,使用了
volatile
关键字- 保证线程的可见性:一个被
volatile
修饰的变量被一个线程修改时,其他线程可以立刻得到修改之后的结果 - 禁止指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果正确,但是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题
- 保证线程的可见性:一个被
- 将同步方法改为同步代码块。在同步代码块中使用二次检查,以保证其不被重复实例化,同时在调用
getInstance()
不进行同步锁,效率高
/**
* 单例模式 - 双重校验
*/
public class Singleton_04 {
// 1. 私有构造方法
private Singleton_04() {
}
// 2. 在本类中创建私有静态的全局对象
// 使用volatile保证变量可见性, 屏蔽指令重排序
private volatile static Singleton_04 instance;
// 3. 获取单例对象的静态方法
public static Singleton_04 getInstance() {
// 第一次判断, 如果instance不为null, 不进入抢锁阶段, 直接返回实例
if (instance == null) {
synchronized (Singleton_04.class) {
// 第二次判断, 抢到锁之后再次进行判断,判断是否为null
if (instance == null) {
instance = new Singleton_04();
/*
* 上面的创建对象的代码, 在JVM中被分为三步:
* 1. 分配内存空间
* 2. 初始化对象
* 3. 将instance指向分配好的内存空间
*/
}
}
}
return instance;
}
}
在双重检查锁模式中为什么需要使用 volatile 关键字?
- 在java内存模型中,
volatile
作用可以是保证可见性、禁止指令重排 singleton = new Singleton()
,并非是一个原子操作,事实上,在JVM中上述语句至少做了以下这3件事- 给
singleton
分配内存空间 - 开始调用
Singleton
的构造函数等,来初始化singleton
- 将
singleton
对象指向分配的内存空间(执行完这步singleton
就不是null
了)
- 给
指令重排底层
- 留意一下《1-2-3》的顺序,因为存在指令重排序的优化,也就是说第2步、第3步的顺序是不能保证的,最终的执行顺序,可能是《1-2-3》,也有可能是《1-3-2》
- 如果《1-3-2》,那么在第3步执行完后,
singleton
就不是null
了,可这时第2步并没有执行,singleton
对象未完成初始化 - 假设此时线程2进入
getInstance()
,由于singleton
已经不是null
,会通过第一重检查并直接返回,但这时的singleton
并没有完成初始化,使用这个实例会报错
- 如果《1-3-2》,那么在第3步执行完后,
6. 静态内部类
- 根据 静态内部类 的特性(外部类的加载不影响内部类),解决了按需加载、线程安全问题,同时实现简洁
- 在静态内部类里创建单例,在装载该内部类时才会去创建单例
- 线程安全:类是由
JVM
加载,而JVM
只会加载1遍,保证只有1个单例
/*
* 单例模式 - 静态内部类(懒加载)
* 根据静态内部类的特性, 同时解决了`延时加载, 线程安全`问题, 并且代码更加简洁
*/
public class Singleton_05 {
private Singleton_05() {
}
// 创建静态内部类
private static class SingletonHandler {
// 在静态内部类中创建单例, 在装载内部类的时候, 才会创建单例对象
private static Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance() {
return SingletonHandler.instance;
}
}
1. 反射破坏单例
反射概念:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制
- 反射技术过于强大,它可以通过
setAccessible()
来修改构造器,字段,方法的可见性 - 单例模式的构造方法是私有的,如果将其可见性设为
public
,那么将无法控制对象的创建
@Test
public void singleton_05() throws Exception {
Class<Singleton_05> clazz = Singleton_05.class;
Constructor<Singleton_05> ctor = clazz.getDeclaredConstructor();
// 设置为true后, 就可以对类中的私有成员进行操作
ctor.setAccessible(true);
Singleton_05 instance1 = ctor.newInstance();
Singleton_05 instance2 = ctor.newInstance();
// Singleton_06 instance = ctor.newInstance();
System.out.println(instance1 == instance2);
}
- 解决方法之一:在单例类的构造方法中,添加判断
instance != null
时,直接抛出异常- 这种方式代码简洁性遭到破坏,设计不够优雅
/*
* 单例模式 - 静态内部类(懒加载)
* - JVM加载一个类的时候只加载一次,加载外部类时不会加载内部类,这样可以实现懒加载
* - 根据静态内部类的特性, 同时解决了`延时加载, 线程安全`问题, 并且代码更加简洁
*/
public class Singleton_05 {
private Singleton_05() {
if (SingletonHandler.instance != null) {
throw new RuntimeException("不允许非法访问");
}
}
// 创建静态内部类
private static class SingletonHandler {
// 在静态内部类中创建单例, 在装载内部类的时候, 才会创建单例对象
private static final Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance() {
return SingletonHandler.instance;
}
}
2. 序列化破坏单例
Singleton
中定义readResolve()
,并在该方法中指定要返回的对象的生成策略,可以防止单例被破坏
@Test
public void singleton() throws Exception {
// 序列化对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
oos.writeObject(Singleton.getInstance());
// 序列化对象输入流
File file = new File("tempFile.obj");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton singleton = (Singleton) ois.readObject();
System.out.println(singleton);
System.out.println(Singleton.getInstance());
System.out.println(Singleton.getInstance() == singleton); // false
// Singleton singleton = Singleton.getInstance();
// Object data = singleton.getData();
}
/**
* 单例类实现序列化接口
*/
class Singleton implements Serializable {
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;
}
/*
* 只需要在单例类中定义`readResolve()`, 就可以解决序列化对于单例的破坏
* 程序会判断是否有`readResolve()`, 如果有就执行该方法, 如果不存在,就会创建一个新的对象
*/
private Object readResolve() {
return singleton;
}
}
- 通过对
Singleton
的序列化与反序列化得到的对象是一个新的对象,破坏了Singleton
的单例性 - 问题是出在
ObjectInputputStream
的readObject()
上
ObjectInputStream#readObject()
public class ObjectInputStream
extends InputStream implements ObjectInput, ObjectStreamConstants
{
public final Object readObject()
throws IOException, ClassNotFoundException {
// 1..
return readObject(Object.class);
}
private final Object readObject(Class<?> type)
throws IOException, ClassNotFoundException
{
// ...
// 1.. 最终会返回一个object对象,其实就是序列化对象
Object obj = readObject0(type, false);
// ...
}
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
try {
switch (tc) {
// ...
case TC_OBJECT: // 匹配如果是对象
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
// 1..
return checkResolve(readOrdinaryObject(unshared));
// ...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
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 {
// 1.
// 通过反射创建的obj对象,可以暂时理解为是`ObjectInputStream#readObject()`返回的对象
// - isInstantiable():一个serializable的类可以在运行时被实例化,那么该方法就返回true
// - desc.newInstance():通过反射调用无参构造新建一个对象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
// ...
// 2. `hasReadResolveMethod()`:实现了serializable接口的类中包含`readResolve()`,则返回true
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
// 3. `invokeReadResolve()`:反射调用要被反序列化的类的`readResolve()`
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;
}
}
7. 枚举(推荐)
- 枚举单例方式是《Effective Java》作者推荐的使用方式。满足单例模式所需的 创建单例、线程安全、实现简洁、阻止反射、序列化对单例的破坏
- 使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例
- 默认枚举实例的创建是线程安全的,即使反序列化也不会生成新的实例
- 任何情况下都是一个单例(暴力反射对枚举方式无效)
/*
* 单例模式-枚举
* - 阻止反射的破坏: 在反射方法中不允许使用反射创建枚举对象(没有构造方法。拿到其.class文件,也没办法构造对象)
* - 阻止序列化的破坏: 在序列化时, 仅仅是将枚举对象的name属性输出到了结果中, 反序列化的时候, 就会通过
* Enum的`valueOf()`来根据名字去查找对应枚举对象
*/
@Getter
public enum Singleton_06 {
INSTANCE;
@Setter
private Object data;
public static Singleton_06 getInstance() {
return INSTANCE;
}
}
1. 阻止反射
- 首先枚举类中是没有空参构造方法的,只有一个带两个参数的构造方法
- 真正原因是:反射方法中不允许使用反射创建枚举对象
异常: 不能使用反射方式创建enum对象
java.lang.NoSuchMethodException: com.listao.dp.singleton.Singleton_06.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.listao.dp.singleton.Test_Reflect.singleton_06(Test_Reflect.java:34)
public final class Constructor<T> extends Executable {
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
}
@Test
public void singleton_06() throws Exception {
Class<Singleton_06> clazz = Singleton_06.class;
Constructor<Singleton_06> constructor = clazz.getDeclaredConstructor();
// 设置为true后, 就可以对类中的私有成员进行操作
constructor.setAccessible(true);
Singleton_06 instance = constructor.newInstance();
System.out.println(instance);
}
2. 阻止序列化
- Java规范规定,每个枚举类型及其定义的枚举变量在JVM中是唯一的
- 枚举类型序列化、反序列化上,Java做了特殊的规定。在序列化时,Java仅仅是将枚举对象的name属性输到结果中,反序列化时则是通过
java.lang.Enum
的valueOf()
方法来根据名字查找枚举对象- eg:序列化时只将
INSTANCE
这个名称输出,反序列化时再通过这个名称,查找对应的枚举类型,因此反序列化前后的实例相同
- eg:序列化时只将
public enum Singleton_06 {
INSTANCE;
}
@Test
public void enum_() throws Exception {
// 序列化对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
oos.writeObject(Singleton_06.getInstance());
// 序列化对象输入流
File file = new File("tempFile.obj");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton_06 singleton = (Singleton_06) ois.readObject();
System.out.println(singleton);
System.out.println(Singleton.getInstance());
System.out.println(Singleton_06.getInstance() == singleton); // true
}
2. 工厂方法模式
- 工厂模式(Factory Pattern)是Java中最常用的设计模式之一。提供了一种创建对象的最佳方式
- 在工厂模式中,创建对象时不会对客户端暴露创建逻辑,并且通过使用一个共同的接口来指向新创建的对象
- 《设计模式》一书中,工厂模式被分为三种:(不过,书中作者将简单工厂模式看作是工厂方法模式的一种特例)
- 简单工厂模式(不属于GOF的23种经典设计模式)
- 工厂方法模式
- 抽象工厂模式
1. 模拟发放奖品
- 需求:模拟一下互联网电商中促销拉新下的业务场景,新用户注册立即参与抽奖活动,奖品的种类有:打折券,免费优酷会员,小礼品
2. 常规写法
- 不考虑设计原则,不使用设计模式的方式进行开发
- 在不考虑任何代码的可扩展性的前提下,只为了尽快满足需求
1. 实体类
/**
* 获奖信息实体类
*/
@Data
public class AwardInfo {
// 用户id
private String uid;
// 奖品类型: 1-打折券, 2-优酷会员, 3-小礼品
private Integer awardTypes;
// 奖品编号
private String awardNumber;
// 额外信息
private Map<String, String> extMap;
}
/**
* 打折券信息实体类
*/
public class DiscountInfo {
// 属性信息省略...
}
/**
* 优酷会员实体类
*/
public class YouKuMember {
// 属性信息省略...
}
/**
* 小礼品实体类
*/
@Data
public class SmallGiftInfo {
private String userName;
private String userPhone;
private String orderId;
private String address;
}
2. 服务层
/**
* 模拟打折券服务
*/
public class DiscountService {
public Result<Object> sendDiscount(String uid, String awardNumber) {
System.out.println("向用户发送一张打折券: " + uid + ", " + awardNumber);
return new Result<>(200, "发放打折券成功!");
}
}
/**
* 模拟赠送优酷会员服务
*/
public class YouKuMemberService {
public void openMember(String bindMobile, String awardNumber) {
System.out.println("发放优酷会员: " + bindMobile + ", " + awardNumber);
}
}
/**
* 模拟礼品服务
*/
public class SmallGiftService {
public Boolean giveSmallGift(SmallGiftInfo smallGiftInfo) {
System.out.println("小礼品已发送,获奖用户注意查收! " + JSON.toJSON(smallGiftInfo));
return true;
}
}
3. 控制层
- 如果添加新的奖品,势必要改动
DeliverCtl1
,违反开闭原则。抽奖接口出现问题,重构成本会非常高 - 有一组
if-else
分支判断逻辑,如果经历几次迭代、拓展,后续if-else
还会增加,这段代码的研发将会十分痛苦
/**
* 发放奖品接口
*/
public class DeliverCtl1 {
/*
* 按照类型的不同发放奖品
* 奖品类型: 1-打折券, 2-优酷会员, 3-小礼品, 4-优惠券
*/
public void awardToUser(AwardInfo awardInfo) {
// 打折券
if (awardInfo.getAwardTypes() == 1) {
DiscountService discountService = new DiscountService();
DiscountResult discountResult = discountService.sendDiscount(awardInfo.getUid(), awardInfo.getAwardNumber());
System.out.println("打折券发放成功!" + discountResult);
}
// 优酷会员
else if (awardInfo.getAwardTypes() == 2) {
String phone = awardInfo.getExtMap().get("phone");
YouKuMemberService youKuMemberService = new YouKuMemberService();
youKuMemberService.openMember(phone, awardInfo.getAwardNumber());
System.out.println("优酷会员发放成功!");
}
// 小礼品
else if (awardInfo.getAwardTypes() == 3) {
// 封装收获人信息
SmallGiftInfo info = new SmallGiftInfo();
info.setUserPhone(awardInfo.getExtMap().get("phone"));
info.setUserName(awardInfo.getExtMap().get("username"));
info.setAddress(awardInfo.getExtMap().get("address"));
info.setOrderId(UUID.randomUUID().toString());
SmallGiftService smallGiftService = new SmallGiftService();
Boolean aBoolean = smallGiftService.giveSmallGift(info);
if (aBoolean) {
System.out.println("小礼品发放成功!");
}
}
}
}
4. 测试
@Test
public void test01() {
// 1. 发放打折券优惠
AwardInfo info = new AwardInfo();
info.setUid("1001");
info.setAwardTypes(1);
info.setAwardNumber("DEL12345");
deliverCtl1.awardToUser(info);
}
@Test
public void test02() {
// 2. 发放优酷会员
AwardInfo info = new AwardInfo();
info.setUid("1002");
info.setAwardTypes(2);
info.setAwardNumber("DW12345");
Map<String, String> map = new HashMap<>();
map.put("phone", "13512341234");
info.setExtMap(map);
deliverCtl1.awardToUser(info);
}
@Test
public void test03() {
// 3. 发放小礼品
AwardInfo info = new AwardInfo();
info.setUid("1003");
info.setAwardTypes(3);
info.setAwardNumber("SM12345");
Map<String, String> map = new HashMap<>();
map.put("username", "大远");
map.put("phone", "13512341234");
map.put("address", "北京天安门");
info.setExtMap(map);
deliverCtl1.awardToUser(info);
}
3. 简单工厂模式
- 简单工厂(静态工厂方法)模式(Static Factory Method Pattern),通过使用静态方法接收不同的参数来返回不同的实例对象
1. 介绍
- 简单工厂不是一种设计模式,反而比较像是一种编程习惯
2. 原理
- 抽象产品:定义了产品的规范,描述了产品的主要特性、功能
- 具体产品:实现、继承抽象产品的子类
- 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
3. 重构
/**
* 免费商品发放接口
*/
public interface IFreeGoods {
Result<Object> sendFreeGoods(AwardInfo awardInfo);
}
/**
* 模拟打折券服务
*/
public class DiscountFreeGoods implements IFreeGoods {
@Override
public Result<Object> sendFreeGoods(AwardInfo awardInfo) {
System.out.println("向用户发放一张打折券: " + awardInfo.getUid() + " , " + awardInfo.getAwardNumber());
return Result.custom(200, "打折券发放成功!");
}
}
/**
* 小礼品发放服务
*/
public class SmallGiftFreeGoods implements IFreeGoods {
@Override
public Result<Object> sendFreeGoods(AwardInfo awardInfo) {
SmallGiftInfo smallGiftInfo = new SmallGiftInfo();
smallGiftInfo.setUserPhone(awardInfo.getExtMap().get("phone"));
smallGiftInfo.setUserName(awardInfo.getExtMap().get("username"));
smallGiftInfo.setAddress(awardInfo.getExtMap().get("address"));
smallGiftInfo.setOrderId(UUID.randomUUID().toString());
System.out.println("小礼品发放成,请注意查收: " + JSON.toJSON(smallGiftInfo));
return Result.custom(200, "小礼品发送成功", smallGiftInfo);
}
}
/**
* 优酷 会员服务
*/
public class YouKuMemberFreeGoods implements IFreeGoods {
@Override
public Result<Object> sendFreeGoods(AwardInfo awardInfo) {
String phone = awardInfo.getExtMap().get("phone");
System.out.println("发放优酷会员成功,绑定手机号: " + phone);
return Result.custom(200, "优酷会员发放成功!");
}
}
/**
* 具体工厂: 生成免费商品
*/
public class FreeGoodsFactory {
public static IFreeGoods getInstance(Integer awardType) {
IFreeGoods iFreeGoods = null;
// 打折券
if (awardType == 1) {
iFreeGoods = new DiscountFreeGoods();
}
// 优酷会员
else if (awardType == 2) {
iFreeGoods = new YouKuMemberFreeGoods();
}
// 小礼品
else if (awardType == 3) {
iFreeGoods = new SmallGiftFreeGoods();
}
return iFreeGoods;
}
}
/**
* 发放奖品接口
*/
public class DeliverCtl2 {
// 发放奖品
public Result<Object> awardToUser(AwardInfo awardInfo) {
try {
IFreeGoods freeGoods = FreeGoodsFactory.getInstance(awardInfo.getAwardTypes());
return freeGoods.sendFreeGoods(awardInfo);
} catch (Exception e) {
e.printStackTrace();
return Result.custom(200, "奖品发放失败!");
}
}
}
@Test
public void test04() {
// 1. 发放打折券优惠
AwardInfo info = new AwardInfo();
info.setUid("1001");
info.setAwardTypes(1);
info.setAwardNumber("DEL12345");
Result<Object> result = deliverCtl2.awardToUser(info);
System.out.println(result);
}
@Test
public void test05() {
// 2. 发放优酷会员
AwardInfo info = new AwardInfo();
info.setUid("1002");
info.setAwardTypes(2);
info.setAwardNumber("DW12345");
Map<String, String> map = new HashMap<>();
map.put("phone", "13512341234");
info.setExtMap(map);
Result<Object> result = deliverCtl2.awardToUser(info);
System.out.println(result);
}
@Test
public void test06() {
// 3. 发放小礼品
AwardInfo info = new AwardInfo();
info.setUid("1003");
info.setAwardTypes(3);
info.setAwardNumber("SM12345");
Map<String, String> map = new HashMap<>();
map.put("username", "大远");
map.put("phone", "13512341234");
map.put("address", "北京天安门");
info.setExtMap(map);
Result<Object> result = deliverCtl2.awardToUser(info);
System.out.println(result);
}
4. 总结
1. 优点
- 封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码
- 要实现新产品,直接修改工厂类,而不需要修改原代码,降低了客户代码修改的可能性,更加容易扩展
2. 缺点
- 增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”
3. 适用场景
- 需要创建的对象较少
- 客户端不关心对象的创建过程
4. 工厂方法模式
- 工厂方法模式(Factory Method Pattern)定义:一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类
1. 介绍
- 工厂方法模式目的:封装对象创建的过程,提升创建对象方法的可复用性
2. 原理
- 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品
- 具体工厂:完成具体产品的创建
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能
- 具体产品:由具体工厂来创建,它同具体工厂之间一一对应
3. 重构
- 提高代码扩展性,将简单工厂中的if分支去掉,通过增加抽象工厂(生产工厂的工厂),让具体工厂去进行生产具体产品对象
/**
* 抽象工厂
*/
public interface IFreeGoodsFactory {
IFreeGoods getInstance();
}
/**
* 具体工厂 - 生产优惠券发放接口
*/
public class DiscountFreeGoodsFactory implements IFreeGoodsFactory {
@Override
public IFreeGoods getInstance() {
// 返回的是具体产品
return new DiscountFreeGoods();
}
}
/**
* 具体工厂 - 生产小礼品发放接口
*/
public class SmallGiftFreeGoodsFactory implements IFreeGoodsFactory {
@Override
public IFreeGoods getInstance() {
return new SmallGiftFreeGoods();
}
}
/**
* 工厂的工厂, 用来创建工厂类对象
*/
public class FreeGoodsFactoryMap {
// 创建map集合,保存工厂对象
private static final Map<Integer, IFreeGoodsFactory> cachedFactories = new HashMap<>();
static {
cachedFactories.put(1, new DiscountFreeGoodsFactory());
cachedFactories.put(2, new SmallGiftFreeGoodsFactory());
}
public static IFreeGoodsFactory getParserFactory(Integer type) {
return cachedFactories.get(type);
}
}
/**
* 发放奖品接口
*/
public class DeliverCtl3 {
// 发放奖品
public Result<Object> awardToUser(AwardInfo awardInfo) {
// 根据类型获取具体工厂
IFreeGoodsFactory goodsFactory = FreeGoodsFactoryMap.getParserFactory(awardInfo.getAwardTypes());
// 从工厂类中获取对应实例
IFreeGoods iFreeGoods = goodsFactory.getInstance();
System.out.println("==========工厂方法模式=============");
return iFreeGoods.sendFreeGoods(awardInfo);
}
}
基本符了开闭原则,新增产品时:
- 创建新的产品类,并且让该产品实现抽象产品接口
- 创建产品类对应的具体工厂,并让具体工厂实现抽象工厂
- 将新的具体工厂对象,添加到
FreeGoodsFactoryMap.cachedFactories
中即可,代码改动非常少
4. 总结
1. 优点
- 用户只需要知道具体工厂的名称就可得到所要产品,无须知道产品的具体创建过程
- 在系统增加新的产品时,只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则
2. 缺点
- 每增加一个产品就要增加一个具体产品类、一个对应的具体工厂类,这增加了系统的复杂度
3. 使用场景
- 需要使用很多重复代码创建对象时
- DAO层的数据对象、API层的VO对象等
- 创建对象要访问外部信息或资源时
- 读取DB字段,获取访问授权token信息,配置文件等
- 创建需要统一管理生命周期的对象时
- 会话信息、用户网页浏览轨迹对象等
- 创建池化对象时
- 连接池对象、线程池对象、日志对象等。这些对象的特性是:有限、可重用,使用工厂方法模式可以有效节约资源
- 希望隐藏对象的真实类型时
- 不希望使用者知道对象的真实构造函数参数等
3. 抽象工厂模式
- 抽象工厂模式(Abstract Factory Pattern)原始定义:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类
1. 介绍
- 抽象工厂模式比工厂方法模式的抽象程度更高。为创建一组对象提供了解决方案
- 工厂方法模式:每一个具体工厂只需要生产一种具体产品
- 抽象工厂模式:一个具体工厂可以生产一组相关的具体产品(产品族)。产品族中的每一个产品都分属于某一个产品继承等级结构
1. 产品等级结构
- 产品等级结构:即产品的继承结构
- eg:一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构。抽象电视机是父类,而具体品牌的电视机是其子类
2. 产品族
- 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品
- eg:海尔电器工厂,生产的电视机、电冰箱。海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中
- 上图中,每一个具体工厂可以生产属于一个产品族的所有产品。eg:海尔工厂生产电视机、空调、冰箱,所生产的产品又位于不同的产品等级结构中
- 使用工厂方法模式,上图所示的结构需要提供9个具体工厂;使用抽象工厂模式,只需要提供3个具体工厂,极大减少了系统中类的个数
2. 原理
- 在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法,用于产生多种不同类型的产品。这些产品构成了一个产品族
- 抽象工厂(Abstract Factory):声明了一种用于创建一族产品的方法,每一个方法对应一种产品
- 具体工厂(Concrete Factory):完成具体产品的创建
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性、功能。抽象工厂模式有多个抽象产品
- 具体产品(ConcreteProduct):由具体工厂来创建,它同具体工厂之间是多对一的关系
3. 实现
/**
* 抽象工厂: 在一个抽象工厂中可以声明多个工厂方法, 用于创建不同类型的产品
*/
public interface IFactory {
ITV createTV();
IFreezer createFreezer();
}
- 具体工厂:每一个具体工厂方法,可以返回一个特定的产品对象,而同一个具体工厂所创建的产品对象构成了一个产品族
/**
* 具体工厂
*/
public class HairFactory implements IFactory {
@Override
public ITV createTV() {
return new HairTV();
}
@Override
public IFreezer createFreezer() {
return new HairFreezer();
}
}
public class HisenseFactory implements IFactory {
@Override
public ITV createTV() {
return new HisenseTV();
}
@Override
public IFreezer createFreezer() {
return new HisenseFreezer();
}
}
/**
* 抽象产品: 定义产品规范, 描述了产品的主要的特征和功能
*/
public abstract class AbsTV {
}
public class HairTV extends AbsTV {
}
public class HisenseTV extends AbsTV {
}
/**
* 抽象产品
*/
public abstract class AbsFreezer {
}
public class HairFreezer extends AbsFreezer {
}
public class HisenseFreezer extends AbsFreezer {
}
private AbsTV tv;
private AbsFreezer freezer;
public T_factory(IFactory factory) {
// 在客户端看来就是使用抽象工厂来生产家电
this.tv = factory.createTV();
this.freezer = factory.createFreezer();
}
@Test
public void test08() {
T_factory tFactory = new T_factory(new HisenseFactory());
AbsTV tv = tFactory.getTv();
System.out.println(tv);
AbsFreezer freezer = tFactory.getFreezer();
System.out.println(freezer);
}
4. 总结
- 抽象工厂模式向使用方(客户)隐藏了下列变化:
- 程序所支持的实例集合(具体工厂)的数目
- 当前是使用的实例集合中的哪一个实例
- 在任意给定时刻被实例化的具体类型
- 在理解抽象工厂模式原理时,你一定要牢牢记住 “如何找到某一个类产品的正确共性功能” 这个重点
1. 优点
- 对于不同产品系列有比较多共性特征时。有助于提升组件的复用性(JDBC)
- 当需要提升代码的扩展性并降低维护成本时,把对象的创建、使用过程分开,有效地将代码统一到一个级别上
- 解决跨平台带来的兼容性问题
2. 缺点
- 增加新的产品等级结构麻烦,需要对原有结构进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大不变,违背了开闭原则
4. 建造者模式
- 建造者(生成器)模式(Builder Pattern)定义: 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示
1. 介绍
- 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节
一辆汽车是由多个部件组成的,包括了车轮、方向盘、发动机等等。对于大多数用户而言,并不需要知道这些部件的装配细节,并且几乎不会使用单独某个部件,而是使用一辆完整的汽车。而建造者模式就是负责将这些部件进行组装让后将完整的汽车返回给用户
2. 原理
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建
- 具体建造者类(ConcreteBuilder):完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供一个方法,返回创建好的负责产品对象
- 产品类(Product):要创建的复杂对象 (包含多个组成部件)
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分。指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建(客户端一般只需要与指挥者进行交互)
3. 实现1
生产自行车是一个复杂的过程,包含车架,车座等组件。而车架又有碳纤维,铝合金等材质,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式
/**
* 自行车类
*/
@Data
public class Bike {
// 车架
private String frame;
// 车座
private String seat;
}
/**
* 抽象建造者类
*/
public abstract class AbsBuilder {
// 只给子类用
protected Bike bike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
/**
* 哈罗单车建造者
*/
public class HelloBikeBuilder extends AbsBuilder {
@Override
public void buildFrame() {
System.out.println("制作碳纤维车架");
bike.setFrame("碳纤维车架");
}
@Override
public void buildSeat() {
System.out.println("制作橡胶车座");
bike.setFrame("橡胶车座");
}
@Override
public Bike createBike() {
return bike;
}
}
/**
* 摩拜单车建造者
*/
public class MobikeBuilder extends AbsBuilder {
@Override
public void buildFrame() {
System.out.println("制作车架!");
bike.setFrame("铝合金车架");
}
@Override
public void buildSeat() {
System.out.println("制作车座");
bike.setSeat("真皮车座");
}
@Override
public Bike createBike() {
return bike;
}
}
/**
* 指挥者类
*/
@AllArgsConstructor
public class Director {
private final AbsBuilder builder;
// 自行车制作方法
public Bike construct() {
builder.buildFrame();
builder.buildSeat();
return builder.createBike();
}
}
/**
* 客户端
*/
public class T_builder {
@Test
public void test1() {
// 1.创建指挥者
Director director = new Director(new MobikeBuilder());
// 2.获取自行车
Bike bike = director.construct();
System.out.println(bike.getFrame() + "," + bike.getSeat());
}
}
4. 实现2
1. 有参构造方法
- 构造方法参数过多,代码可读性、易用性变差。使用构造函数时,很容易搞错参数的顺序,导致很有隐蔽的BUG出现
/**
* MQ连接客户端
*/
@Getter
public class RabbitMQClient1 {
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 1000;
// 构造方法参数过多,代码的可读性、易用性太差,在使用构造函数时,很容易搞错顺序,传递错误的参数值,导致很有隐蔽的BUG
public RabbitMQClient1(String host, int port, int mode, String exchange, String queue, boolean isDurable,
int connectionTimeout) {
this.host = host;
this.port = port;
this.mode = mode;
this.exchange = exchange;
this.queue = queue;
this.isDurable = isDurable;
this.connectionTimeout = connectionTimeout;
// 工作队列模式不需要设计交换机, 但是队列名称一定要有
if (mode == 1) {
if (exchange != null) {
throw new RuntimeException("工作队列模式无需设计交换机");
}
if (queue == null || queue.trim().isEmpty()) {
throw new RuntimeException("工作队列模式名称不能为空");
}
if (!isDurable) {
throw new RuntimeException("工作队列模式必须开启持久化");
}
}
// 路由模式必须设计交换机, 但是不能设计队列
else if (mode == 2) {
if (exchange == null) {
throw new RuntimeException("路由模式下必须设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式无须设计队列名称");
}
}
// 其他验证方式
}
public void sendMessage(String msg) {
System.out.println("发送消息......");
}
}
@Test
public void test2() {
// 每一种模式, 都需要根据不同的情况进行实例化, 构造方法会变得过于复杂
RabbitMQClient1 client = new RabbitMQClient1("192.168.52.123", 5672,
2, "sample-exchange", null, true, 5000);
client.sendMessage("Test-MSG");
}
2. setter()
setter()
设置对象属性时,存在中间状态。并且属性校验有前后顺序约束时,逻辑校验代码找不到合适的地方放置
Rectangle r = new Rectangle(); // 对象处于无效状态
r.setWidth(2); // 对象处于无效状态
r.setHeight(3); // 有效状态
- 破坏了"不可变对象"的密闭性
不可变对象:对象创建好了,就不能再修改内部的属性值,下面的client类就是典型的不可变对象,创建好的连接对象不能再改动
/**
* MQ连接客户端
*/
@Getter
@Setter
public class RabbitMQClient2 {
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 1000;
// 私有化构造方法
public RabbitMQClient2() {
}
public void setExchange(String exchange) {
// 工作队列模式不需要设计交换机, 但是队列名称一定要有
if (mode == 1) {
if (exchange != null) {
throw new RuntimeException("工作队列模式无需设计交换机");
}
if (queue == null || queue.trim().isEmpty()) {
throw new RuntimeException("工作队列模式名称不能为空");
}
if (!isDurable) {
throw new RuntimeException("工作队列模式必须开启持久化");
}
}
// 路由模式必须设计交换机, 但是不能设计队列
else if (mode == 2) {
if (exchange == null) {
throw new RuntimeException("路由模式下必须设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式无须设计队列名称");
}
}
// 其他验证方式
this.exchange = exchange;
}
public void setMode(int mode) {
// 工作队列模式不需要设计交换机, 但是队列名称一定要有
if (mode == 1) {
if (exchange != null) {
throw new RuntimeException("工作队列模式无需设计交换机");
}
if (queue == null || queue.trim().isEmpty()) {
throw new RuntimeException("工作队列模式名称不能为空");
}
if (!isDurable) {
throw new RuntimeException("工作队列模式必须开启持久化");
}
}
// 路由模式必须设计交换机,但是不能设计队列
else if (mode == 2) {
if (exchange == null) {
throw new RuntimeException("路由模式下必须设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式无须设计队列名称");
}
}
this.mode = mode;
}
public void sendMessage(String msg) {
System.out.println("发送消息......");
}
}
/*
* setter()好处是参数的设计更加的灵活, 但通过set方式设置对象属性时
* - 对象有可能存在中间状态(无效状态)
* - 并且进行属性校验时有前后顺序约束
* - 破坏了不可变对象的密封性
*/
@Test
public void test3() {
RabbitMQClient2 client = new RabbitMQClient2();
client.setHost("192.168.52.123");
client.setMode(1);
client.setQueue("queue");
client.setDurable(true);
client.sendMessage("Test-MSG2");
}
3. 建造者方式
/*
* 建造者模式
* 1. 目标类的构造方法要传入一个Builder对象
* 2. builder类位于目标类的内部, 并且使用static修饰
* 3. builder类对象提供内置各种setter(), 注意: setter()返回值是builder本身
* 4. builder类提供一个build(), 实现目标对象的创建
*/
public class RabbitMQClient3 {
// 私有构造, 目标类的构造方法要传入一个Builder对象
private RabbitMQClient3(Builder builder) {
}
@Getter
// builder类位于目标类的内部, 并且使用static修饰
public static class Builder {
// 保证不可变对象的属性密闭性
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 1000;
// builder类对象提供内置各种set方法,注意: set方法的返回值是builder本身
public Builder setHost(String host) {
this.host = host;
return this;
}
public Builder setPort(int port) {
this.port = port;
return this;
}
public Builder setMode(int mode) {
this.mode = mode;
return this;
}
public Builder setExchange(String exchange) {
this.exchange = exchange;
return this;
}
public Builder setQueue(String queue) {
this.queue = queue;
return this;
}
public Builder setDurable(boolean durable) {
isDurable = durable;
return this;
}
public Builder setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
return this;
}
// builder类提供一个build(), 实现目标对象的创建
public RabbitMQClient3 build() {
// 工作队列模式不需要设计交换机, 但是队列名称一定要有
if (mode == 1) {
if (exchange != null) {
throw new RuntimeException("工作队列模式无需设计交换机");
}
if (queue == null || queue.trim().isEmpty()) {
throw new RuntimeException("工作队列模式名称不能为空");
}
if (!isDurable) {
throw new RuntimeException("工作队列模式必须开启持久化");
}
}
// 路由模式必须设计交换机, 但是不能设计队列
else if (mode == 2) {
if (exchange == null) {
throw new RuntimeException("路由模式下必须设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式无须设计队列名称");
}
}
return new RabbitMQClient3(this);
}
}
public void sendMessage(String msg) {
System.out.println("发送消息......");
}
}
@Test
public void test4() {
// 使用链式编程设置参数
RabbitMQClient3 client = new RabbitMQClient3.Builder()
.setHost("192.168.52.123")
.setMode(2)
.setExchange("text-exchange")
.setPort(5672)
.setDurable(true)
.build();
client.sendMessage("Test");
}
@Data
public class RabbitMQClient4 {
// 保证不可变对象的属性密闭性
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 1000;
// 私有构造, 目标类的构造方法要传入一个Builder对象
public RabbitMQClient4() {
}
// builder类位于目标类的内部,并且使用static修饰
public static class Builder {
RabbitMQClient4 ooxx = new RabbitMQClient4();
// builder类对象提供内置各种set方法,注意: set方法的返回值是builder本身
public Builder setHost(String host) {
ooxx.host = host;
return this;
}
public Builder setPort(int port) {
ooxx.port = port;
return this;
}
public Builder setMode(int mode) {
ooxx.mode = mode;
return this;
}
public Builder setExchange(String exchange) {
ooxx.exchange = exchange;
return this;
}
public Builder setQueue(String queue) {
ooxx.queue = queue;
return this;
}
public Builder setDurable(boolean durable) {
ooxx.isDurable = durable;
return this;
}
public Builder setConnectionTimeout(int connectionTimeout) {
ooxx.connectionTimeout = connectionTimeout;
return this;
}
// builder类提供一个build(), 实现目标对象的创建
public RabbitMQClient4 build() {
if (ooxx.mode == 1) {
if (ooxx.exchange != null) {
throw new RuntimeException("工作队列模式无需设计交换机");
}
if (ooxx.queue == null || ooxx.queue.trim().isEmpty()) {
throw new RuntimeException("工作队列模式名称不能为空");
}
if (!ooxx.isDurable) {
throw new RuntimeException("工作队列模式必须开启持久化");
}
} else if (ooxx.mode == 2) {
if (ooxx.exchange == null) {
throw new RuntimeException("路由模式下必须设置交换机");
}
if (ooxx.queue != null) {
throw new RuntimeException("路由模式无须设计队列名称");
}
}
return ooxx;
}
}
public void sendMessage(String msg) {
System.out.println("发送消息......");
}
}
@Test
public void test5() {
// 获取连接对象
RabbitMQClient4 instance = new RabbitMQClient4.Builder()
.setHost("192.168.52.123")
.setMode(1)
.setPort(5672)
.setQueue("test")
.build();
instance.sendMessage("test");
}
1. PersonBuilder
@ToString
public class Person {
private int id;
private String name;
private int age;
private double weight;
private int score;
private Location loc;
private Person() {
}
public static PersonBuilder builder() {
return new PersonBuilder();
}
// 静态工厂
public static class PersonBuilder {
Person p = new Person();
public PersonBuilder basicInfo(int id, String name, int age) {
p.id = id;
p.name = name;
p.age = age;
return this;
}
public PersonBuilder weight(double weight) {
p.weight = weight;
return this;
}
public PersonBuilder score(int score) {
p.score = score;
return this;
}
public PersonBuilder loc(String street, String roomNo) {
p.loc = new Location(street, roomNo);
return this;
}
public Person build() {
return p;
}
}
public static void main(String[] args) {
Person p = new PersonBuilder()
.basicInfo(1, "zhangsan", 18)
.score(20)
.weight(200)
.loc("bj", "23")
.build();
System.out.println(p);
}
}
@Builder
@ToString
class Location {
private final String street;
private final String roomNo;
public static void main(String[] args) {
// lombok
Location build = Location.builder()
.street("北京,昌平")
.roomNo("101")
.build();
System.out.println(build);
}
}
2. ComplexTerrainBuilder
/**
* 复杂对象
*/
public class ComplexTerrainBuilder implements TerrainBuilder {
Terrain terrain = new Terrain();
@Override
public TerrainBuilder buildWall() {
terrain.w = new Wall(10, 10, 50, 50);
return this;
}
@Override
public TerrainBuilder buildFort() {
terrain.f = new Fort(10, 10, 50, 50);
return this;
}
@Override
public TerrainBuilder buildMine() {
terrain.m = new Mine(10, 10, 50, 50);
return this;
}
@Override
public Terrain build() {
return terrain;
}
public static void main(String[] args) {
// Effective Java
TerrainBuilder builder = new ComplexTerrainBuilder();
Terrain t = builder.buildFort().buildMine().buildWall().build();
System.out.println(t);
}
}
interface TerrainBuilder {
TerrainBuilder buildWall();
TerrainBuilder buildFort();
TerrainBuilder buildMine();
Terrain build();
}
/**
* 地形
*/
@ToString
class Terrain {
Wall w;
Fort f;
Mine m;
}
/**
*
*/
@ToString
class Wall {
int x, y, w, h;
public Wall(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
/**
* 堡垒
*/
@ToString
class Fort {
int x, y, w, h;
public Fort(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
/**
* 地雷
*/
@ToString
class Mine {
int x, y, w, h;
public Mine(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
3. @Builder底层
@Data
// 默认《类名Builder》内部类
@Builder
@AllArgsConstructor
@NoArgsConstructor
class Menu implements TreeEntity<Menu> {
private Integer id;
private String name;
private Integer pId;
private String url;
private List<Menu> children;
@Override
public void addChild(Menu menu) {
if (children != null) {
children.add(menu);
} else {
children = CollUtil.newArrayList(menu);
}
}
// public static MenuBuilder builder() {
// return new MenuBuilder();
// }
public static class MenuBuilder {
// private Integer id;
// private String name;
// private Integer pId;
// private String url;
// private List<Menu> children;
// public MenuBuilder id(Integer id) {
// this.id = id;
// return this;
// }
// public Menu build() {
// return new Menu(this.id, this.name, this.pId, this.url, this.children);
// }
// 增加自定义方法
public MenuBuilder baseInfo(Integer id, String name, Integer pId) {
this.id = id;
this.name = name;
this.pId = pId;
return this;
}
}
}
5. 总结
1. 与工厂模式区别
- 工厂模式:用来创建不同,但相关类型的对象(继承同一父类、接口的一组子类),由给定的参数来决定创建哪种类型的对象
- 建造者模式:用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”创建不同的对象
- 顾客走进一家餐馆点餐,利用工厂模式,根据用户不同的选择,制作不同的食物。eg:披萨、汉堡、沙拉
- 对于单品披萨来说,用户又有各种配料可以定制。eg:奶酪、西红柿、起司,通过建造者模式根据用户选择的不同配料来制作披萨
2. 优点
- 封装性很好。可以有效的封装变化,一般产品类、建造者类是比较稳定的。因此,将主要业务逻辑封装在指挥者类中,对整体而言可以取得比较好的稳定性
- 客户端不必知道产品内部组成细节。将产品本身、创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 更加精细地控制产品的创建过程。将复杂创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
- 很容易进行扩展。有新需求,实现一个新建造者类就可以完成,基本不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则
3. 缺点
- 所创建的产品一般具有较多的共同点,其组成部分相似。如果产品之间的差异性很大,不适合使用,使用范围受到一定的限制
4. 应用场景
- 创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,通常在以下场合使用
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的
5. 原型模式
- 原型模式(Prototype Design Pattern)定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象
1. 介绍
- 如果创建对象的成本比较大,(eg:对象中的数据是经过复杂计算才能得到,或者需要从RPC接口或DB等比较慢的IO中获取)就可以使用原型模式,从其他已有的对象中进行拷贝,而不是每次都创建新对象,进行一些耗时的操作
西游记中的孙悟空,拔毛变小猴。孙悟空这种根据自己的形状复制出多个身外化身的技巧
2. 原理
- 抽象原型类(Prototype):声明克隆方法的接口、抽象类,是所有具体原型类的公共父类
- 具体原型类(ConcretePrototype):实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象
- 客户类(Client):在客户类中,让一个原型对象克隆自身从而创建一个新的对象.由于客户类针对抽象原型类Prototype编程.因此用户可以根据需要选择具体原型类,系统具有较好的扩展性,增加或者替换具体原型类都比较方便
3. 深克隆 & 浅克隆
- 复制原型对象的同时是否复制包含在原型对象中引用类型的成员变量,分为浅克隆(Shallow Clone)、深克隆(Deep Clone)
不推荐用Cloneable
接口,实现比较麻烦,现在借助Apache Commons
或SpringFramework
可以直接实现
- 浅克隆:
BeanUtils.cloneBean(Object obj);
BeanUtils.copyProperties(S,T);
BeanUtils
利用反射原理获得所有类可见的属性和方法,然后复制到target类
- 深克隆:
SerializationUtils.clone(T object);
SerializationUtils.clone()
序列化实现深克隆,克隆的类实现Serialization
接口
1. 浅克隆
- 克隆对象、原型对象共享引用数据类型变量
1. code
- Object类中提供了
clone()
实现浅克隆。注意:要实现克隆的Java类必须实现一个标识接口Cloneable
,表示这个Java类支持被复制 Cloneable
接口是抽象原型类,Cloneable
接口实现类是具体的原型类
/*
* 具体原型类
* 实现Cloneable标识接口, 表示当前类对象可复制
*/
@Getter
@Setter
@AllArgsConstructor
public class ConcretePrototype implements Cloneable, Serializable {
private Ooxx ooxx;
public void show() {
System.out.println("嫌疑人姓名: " + ooxx.getName());
}
public ConcretePrototype() {
System.out.println("具体原型对象创建成功!");
}
/*
* 1. implements Cloneable
* 2. 重写clone()方法
*
* Cloneable标记性接口,接口里没有任何方法。
* - 编译时不报错,运行的时候CloneNotSupportedException。所以这个接口还是要实现的
* - protected native Object clone() throws CloneNotSupportedException;
* - protected其他包中的子类
* - native C++写的,没有java原码
*/
@Override
public ConcretePrototype clone() throws CloneNotSupportedException {
System.out.println("克隆对象复制成功!");
return (ConcretePrototype) super.clone();
}
}
@Test
public void test01() throws CloneNotSupportedException {
ConcretePrototype c1 = new ConcretePrototype();
ConcretePrototype c2 = c1.clone();
System.out.println("对象c1和对象c2是同一个对象吗?" + (c1 == c2)); // false
}
ConcretePrototype
类添加引用类型private Ooxx ooxx;
@Test
public void test02() throws CloneNotSupportedException {
ConcretePrototype c1 = new ConcretePrototype();
Ooxx o1 = new Ooxx("峰哥");
c1.setOoxx(o1);
// 复制c1
ConcretePrototype c2 = c1.clone();
Ooxx o2 = c2.getOoxx();
o2.setName("凡哥");
c1.show();
c2.show();
System.out.println("对象o1和对象o2是同一个对象吗?" + (o1 == o2)); // true
}
- 说明:o1与o2是同一对象,这是浅克隆的效果,也就是对具体原型类中的引用数据类型属性,只进行了引用复制
2. BeanUtils
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
@Test
public void test04() throws Exception {
ConcretePrototype cp1 = new ConcretePrototype(new Ooxx("峰哥"));
ConcretePrototype cp2 = (ConcretePrototype) BeanUtils.cloneBean(cp1);
System.out.println("cp1 == cp2 = " + (cp1 == cp2)); // false
System.out.println("(cp1.getOoxx() == cp2.getOoxx()) = " + (cp1.getOoxx() == cp2.getOoxx())); // true
}
2. 深克隆
- 深复制把要复制的对象所引用的对象都复制了一遍
1. code
- 进行深拷贝,需要使用到对象序列化流(对象序列化之后,再进行反序列化获取到不同对象)
注意:
ConcretePrototype
和Person
类必须实现Serializable
接口,否则会抛NotSerializableException
异常
@Test
public void test03() throws Exception {
ConcretePrototype c1 = new ConcretePrototype();
Ooxx o1 = new Ooxx("峰哥");
c1.setOoxx(o1);
// 创建对象序列化输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
// 将c1对象写到文件
oos.writeObject(c1);
oos.close();
// 创建对象序列化输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
// 读取对象
ConcretePrototype c2 = (ConcretePrototype) ois.readObject();
Ooxx o2 = c2.getOoxx();
o2.setName("凡哥");
c1.show();
c2.show();
System.out.println("对象o1和对象o2是同一个对象吗?" + (o1 == o2)); // false
}
2. SerializationUtils
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
// 必须实现Serializable接口
@Test
public void test05() {
ConcretePrototype cp1 = new ConcretePrototype(new Ooxx("峰哥"));
ConcretePrototype cp2 = SerializationUtils.clone(cp1);
System.out.println("cp1 == cp2 = " + (cp1 == cp2)); // false
System.out.println("(cp1.getOoxx() == cp2.getOoxx()) = " + (cp1.getOoxx() == cp2.getOoxx())); // false
}
3. ObjectUtil
import cn.hutool.core.util.ObjectUtil;
// ObjectUtil.clone 克隆对象,如果对象实现Cloneable接口,调用其clone方法,如果实现Serializable接口,执行深度克隆,否则返回null
@Test
public void test06() {
// ConcretePrototype不能实现Cloneable接口
ConcretePrototype cp1 = new ConcretePrototype(new Ooxx("峰哥"));
ConcretePrototype cp2 = ObjectUtil.clone(cp1);
System.out.println("cp1 == cp2 = " + (cp1 == cp2)); // false
System.out.println("(cp1.getOoxx() == cp2.getOoxx()) = " + (cp1.getOoxx() == cp2.getOoxx())); // false
}
4. 应用实例
- 模拟某银行电子账单系统的广告信发送功能,广告信的发送都是有一个模板的,从数据库查出客户的信息,然后放到模板中生成一份完整的邮件,然后交给发送机进行发送处理
/**
* 广告模板
*/
@Data
public class AdvTemplate {
// 广告信名称
private String advSubject = "xx银行本月还款达标,可抽iPhone 13等好礼!";
// 广告信内容
private String advContext = "达标用户请在2022年3月1日到2022年3月30日参与抽奖...";
}
/**
* 邮件类
*/
@Data
public class Mail {
// 收件人
private String receiver;
// 邮件名称
private String subject;
// 称呼
private String appellation;
// 邮件内容
private String context;
// 邮件尾部 "xx版权所有"
private String tail;
public Mail(AdvTemplate advTemplate) {
this.subject = advTemplate.getAdvSubject();
this.context = advTemplate.getAdvContext();
}
}
// 发送邮件
public static void sendMail(Mail mail) {
System.out.println("标题: " + mail.getSubject() + "\t 收件人: " + mail.getReceiver()
+ "\t ...发送成功!");
}
@Test
public void test07() {
int i = 0;
while (i < 6) {
// 定义模板
Mail mail = new Mail(new AdvTemplate());
mail.setTail("xxx银行版权所有");
// 每封邮件不同的信息
mail.setAppellation(" 先生 (女士)");
Random random = new Random();
int num = random.nextInt(999999999);
mail.setReceiver(num + "@" + "listao.com");
// 发送邮件
sendMail(mail);
i++;
}
}
标题: xx银行本月还款达标,可抽iPhone 13等好礼! 收件人: 154428525@listao.com ...发送成功!
标题: xx银行本月还款达标,可抽iPhone 13等好礼! 收件人: 325301232@listao.com ...发送成功!
标题: xx银行本月还款达标,可抽iPhone 13等好礼! 收件人: 988794516@listao.com ...发送成功!
标题: xx银行本月还款达标,可抽iPhone 13等好礼! 收件人: 575594606@listao.com ...发送成功!
标题: xx银行本月还款达标,可抽iPhone 13等好礼! 收件人: 613611790@listao.com ...发送成功!
标题: xx银行本月还款达标,可抽iPhone 13等好礼! 收件人: 578588863@listao.com ...发送成功!
上面的代码存在的问题:
- 发送邮件需要重复创建
Mail
对象,而不同对象间差别非常小,这样重复的创建操作十分浪费资源 - 使用原型模式,从已有对象中进行拷贝,而不是每次都创建新对象,减少耗时操作
/**
* 邮件类
*/
@Data
public class Mail implements Cloneable {
// 收件人
private String receiver;
// 邮件名称
private String subject;
// 称呼
private String appellation;
// 邮件内容
private String context;
// 邮件尾部 "xx版权所有"
private String tail;
public Mail(AdvTemplate advTemplate) {
this.subject = advTemplate.getAdvSubject();
this.context = advTemplate.getAdvContext();
}
public Mail clone() {
try {
return (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
@Test
public void test08() {
int i = 0;
// 定义模板
Mail mail = new Mail(new AdvTemplate());
mail.setTail("xxx银行版权所有");
while (i < 6) {
// 每封邮件不同的信息
Mail cloneMail = mail.clone();
cloneMail.setAppellation(" 先生 (女士)");
Random random = new Random();
int num = random.nextInt(999999999);
cloneMail.setReceiver(num + "@" + "listao .com");
// 发送邮件
sendMail(cloneMail);
i++;
}
}
5. 总结
1. 优点
当创建新对象实例较为复杂时,使用原型模式可以简化对象的创建过程
- eg:在AI系统中,经常需要频繁使用大量不同分类的数据模型文件,在对这一类文件建立对象模型时,不仅会长时间占用IO读写资源,还会消耗大量CPU运算资源,如果频繁创建模型对象,就会很容易造成服务器CPU被打满而导致系统宕机
- 通过原型模式可以很容易地解决这个问题,当完成对象的第一次初始化后,新创建的对象便使用对象拷贝(在内存中进行二进制流的拷贝),虽然拷贝也会消耗一定资源,但相比初始化的外部读写和运算来说,内存拷贝消耗会小很多,而且速度快很多
原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,比如恢复到某一历史状态,可以辅助实现撤销操作
在某些需要保存历史状态的场景中,比如,聊天消息、上线发布流程、需要撤销操作的程序等,原型模式能快速地复制现有对象的状态并留存副本,方便快速地回滚到上一次保存或最初的状态,避免因网络延迟、误操作等原因而造成数据的不可恢复。
2. 缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码,违背了开闭原则
3. 使用场景
- 资源优化场景。当进行对象初始化需要使用很多外部资源时。eg:IO资源、数据文件、CPU、网络和内存等
- 复杂的依赖场景。eg:F对象的创建依赖A,A又依赖B,B又依赖C... 创建过程是一连串对象的
get(), set()
- 性能和安全要求的场景。eg:同一个用户在一个会话周期里,可能会反复登录平台或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权。每次都通过
new
产生一个对象会非常烦琐 - 同一个对象可能被多个修改者使用的场景。eg:一个商品对象需要提供给物流、会员、订单等多个服务访问,且各调用者可能都需要修改其值时
- 需要保存原始对象状态的场景。eg:记录历史操作的场景中,通过原型模式快速保存记录