设计模式

#todo/note

设计模式,学了要会用,从知名开源项目中学习设计模式更是事半功倍,提高我们的代码鉴赏能力,比如:

虽有二十三种设计模式,但日常编码常用的就构造器、模板设计方法、策略模式,外加责任链

工厂模式

什么是工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一, 这种类型的设计模式属于创建型模式,它的思想是提供一个工厂方法返回对象!可以理解为【代工厂】

工厂模式的作用

  • 对象通过工厂的方法创建返回,工厂的方法可以为该对象进行加工和数据注入
  • 可以实现类与类之间的解耦操作(核心思想)

一个工厂模式的例子

联想到实际生活,公司会把具体产品类的信息和数据告诉工厂,让工厂去替他们生产出具体的产品而不是自己去生产具体的产品。以电脑产品为例:

  1. 首先先定义Computer的抽象类
public abstract class Computer {
    private String name;
    private double price;

    public abstract void start();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}
  1. 现在有两个MacHuaiwei品牌的电脑继承和发展了Computer
public class Mac extends Computer{
    @Override
    public void start() {
        System.out.println(getName() + "以非常优雅的方法启动了,展示了一个苹果logo");
    }
}

------------------------------------------------------------------------

public class Huawei extends Computer{  
    @Override  
 public void start() {  
        System.out.println(getName() + "开机了,展示了华为的菊花图标~~~~");  
 }  
}
  1. 现在有一家Factory工厂专门为公司生产品牌电脑
public class FactoryPattern {
    /**
       定义一个方法,创建对象返回
     */
    public static Computer createComputer(String info){
        switch (info){
            case "huawei":
                Computer c = new Huawei();
                c.setName("huawei pro 16");
                c.setPrice(5999);
                return c;
            case "mac":
                Computer c2 = new Mac();
                c2.setName("MacBook pro");
                c2.setPrice(11999);
                return c2;
            default:
                return null;
        }
    }
}

  1. 接下来,不MacHuawei公司都来委托Factory工厂帮他们生产电脑
public class FactoryDemo {
    public static void main(String[] args) {
        Computer c1 = FactoryPattern.createComputer("huawei");
        c1.start();

        Computer c2 = FactoryPattern.createComputer("mac");
        c2.start();
    }
}

可以看到多个类之间不在耦合,公司如果对工厂生产的产品不满意只要换更好的工厂即可【只需要修改FactoryPattern类即可】

装饰模式

什么是装饰设计模式及其作用

创建一个新类,包装原始类,从而在新类中提升原来类的功能。
简单来说就是:在加强类中定义抽象父类的实现类,然后调用实现类,并增强功能
作用: 装饰模式指的是在不改变原类的基础上, 动态地扩展一个类的功能。

一个装饰模式的例子

以IO流为例,模拟其装饰过程

  1. 定义父类
/**
   共同父类
 */
public abstract class InputStream {
    public abstract int read();
    public abstract int read(byte[] buffer);
}
  1. 定义原始类,继承父类,定义功能。
/**
   原始类
 */
public class FileInputStream extends InputStream{
    @Override
    public int read() {
        System.out.println("低性能的方式读取了一个字节a");
        return 97;
    }

    @Override
    public int read(byte[] buffer) {
        buffer[0] = 97;
        buffer[1] = 98;
        buffer[2] = 99;
        System.out.println("低性能的方式读取了一个字节数组:" + Arrays.toString(buffer));
        return 3;
    }
}
  1. 定义装饰类,继承父类,包装原始类,增强功能!!
/**
   装饰类:继承InputStream 拓展原始类的功能
 */
public class BufferedInputStream extends InputStream{
    private InputStream is;
    public BufferedInputStream(InputStream is){
        this.is = is;
    }
    @Override
    public int read() {
        System.out.println("提供8KB的缓冲区,提高读数据性能~~~~");
        return is.read();
    }

    @Override
    public int read(byte[] buffer) {
        System.out.println("提供8KB的缓冲区,提高读数据性能~~~~");
        return is.read(buffer);
    }
}

构造器模式

@Builder注解

@Builder注解通常与Java语言中的Lombok库一起使用。它的作用是简化Java类的构造方法和生成Builder模式的代码。使用@Builder注解,你可以轻松地创建一个具有多个属性的对象,而无需手动编写冗长的构造方法或Builder模式的代码。

以下是@Builder注解的一些主要功能和用法:

  1. 自动生成Builder类:当你在类上使用@Builder注解时,Lombok会自动生成一个相应的Builder类,该Builder类包含了类中所有字段的设置方法,使你可以链式调用设置属性的方法。

  2. 省去构造方法:@Builder注解允许你创建一个对象,只需提供所需属性的值,而不需要编写构造方法。这可以大大简化代码。

  3. 默认值:你可以在属性上使用@Builder.Default注解来设置默认值,这样在使用Builder创建对象时,如果没有显式设置该属性的值,就会使用默认值。

  4. 忽略属性:如果你不希望某些属性被Builder设置,可以使用@Builder.Exclude注解来排除这些属性。

示例代码:

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class Person {
    private String name;
    private int age;
    @Builder.Default
    private String country = "China";
}

// 使用Builder创建对象
Person person = Person.builder()
    .name("John")
    .age(30)
    .build();

// 使用默认值
Person defaultPerson = Person.builder()
    .name("Alice")
    .build();

总之,@Builder注解是用来简化Java对象的构建过程的工具,特别适用于具有多个属性的类。通过减少样板代码,它可以提高代码的可读性和维护性。

策略模式🎯

定义一组算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式使这些算法在客户端调用它们的时候能够互不影响地变化,客户端代指使用算法的代码。用来消除 if-else、switch 等多重判断的代码,消除 if-else、switch 多重判断 可以有效应对代码的复杂性。

image.png
这段代码是能够满足项目中业务需求的,而且很多已上线生产环境的代码也有这类代码。但是,这一段代码存在存在两个弊端:

  1. 代码的复杂性,正常业务代码逻辑肯定会比这个代码块复杂很多,这也就 导致了 if-else 的分支以及代码数量过多。这种方式可以通过将代码拆分成独立函数或者拆分成类来解决。
  2. 开闭原则,价格优惠肯定会 随着不同的时期作出不同的改变,或许新增、删除或修改。如果在一个函数中修改无疑是件恐怖的事情,想想可能多个开发者分别进行开发,杂乱无章的注释,混乱的代码逻辑等情况十有八九会发生。

策略模式示例

将上述代码块改造为策略设计模式,大致需要三个步骤。

  1. 定义抽象策略接口,因为业务使用接口而不是具体的实现类的话,便可以灵活的替换不同的策略;
  2. 定义具体策略实现类,实现自抽象策略接口,其内部封装具体的业务实现;
  3. 定义策略工厂,封装创建策略实现(算法),对客户端屏蔽具体的创建细节。

目前把抽象策略接口、具体的策略实现类以及策略工厂都已经创建了,现在可以看一下客户端需要如何调用,又是如何对客户端屏蔽具体实现细节的。

根据代码块图片得知,具体策略类是从策略工厂中获取,确实是取消了 if-else 设计,在工厂中使用 Map 存储策略实现。获取到策略类后执行具体的优惠策略方法就可以获取优惠后的金额。

通过分析大家得知,目前这种设计确实将应用代码的复杂性降低了。如果新增一个优惠策略,只需要新增一个策略算法实现类即可。但是,添加一个策略算法实现,意味着需要改动策略工厂中的代码,还是不符合开闭原则。

如何完整实现符合开闭原则的策略模式,需要借助 Spring 的帮助,详细案例请继续往下看。

项目中真实的应用场景

最近项目中设计的一个功能用到了策略模式,分为两类角色,笔者负责定义抽象策略接口以及策略工厂,不同的策略算法需要各个业务方去实现,可以联想到上文中的优惠券功能。因为是 Spring 项目,所以都是按照 Spring 的方式进行处理,话不多说,上代码。

可以看到,比对上面的示例代码,有两处明显的变化:

  1. 抽象策略接口中,新定义了 mark() 接口,此接口用来标示算法的唯一性;
  2. 具体策略实现类,使用 @Component 修饰,将对象本身交由 Spring 进行管理 。

小贴士:为了阅读方便,mark() 返回直接使用字符串替代,读者朋友在返回标示时最好使用枚举定义。

接下来继续查看抽象策略工厂如何改造,才能满足开闭原则。

和之前责任链模式相同 (TODO 添加链接),都是通过 InitializingBean 接口实现中调用 IOC 容器查找对应策略实现,随后将策略实现 mark() 方法返回值作为 key, 策略实现本身作为 value 添加到 Map 容器中等待客户端的调用。

这里使用的 SpringBoot 测试类,注入策略工厂 Bean,通过策略工厂选择出具体的策略算法类,继而通过算法获取到优惠后的价格。小插曲:如果不想把策略工厂注入 Spring 也可以,实现方法有很多。

总结下本小节,我们通过和 Spring 结合的方式,通过策略设计模式对文初的代码块进行了两块优化:应对代码的复杂性,让其满足开闭原则。更具体一些呢就是 通过抽象策略算法类减少代码的复杂性,继而通过 Spring 的一些特性同时满足了开闭原则,现在来了新需求只要添加新的策略类即可,健壮易扩展。

责任链模式

public interface AbstractChainHandler<T> extends Ordered {

    /**
     * 执行责任链逻辑
     *
     * @param requestParam 责任链执行入参
     */
    void handler(T requestParam);

    /**
     * @return 责任链组件标识
     */
    String mark();
}

具体的过滤器实现:

/**
 * 购票流程过滤器之验证参数必填
 *
 * @公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
 */
@Component
public class TrainPurchaseTicketParamNotNullChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
    	// ......
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

/**
 * 购票流程过滤器之验证参数是否有效
 * 验证参数有效这个流程会大量交互缓存,为了优化性能需要使用 Lua。为了方便大家理解流程,这里使用多次调用缓存
 *
 * @公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
 */
@Component
@RequiredArgsConstructor
public class TrainPurchaseTicketParamVerifyChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {

    private final TrainMapper trainMapper;
    private final TrainStationMapper trainStationMapper;
    private final DistributedCache distributedCache;

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        // ......
    }

    @Override
    public int getOrder() {
        return 10;
    }
}

/**
 * 购票流程过滤器之验证列车站点库存是否充足
 *
 * @公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
 */
@Component
@RequiredArgsConstructor
public class TrainPurchaseTicketParamStockChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {

    private final SeatMarginCacheLoader seatMarginCacheLoader;
    private final DistributedCache distributedCache;

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        // ......
    }

    @Override
    public int getOrder() {
        return 20;
    }
}

/**
 * 购票流程过滤器之验证乘客是否重复购买
 *
 * @公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
 */
@Component
@RequiredArgsConstructor
public class TrainPurchaseTicketRepeatChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        // ......
    }

    @Override
    public int getOrder() {
        return 30;
    }
}

购票流程使用过滤器:

private final AbstractChainContext<PurchaseTicketReqDTO> purchaseTicketAbstractChainContext;

@Override
@Transactional(rollbackFor = Throwable.class)
public TicketPurchaseRespDTO purchaseTickets(PurchaseTicketReqDTO requestParam) {
    // 责任链模式,验证 0:参数必填 1:参数正确性 2:列车车次余量是否充足 3:乘客是否已买当前车次等
    purchaseTicketAbstractChainContext.handler(TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name(), requestParam);
	// ......
}

购票责任链实现原理:

  1. 为起到标识特定子业务线的效果,增设一层Handler 接口的子接口。
  2. 一条业务线内,可能有多个 Handler 执行各种各样的校验任务:编写对应 Handler 实现类。
  3. 多个实现类需要一个容器组织起来:编写 Context 类容纳若干实现类,并提供一个 HashMap 供外界按需取用 Handler。
/**
 * 抽象责任链上下文
 *
 * @公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
 */
public final class AbstractChainContext<T> implements CommandLineRunner {

    private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();

    /**
     * 责任链组件执行
     *
     * @param mark         责任链组件标识
     * @param requestParam 请求参数
     */
    public void handler(String mark, T requestParam) {
        List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
        if (CollectionUtils.isEmpty(abstractChainHandlers)) {
            throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
        }
        abstractChainHandlers.forEach(each -> each.handler(requestParam));
    }

    @Override
    public void run(String... args) throws Exception {
        Map<String, AbstractChainHandler> chainFilterMap = ApplicationContextHolder
                .getBeansOfType(AbstractChainHandler.class);
        chainFilterMap.forEach((beanName, bean) -> {
            List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());
            if (CollectionUtils.isEmpty(abstractChainHandlers)) {
                abstractChainHandlers = new ArrayList();
            }
            abstractChainHandlers.add(bean);
            List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers.stream()
                    .sorted(Comparator.comparing(Ordered::getOrder))
                    .collect(Collectors.toList());
            abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);
        });
    }
}