设计模式之美学习-创建型-建造者模式(十七)
场景一构建复杂参数的对象
需求
我们需要定义一个资源池配置类 ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。现在,请你编写代码实现这个 ResourcePoolConfig 类。
传统实现一
实现
public class ResourcePoolConfig { private static final int DEFAULT_MAX_TOTAL = 8; private static final int DEFAULT_MAX_IDLE = 8; private static final int DEFAULT_MIN_IDLE = 0; private String name; private int maxTotal = DEFAULT_MAX_TOTAL; private int maxIdle = DEFAULT_MAX_IDLE; private int minIdle = DEFAULT_MIN_IDLE; public ResourcePoolConfig(String name, Integer maxTotal, Integer maxIdle, Integer minIdle) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("name should not be empty."); } this.name = name; if (maxTotal != null) { if (maxTotal <= 0) { throw new IllegalArgumentException("maxTotal should be positive."); } this.maxTotal = maxTotal; } if (maxIdle != null) { if (maxIdle < 0) { throw new IllegalArgumentException("maxIdle should not be negative."); } this.maxIdle = maxIdle; } if (minIdle != null) { if (minIdle < 0) { throw new IllegalArgumentException("minIdle should not be negative."); } this.minIdle = minIdle; } } //...省略getter方法... }
使用
// 参数太多,导致可读性差、参数可能传递错误 ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20,false, true);
传统实现方式优化版二
实现
public class ResourcePoolConfig { private static final int DEFAULT_MAX_TOTAL = 8; private static final int DEFAULT_MAX_IDLE = 8; private static final int DEFAULT_MIN_IDLE = 0; private String name; private int maxTotal = DEFAULT_MAX_TOTAL; private int maxIdle = DEFAULT_MAX_IDLE; private int minIdle = DEFAULT_MIN_IDLE; public ResourcePoolConfig(String name) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("name should not be empty."); } this.name = name; } public void setMaxTotal(int maxTotal) { if (maxTotal <= 0) { throw new IllegalArgumentException("maxTotal should be positive."); } this.maxTotal = maxTotal; } public void setMaxIdle(int maxIdle) { if (maxIdle < 0) { throw new IllegalArgumentException("maxIdle should not be negative."); } this.maxIdle = maxIdle; } public void setMinIdle(int minIdle) { if (minIdle < 0) { throw new IllegalArgumentException("minIdle should not be negative."); } this.minIdle = minIdle; } //...省略getter方法... }
使用
// ResourcePoolConfig使用举例 ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool"); config.setMaxTotal(16); config.setMaxIdle(8);
缺点
1.名字不能为空 只能在使用的时候校验
2.配置间的依赖关系 比如,如果用户设置了 maxTotal、maxIdle、minIdle 其中一个,就必须显式地设置另外两个;或者配置项之间有一定的约束条件,比如,maxIdle 和 minIdle 要小于等于 maxTotal。如果我们继续使用现在的设计思路,那这些配置项之间的依赖关系或者约束条件的校验逻辑就无处安放了。
建造者模式实现
通过建造者的集中校验
public class ResourcePoolConfig { private String name; private int maxTotal; private int maxIdle; private int minIdle; private ResourcePoolConfig(Builder builder) { this.name = builder.name; this.maxTotal = builder.maxTotal; this.maxIdle = builder.maxIdle; this.minIdle = builder.minIdle; } //...省略getter方法... //我们将Builder类设计成了ResourcePoolConfig的内部类。 //我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。 public static class Builder { private static final int DEFAULT_MAX_TOTAL = 8; private static final int DEFAULT_MAX_IDLE = 8; private static final int DEFAULT_MIN_IDLE = 0; private String name; private int maxTotal = DEFAULT_MAX_TOTAL; private int maxIdle = DEFAULT_MAX_IDLE; private int minIdle = DEFAULT_MIN_IDLE; public ResourcePoolConfig build() { // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等 if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("..."); } if (maxIdle > maxTotal) { throw new IllegalArgumentException("..."); } if (minIdle > maxTotal || minIdle > maxIdle) { throw new IllegalArgumentException("..."); } return new ResourcePoolConfig(this); } public Builder setName(String name) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("..."); } this.name = name; return this; } public Builder setMaxTotal(int maxTotal) { if (maxTotal <= 0) { throw new IllegalArgumentException("..."); } this.maxTotal = maxTotal; return this; } public Builder setMaxIdle(int maxIdle) { if (maxIdle < 0) { throw new IllegalArgumentException("..."); } this.maxIdle = maxIdle; return this; } public Builder setMinIdle(int minIdle) { if (minIdle < 0) { throw new IllegalArgumentException("..."); } this.minIdle = minIdle; return this; } } } // 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle ResourcePoolConfig config = new ResourcePoolConfig.Builder() .setName("dbconnectionpool") .setMaxTotal(16) .setMaxIdle(10) .setMinIdle(12) .build();
通过builder集中设置,最后build创建对象
场景二构建复杂对象
说明
当一个对象的构建过于复杂的时候(如:解析 校验 加工 查询结果 组装) 每一步都很复杂。如果使用面向过程的设计函数化会导致impl非常臃肿和非常多的方法。代码的可读性非常差。有什么办法可以让构建复杂对象 的各个对象暗部就班的来,而不至于代码眼花缭乱。这个时候就可以使用建造者模式,可以将一个类的构建和表示分离
什么是建造者模式
得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。
适用场景
隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果
多个部件都可以装配到一个对象中,但产生的运行结果不相同
产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
初始化一个对象时,参数过多,或者很多参数具有默认值
Builder模式不适合创建差异性很大的产品类
产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本
需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
主要作用
在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
用户只需要给出指定复杂对象的类型和内容;
建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
UML类图
director:为调度者 负责调度builder按照指定工序完成构建
AbstractBuilder为抽象的建造者对象,定义抽象的工序步骤,具体建造者需要实现抽象方法
APPHomePageBuilder 为具体的建造者对象 这里是app首页数据资源建造,也可以有微信首页建造等
product: 为最终构建出的产品(VO)
代码实现
附上一个自己业务场景的代码
建造者

/** * @author liqiang * @date 2020/7/1 09:54 * @Description: (what)茅台预约订单下单参与活动订单建造者 * (why)因为逻辑项很多,都定义在一个方法显得臃肿,将逻辑拆分成 各个部分 然后交给指挥者根据工序构建出最终对象 * (how) */ public class BookingActivityOrderPoBuilder { private ApplicationContext applicationContet = null; //==============================spring托管=================== private BookingActivitySettingRedisDao bookingActivitySettingRedisDao; private MaotaiBookingRedisDao maotaiBookingRedisDao; private BookingActivityLimitRedisDao bookingActivityLimitRedisDao; private BookingActivityOrderRedisDao bookingActivityOrderRedisDao; private BookingActivityOrderConvert bookingActivityOrderConvert; //==============================静态PORO====================== private PurchaseBookingProductReqDto purchaseBookingProductReqDto; private BookingActivitySettingRo bookingActivitySettingRo; private BookingActivityLimitRo bookingActivityLimitRo; private MaotaiBookingRo maotaiBookingRo; private BookingActivityOrderPo bookingActivityOrderPo; private Date today = new Date(); //年度下单数量 List<BookingActivityOrderRo> yearOrderRos; //月度下单 List<BookingActivityOrderRo> monthOrderRos; //月度盘点如果年度度有加载 就不查redis boolean isLoadYear=false; //==============================错误码定义==============================/ public BookingActivityOrderPoBuilder(ApplicationContext applicationContext, PurchaseBookingProductReqDto purchaseBookingProductReqDto) { this.purchaseBookingProductReqDto = purchaseBookingProductReqDto; this.applicationContet = applicationContext; this.bookingActivitySettingRedisDao = applicationContext.getBean(BookingActivitySettingRedisDao.class); this.maotaiBookingRedisDao = applicationContext.getBean(MaotaiBookingRedisDao.class); this.bookingActivityLimitRedisDao = applicationContext.getBean(BookingActivityLimitRedisDao.class); this.bookingActivityOrderRedisDao = applicationContext.getBean(BookingActivityOrderRedisDao.class); this.bookingActivityOrderConvert = applicationContext.getBean(BookingActivityOrderConvert.class); } /** * 基本参数校验 */ public void validateReqDto() { ValidatorUtils.ValidateResult validateResult = ValidatorUtils.validateEntity(purchaseBookingProductReqDto, Default.class); if (validateResult.isError()) { throw new BusinessException(500, validateResult.getFirstMessage("[购买失败]")); } } /** * 获得活动信息 */ public void buildActivity() { this.bookingActivitySettingRo = bookingActivitySettingRedisDao.findOne(purchaseBookingProductReqDto.getActivityId()); } /** * 校验活动信息 */ public void validateActivity() { if (bookingActivitySettingRo == null) { throw new BusinessException(GlobalException.MaotaiBookingException.noSelectActivity); } if (!bookingActivitySettingRo.getEnable()) { throw new BusinessException(GlobalException.MaotaiBookingException.noEnable); } if (bookingActivitySettingRo.getStartTime().getTime() > System.currentTimeMillis()) { throw new BusinessException(GlobalException.MaotaiBookingException.notStar); } if (bookingActivitySettingRo.getLotteryTime() == null) { throw new BusinessException(GlobalException.MaotaiBookingException.noLottery); } if(!bookingActivitySettingRo.getProductCode().equals(purchaseBookingProductReqDto.getProductCode())){ throw new BusinessException(GlobalException.MaotaiBookingException.notMatchProduct); } } /** * 获得预约记录 */ public void buildBooking() { maotaiBookingRo = maotaiBookingRedisDao.findOne(purchaseBookingProductReqDto.getBookingId()); } /** * 校验预约记录 */ public void validateBooking() { if (maotaiBookingRo == null) { throw new BusinessException(GlobalException.MaotaiBookingException.noBooking); } if (maotaiBookingRo.getWinPrize().equals(PrizeStatusEnum.PENDING)) { throw new BusinessException(GlobalException.MaotaiBookingException.winPrizePending); } if (!maotaiBookingRo.getWinPrize().equals(PrizeStatusEnum.YES)) { throw new BusinessException(GlobalException.MaotaiBookingException.winPrizeNot); } //===================================是否是当前用户的活动========================== if (!maotaiBookingRo.getMemberId().equals(purchaseBookingProductReqDto.getUserId())) { throw new BusinessException(GlobalException.MaotaiBookingException.bookingNotMatch); } } /** * 支付时效校验 */ public void validatePaymentDate() { Calendar c = Calendar.getInstance(); c.setTimeInMillis(bookingActivitySettingRo.getRealLotteryTime().getTime()); c.add(Calendar.MINUTE, bookingActivitySettingRo.getPayExpireTime()); if (c.getTime().getTime() < System.currentTimeMillis()) { throw new BusinessException(GlobalException.MaotaiBookingException.paymentOverdueLimit); } } /** * 自然年购买数量限制校验 */ public void validateYearBuyLimit() { //=====================================自然年购买数量限制============================ bookingActivityLimitRo = bookingActivityLimitRedisDao.findOne(BookingActivityServiceImpl.LIMIT_ID); if (bookingActivityLimitRo != null && bookingActivityLimitRo.getBuyNumLimit() != null) { //年初时间 yyyy-01-01 00:00:00l Calendar yearstar = Calendar.getInstance(); yearstar.set(Calendar.HOUR_OF_DAY, 0); yearstar.set(Calendar.MINUTE, 0); yearstar.set(Calendar.SECOND, 0); yearstar.set(Calendar.MILLISECOND, 0); yearstar.set(Calendar.DATE, 1); yearstar.set(Calendar.MONTH,0); //========================此关系维护在RO注解上=================== yearOrderRos= bookingActivityOrderRedisDao.getCrateTimeBetweenByUserId(purchaseBookingProductReqDto.getUserId(), yearstar.getTime(), new Date()); isLoadYear=true; Long sumPurchaseQuantity =0L; if(CollectionUtils.isNotEmpty(yearOrderRos)){ yearOrderRos.removeIf(c->c.getOrderStatus().equals(MaotaiBookingService.cancelOrderStatus)); if(!CollectionUtils.isEmpty(yearOrderRos)){ sumPurchaseQuantity=yearOrderRos.stream().mapToLong(BookingActivityOrderRo::getQuantity).sum(); } } if ((sumPurchaseQuantity+purchaseBookingProductReqDto.getCount()) > bookingActivityLimitRo.getBuyNumLimit()) { throw new BusinessException(GlobalException.MaotaiBookingException.yearLimit); } } } /** * 月度中奖限制 */ public void validateMonthLimit() { if (bookingActivityLimitRo != null && bookingActivityLimitRo.getPrizeNumLimit() != null) { //月初时间 yyyy-mm-1 00:00:00l final Calendar monthStar = Calendar.getInstance(); monthStar.set(Calendar.HOUR_OF_DAY, 0); monthStar.set(Calendar.MINUTE, 0); monthStar.set(Calendar.SECOND, 0); monthStar.set(Calendar.MILLISECOND, 0); monthStar.set(Calendar.DATE, 1); //取出用户月度的中奖记录 List<MaotaiBookingRo> monthBookings = maotaiBookingRedisDao.findUserWinPrizeYes(maotaiBookingRo.getMemberId(), monthStar.getTime(), new Date()); if(!org.springframework.util.CollectionUtils.isEmpty(monthBookings)){ List<Long> activityIds= monthBookings.stream().map(c->c.getActivityId()).collect(Collectors.toList()); List<BookingActivitySettingRo> bookingActivitySettingRos=bookingActivitySettingRedisDao.findByIds(activityIds); //先中奖的先匹配 bookingActivitySettingRos.sort((c1,c2)->c2.getCreateTimestamp().after(c1.getCreateTimestamp())?-1:0); //取中奖次数限制的数量个 bookingActivitySettingRos=bookingActivitySettingRos.stream().limit(bookingActivityLimitRo.getPrizeNumLimit()).collect(Collectors.toList()); //如果当前活动不包含在里面 则返回可购买数量0 if(!bookingActivitySettingRos.stream().filter(c->c.getId().equals(bookingActivitySettingRo.getId())).findAny().isPresent()){ throw new BusinessException(GlobalException.MaotaiBookingException.monthLimit); } } } } /** * 活动购买次数校验 */ public void validateActivityLimit() { //=====================================活动购买数量限制============================ if (bookingActivitySettingRo.getLimitNum() != null) { Integer activityPurchaseQuantity = bookingActivityOrderRedisDao.getBuyNumber(bookingActivitySettingRo.getCreateTimestamp(), purchaseBookingProductReqDto.getUserId(), purchaseBookingProductReqDto.getActivityId()).intValue(); activityPurchaseQuantity = activityPurchaseQuantity + purchaseBookingProductReqDto.getCount(); if (activityPurchaseQuantity > bookingActivitySettingRo.getLimitNum()) { throw new BusinessException(GlobalException.MaotaiBookingException.activityLimit.getValue(),GlobalException.MaotaiBookingException.activityLimit.getDesc()); } } } /** * 组装产品 */ public void buildPrdocut() { //=====================================创建流水表记录=================================== bookingActivityOrderPo = new BookingActivityOrderPo(); bookingActivityOrderPo.setId(purchaseBookingProductReqDto.getOrderId()); bookingActivityOrderConvert.setBookingActivityOrder(purchaseBookingProductReqDto, bookingActivityOrderPo); bookingActivityOrderPo.setOrderStatus("-1"); } public BookingActivityOrderPo getResult() { return bookingActivityOrderPo; } public MaotaiBookingRo getMaotaiBookingRo() { return maotaiBookingRo; } }
指挥者
/** * @author liqiang * @date 2020/7/1 10:07 * @Description: (what)茅台预约订单下单参与活动订单指挥者 * (why) 因为逻辑项很多,都定义在一个方法显得臃肿,将逻辑拆分成 各个部分 然后交给指挥者根据工序构建出最终对象 * (how) */ public class BookingActivityOrderPoBuilderDirector { BookingActivityOrderPoBuilder bookingActivityOrderPoBuilder; public BookingActivityOrderPoBuilderDirector(BookingActivityOrderPoBuilder bookingActivityOrderPoBuilder){ this.bookingActivityOrderPoBuilder=bookingActivityOrderPoBuilder; } public BookingActivityOrderPo construct(){ //基本参数校验 bookingActivityOrderPoBuilder.validateReqDto(); //获得活动信息 bookingActivityOrderPoBuilder.buildActivity(); //校验活动对象 bookingActivityOrderPoBuilder.validateActivity(); //获得预约记录 bookingActivityOrderPoBuilder.buildBooking(); //校验预约记录 bookingActivityOrderPoBuilder.validateBooking(); //支付时效校验 bookingActivityOrderPoBuilder.validatePaymentDate(); //自然年购买数量校验 bookingActivityOrderPoBuilder.validateYearBuyLimit(); //月度购买数量校验 bookingActivityOrderPoBuilder.validateMonthLimit(); //活动购买数量校验 bookingActivityOrderPoBuilder.validateActivityLimit(); //组装产品 bookingActivityOrderPoBuilder.buildPrdocut(); //返回构建的产品 return bookingActivityOrderPoBuilder.getResult(); } }
使用
/** * 购买 * * @param purchaseBookingProductReqDto * @return */ @Override @Transactional(rollbackFor = Exception.class) public PurchaseBookingProductResDto purchaseBookingProduct(PurchaseBookingProductReqDto purchaseBookingProductReqDto) { //创建构建者 BookingActivityOrderPoBuilder bookingActivityOrderPoBuilder = new BookingActivityOrderPoBuilder(applicationContext, purchaseBookingProductReqDto); //创建指挥者 BookingActivityOrderPoBuilderDirector bookingActivityOrderPoBuilderDirector = new BookingActivityOrderPoBuilderDirector(bookingActivityOrderPoBuilder); //构建产品 BookingActivityOrderPo bookingActivityOrderPo = bookingActivityOrderPoBuilderDirector.construct(); //=====================================持久化=================================== bookingActivityOrderRepository.save(bookingActivityOrderPo); BookingActivityOrderRo bookingActivityOrderRo = bookingActivityOrderConvert.toBookingActivityRo(bookingActivityOrderPo); delayer.executeAfterTransactionCommit(() -> { bookingActivityOrderRedisDao.save(bookingActivityOrderRo); }); return new PurchaseBookingProductResDto(bookingActivityOrderPo.getId()); }
建造者与工厂模式的区别
1.工厂模式是创建相同类型不同实现的子类对象,实现多态
2.建造者是在通过不同的复杂的参数按照指定的工序构造一个复杂的对象(不一定指定的工序 还可以是不同的工序 在上面例子中build抽象出接口)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!