设计模式面试篇

文章目录


前言

最近接手的项目遇到了比较大的坑,在实现流量套餐业务时(有贵宾、高级贵宾、WiFi、导航等等套餐),在其实现的code中光if…else…就超过了100行以上,此时心里一万只羊驼跑过…,最近刚好在学习设计模式,这类的业务可以用建造者模式设计,便先写下这篇作者对建造者模式理解,希望能帮到你。

为什么学习设计模式?

  1. 领域建模能力 将复杂业务合理拆分;
  2. 提升自身功能设计和code能力;
  3. code可读性强,扩展性强;
  4. 提升阅读框架源码能力;
  5. 面试加分项。

设计模式分类

  1. 创建型模式:提供创建对象的机制,提升已有代码的灵活性和可复用性;
  2. 结构型模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效;
  3. 行为型模式:负载对象间的高效沟通和职责传递委派。

本篇介绍的建造者模式就是属于 创建型模式。

需求模拟

为了让建造者模式有温度,这里模拟一个业务需求,通过2种方式(if…else方式和设计模式)来实现业务需求让大家更好的理解建造者模式。

业务需求如下:
装修公司装修3种套餐:豪华欧式、轻奢田园、现代简约套餐。而这些套餐的背后是不同装修材料和设计风格的组合,如 一级顶、二级顶、多乐士涂料、立邦涂料、圣象地板、德尔地板、马可波罗地砖、东鹏地砖等。按照不同的套餐价格,选取不同的品牌进行组合,最终再结合装修面积给出整体报价。

可以思考一下,如果让你来实现,你会怎么设计?

场景模拟工程

1.装修材料接口

装修材料接口提供了基本方法获取信息,以保证所有不同规格和种类的装修材料都可以按照统一标准获取。

代码如下(示例):

/**
 * 装修材料接口
 * @author winter
 */
public interface Matter {
    /**
     * 场景:地板、地砖、涂料、吊顶
     * @return
     */
    public String scene();

    /**
     * 品牌
     * @return
     */
    public String brand();

    /**
     * 型号
     * @return
     */
    public String model();

    /**
     * 价格
     * @return
     */
    public BigDecimal price();

    /**
     * 描述
     * @return
     */
    public String desc();

}

2.吊顶材料

代码如下(示例):

/**
 * 一级顶的吊顶材料
 * @author winter
 */
public class LevelOneCelling implements Matter {
    @Override
    public String scene() {
        return "吊顶";
    }

    @Override
    public String brand() {
        return "装修公司";
    }

    @Override
    public String model() {
        return "一级顶";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(260);
    }

    @Override
    public String desc() {
        return "一级顶...";
    }
}
/**
 * 二级顶的吊顶材料
 * @author winter
 */
public class LevelTwoCelling implements Matter {
    @Override
    public String scene() {
        return "吊顶";
    }

    @Override
    public String brand() {
        return "装修公司";
    }

    @Override
    public String model() {
        return "二级顶";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(850);
    }

    @Override
    public String desc() {
        return "二级吊顶...";
    }
}

3.涂料材料

代码如下(示例):

/**
 * 多乐士 涂料
 * @author winter
 */
public class DuluxCoat implements Matter {
    @Override
    public String scene() {
        return "涂料";
    }

    @Override
    public String brand() {
        return "多乐士";
    }

    @Override
    public String model() {
        return "第二代";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(719);
    }

    @Override
    public String desc() {
        return "多乐士涂料......";
    }
}
/**
 * 立邦 涂料
 * @author winter
 */
public class LiBangCoat implements Matter {
    @Override
    public String scene() {
        return "涂料";
    }

    @Override
    public String brand() {
        return "立邦";
    }

    @Override
    public String model() {
        return "最新代";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(650);
    }

    @Override
    public String desc() {
        return "立邦涂料......";
    }
}

4.地板材料

代码如下(示例):

/**
 * 德尔 地板材料
 * @author winter
 */
public class DerFloor implements Matter {

    @Override
    public String scene() {
        return "地板";
    }

    @Override
    public String brand() {
        return "德尔";
    }

    @Override
    public String model() {
        return "A+";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(119);
    }

    @Override
    public String desc() {
        return "德尔地板...";
    }
}
/**
 * 圣象 地板材料
 * @author winter
 */
public class ShengXiangFloor implements Matter {

    @Override
    public String scene() {
        return "地板";
    }

    @Override
    public String brand() {
        return "圣象";
    }

    @Override
    public String model() {
        return "一级";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(318);
    }

    @Override
    public String desc() {
        return "圣象地板...";
    }
}

5.地砖材料

代码如下(示例):

/**
 * 东鹏 地砖材料
 * @author winter
 */
public class DongPengTile implements Matter {
    @Override
    public String scene() {
        return "地砖";
    }

    @Override
    public String brand() {
        return "东鹏瓷砖";
    }

    @Override
    public String model() {
        return "1001";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(102);
    }

    @Override
    public String desc() {
        return "东鹏瓷砖...";
    }
}
/**
 * 马可波罗 地砖材料
 * @author winter
 */
public class MarcoPoloTile implements Matter {
    @Override
    public String scene() {
        return "地砖";
    }

    @Override
    public String brand() {
        return "马可波罗";
    }

    @Override
    public String model() {
        return "默认";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(140);
    }

    @Override
    public String desc() {
        return "马可波罗地砖...";
    }
}

违背设计模式实现

1.if…else实现需求

代码如下(示例):

/**
 * 装修套餐实现类  违背设计模式实现
 * @author winter
 */
public class DecorationPackageController {

    /**
     * 装修套餐方法
     * @param area 面积
     * @param level 装修类型
     * @return
     */
    public String getMatterList(BigDecimal area, Integer level) {
        // 装修清单
        List<Matter> list = new ArrayList<Matter>();
        // 装修价格
        BigDecimal price = BigDecimal.ZERO;

        // 豪华欧式
        if (1 == level) {
            // 吊顶,二级顶
            LevelTwoCelling levelTwoCeiling = new LevelTwoCelling();
            // 涂料,多乐士
            DuluxCoat duluxCoat = new DuluxCoat();
            // 地板,圣象
            ShengXiangFloor shengXiangFloor = new ShengXiangFloor();

            list.add(levelTwoCeiling);
            list.add(duluxCoat);
            list.add(shengXiangFloor);

            price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
            price = price.add(area.multiply(new BigDecimal("1.4")).multiply(duluxCoat.price()));
            price = price.add(area.multiply(shengXiangFloor.price()));

        }

        // 轻奢田园
        if (2 == level) {
            // 吊顶,二级顶
            LevelTwoCelling levelTwoCeiling = new LevelTwoCelling();
            // 涂料,立邦
            LiBangCoat liBangCoat = new LiBangCoat();
            // 地砖,马可波罗
            MarcoPoloTile marcoPoloTile = new MarcoPoloTile();

            list.add(levelTwoCeiling);
            list.add(liBangCoat);
            list.add(marcoPoloTile);

            price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
            price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
            price = price.add(area.multiply(marcoPoloTile.price()));

        }

        // 现代简约
        if (3 == level) {
            // 吊顶,二级顶
            LevelOneCelling levelOneCeiling = new LevelOneCelling();
            // 涂料,立邦
            LiBangCoat liBangCoat = new LiBangCoat();
            // 地砖,东鹏
            DongPengTile dongPengTile = new DongPengTile();

            list.add(levelOneCeiling);
            list.add(liBangCoat);
            list.add(dongPengTile);

            price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelOneCeiling.price()));
            price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
            price = price.add(area.multiply(dongPengTile.price()));
        }

        StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
                "装修清单" + "\r\n" +
                "套餐等级:" + level + "\r\n" +
                "套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
                "房屋面积:" + area.doubleValue() + " 平米\r\n" +
                "材料清单:\r\n");

        for (Matter matter: list) {
            detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
        }

        return detail.toString();

    }
}

2.单元测试

代码如下(示例):

@SpringBootTest
class BuilderApplicationTests {
    @Test
    void decorationPackageControllerTest() {
        DecorationPackageController decoration = new DecorationPackageController();
        // 豪华欧式
        System.out.println(decoration.getMatterList(new BigDecimal("132.52"),1));
        // 轻奢田园
        System.out.println(decoration.getMatterList(new BigDecimal("98.25"),2));
        // 现代简约
        System.out.println(decoration.getMatterList(new BigDecimal("85.43"),3));
    }
}

3.单元测试结果

装修清单
套餐等级:1
套餐价格:198064.39 元
房屋面积:132.52 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:多乐士、第二代、平米价格:719 元。
地板:圣象、一级、平米价格:318 元。


装修清单
套餐等级:2
套餐价格:119865.00 元
房屋面积:98.25 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:马可波罗、默认、平米价格:140 元。


装修清单
套餐等级:3
套餐价格:90897.52 元
房屋面积:85.43 平米
材料清单:
吊顶:装修公司、一级顶、平米价格:260 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:东鹏瓷砖、1001、平米价格:102 元。

建造者模式重构

1.模式的结构

建造者(Builder)模式的主要角色如下:

  1. 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  4. 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

其结构如下:
在这里插入图片描述

2.产品角色

产品角色: 包含多个组成部件的复杂对象。
代码如下(示例):
接口定义了填充吊顶、涂料、地板、地砖等各种方法

/**
 * 产品菜单
 * @author winter
 */
public interface ProductMenu {
    /**
     * 吊顶
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendCelling(Matter matter);
    /**
     * 涂料
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendCoat(Matter matter);
    /**
     * 地板
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendFloor(Matter matter);
    /**
     * 地砖
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendTile(Matter matter);

    /**
     * 明细
     * @return
     */
    String getDetail();
}

产品角色类实现类

/**
 * 装修产品
 * @author winter
 */
public class Product implements ProductMenu {
    /**
     * 装修清单
     */
    private List<Matter> list =new ArrayList<>();
    /**
     * 装修价格
     */
    private BigDecimal price = BigDecimal.ZERO;
    /**
     * 装修面积
     */
    private BigDecimal area;
    /**
     * 装修等级 豪华欧式、轻奢田园、现代简约
     */
    private String grade;

    public Product() {
    }

    public Product(Double area, String grade) {
        this.area = new BigDecimal(area);
        this.grade = grade;
    }

    @Override
    public ProductMenu appendCelling(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
        return this;
    }

    @Override
    public ProductMenu appendCoat(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
        return this;
    }

    @Override
    public ProductMenu appendFloor(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    @Override
    public ProductMenu appendTile(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    @Override
    public String getDetail() {
        StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
                "装修清单" + "\r\n" +
                "套餐等级:" + grade + "\r\n" +
                "套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
                "房屋面积:" + area.doubleValue() + " 平米\r\n" +
                "材料清单:\r\n");

        for (Matter matter: list) {
            detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
        }

        return detail.toString();
    }

3.抽象建造者

包含创建产品各个子部件的抽象方法

代码如下(示例)

/**
 * 抽象建造者
 * @author winter
 */
abstract class Builder {
    /**
     * 创建产品对象
     */
    protected Product product = new Product();

    /**
     * 豪华装修
     * @return
     */
    public abstract ProductMenu levelOne(Double area);

    /**
     * 轻奢田园
     * @return
     */
    public abstract ProductMenu levelTwo(Double area);

    /**
     * 现代简约
     * @return
     */
    public abstract ProductMenu levelThree(Double area);

    /**
     * 返回产品结果
     * @return
     */
    public Product getResult() {
        return product;
    }
}

4.具体建造者

代码如下(示例)

/**
 * 具体建造者
 * @author winter
 */
public class ConcreteBuilder extends Builder{

    @Override
    public ProductMenu levelOne(Double area) {
        product =new Product(area, "豪华欧式");
        // 吊顶,二级顶
        return product.appendCelling(new LevelTwoCelling())
                // 涂料,多乐士
                .appendCoat(new DuluxCoat())
                // 地板,圣象
                .appendFloor(new ShengXiangFloor());
    }

    @Override
    public ProductMenu levelTwo(Double area) {
        product =new Product(area, "轻奢田园");
        // 吊顶,二级顶
        return product.appendCelling(new LevelTwoCelling())
                // 涂料,立邦
                .appendCoat(new LiBangCoat())
                // 地砖,马可波罗
                .appendTile(new MarcoPoloTile());
    }

    @Override
    public ProductMenu levelThree(Double area) {
        product =new Product(area, "现代简约");
        // 吊顶,二级顶
        return product.appendCelling(new LevelOneCelling())
                // 涂料,立邦
                .appendCoat(new LiBangCoat())
                // 地砖,东鹏;
                .appendTile(new DongPengTile());
    }
}

5.指挥者

调用建造者中的方法完成复杂对象的创建。

/**
 * 指挥者
 * @author winter
 */
public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    /**
     * 产品构建与组装方法
     * @return
     */
    public String constructOne(Double area) {
        builder.levelOne(area);
        return builder.getResult().getDetail();
    }
    /**
     * 产品构建与组装方法
     * @return
     */
    public String constructTwo(Double area) {
        builder.levelTwo(area);
        return builder.getResult().getDetail();
    }
    /**
     * 产品构建与组装方法
     * @return
     */
    public String constructThree(Double area) {
        builder.levelThree(area);
        return builder.getResult().getDetail();
    }
}

6.客户类(测试)

代码如下(示例)

public class Client {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        //豪华装修
        String result1 = director.constructOne(132.52);
        System.out.println(result1);
        //轻奢田园
        String result2 = director.constructTwo(98.25);
        System.out.println(result2);
        //现代简约
        String result3 = director.constructThree(85.43);
        System.out.println(result3);
    }
}

7.测试结果


装修清单
套餐等级:豪华欧式
套餐价格:198064.39 元
房屋面积:132.52 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:多乐士、第二代、平米价格:719 元。
地板:圣象、一级、平米价格:318 元。


装修清单
套餐等级:轻奢田园
套餐价格:119865.00 元
Disconnected from the target VM, address: ‘127.0.0.1:49890’, transport: ‘socket’
房屋面积:98.25 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:马可波罗、默认、平米价格:140 元。


装修清单
套餐等级:现代简约
套餐价格:90897.52 元
房屋面积:85.43 平米
材料清单:
吊顶:装修公司、一级顶、平米价格:260 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:东鹏瓷砖、1001、平米价格:102 元。

执行结果和之前用if…else实现的结果一致。

总结

需求和案例是参考的《重学java设计模式》这本书,在使用建造者模式重构该业务时并没有按照书上方式实现(书上实现方式有简化),而是通过自己的理解严格按照建造者模式的结构实现。
通过上面对建造者模式的使用可以总结出:

  1. 建造者模式选择条件:当一些基本材料不变,而组合经常变化(如套餐);
  2. 建造者模式满足了单一职责原则及可复用技术,建造者独立,易扩展、便于扩展细节风险。

外观模式

文章目录


前言

本篇会用外观模式来设计一个处理白名单业务的中间件,将外观模式实际用到项目中。
注意:设计模式学的是设计思想,而不是固定的实现方式。

外观模式介绍

定义:
外观模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

外观模式是“迪米特法则”的典型应用,它有以下主要优点:

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类;
  2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易;
  3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

通过以上介绍可以发现第三方SDK、开源类库,基本上都使用了外观模式。

违背设计模式实现

伪代码如下:

public static boolean whiteList(Sting userId){
	//白名单列表
	List<String> list =new ArrayList();
	list.add("1001");
	list.add("1002");
	list.add("1003");
	
	//校验是在白名单中
	if(!list.contains(userId)){
		return false;
	}
	return true;
}

写上一个静态方法简单实现白名单功能,在controller层的接口都调用这个接口,如果是一个服务勉强还能接受,如果涉及是多个服务则需要将这个方法copy到其他相关服务中了,如果这块的业务需要变更涉及的服务都改,这种设计方式耦合度太高了,代码也冗余不够优雅。

设计模式重构

1.AOP切面注解

pom(示例):

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>facade</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>facade</name>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.8.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>

2.配置服务类

代码如下(示例):

/**
 * 配置服务类
 * @author winter
 */
public class StarterService {
    private String userStr;
    public StarterService(String userStr) {
        this.userStr = userStr;
    }
    public String[] split(String separatorChar) {
        return StringUtils.split(this.userStr, separatorChar);
    }
}

为了获取springboot中配置文件的信息内容。


3.配置类注解定义

代码如下(示例):

/**
 * 配置类注解定义
 * @author winter
 */
@ConfigurationProperties("white.list")
public class StarterServiceProperties {
    private String userStr;
    public String getUserStr() {
        return userStr;
    }
    public void setUserStr(String userStr) {
        this.userStr = userStr;
    }
}

获取到 application.yml 中添加 white.list的配置信息。

4.获取自定义配置类信息

/**
 * 获取自定义配置类信息
 * @author winter
 */
@Configuration
@ConditionalOnClass(StarterService.class)
@EnableConfigurationProperties(StarterServiceProperties.class)
public class StarterAutoConfigure {
    @Autowired
    private StarterServiceProperties properties;
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "white.list", value = "enabled", havingValue = "true")
    StarterService starterService() {
        return new StarterService(properties.getUserStr());
    }
}

主要是对注解@Configuration、@ConditionalOnClass、@EnableConfigurationProperties的定义,直接读取配置文件中数据并将StarterService加载到bean中。

5.切面注解定义

/**
 * 注解定义
 * @author winter
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {
    String key() default "";
}

切面注解定义了外观模式切面注解。

6.白名单切面逻辑

/**
 * 切面定义
 * @author winter
 */
@Aspect
@Component
public class DoJoinPoint {
    private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);
    @Autowired
    private StarterService starterService;
    @Pointcut("@annotation(com.example.facade.annotation.DoDoor)")
    public void aopPoint() {
    }
    @Around("aopPoint()")
    public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
        //获取内容
        Method method = getMethod(jp);
        DoDoor door = method.getAnnotation(DoDoor.class);
        //获取字段值
        String keyValue = getFiledValue(door.key(), jp.getArgs());
        logger.info("handler method:{} value:{}", method.getName(), keyValue);
        if (null == keyValue || "".equals(keyValue)) {
            return jp.proceed();
        }
        //配置内容
        String[] split = starterService.split(",");
        //白名单过滤
        for (String str : split) {
            if (keyValue.equals(str)) {
                return jp.proceed();
            }
        }
        //拦截
        return returnObject(method);
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {
        return jp.getTarget().getClass();
    }

    //返回对象
    private Object returnObject(Method method) throws IllegalAccessException, InstantiationException {
        Class<?> returnType = method.getReturnType();

        return JSON.parseObject("用户id不在白名单中", returnType);
    }
    //获取属性值
    private String getFiledValue(String filed, Object[] args) {
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (null == filedValue || "".equals(filedValue)) {
                    filedValue = BeanUtils.getProperty(arg, filed);
                } else {
                    break;
                }
            } catch (Exception e) {
                if (args.length == 1) {
                    return args[0].toString();
                }
            }
        }
        return filedValue;
    }
}

主要是用切面方式实现白名单校验的功能。

7.引入中间件POM

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>facade</artifactId>
            <version>1.0</version>
            <scope>compile</scope>
        </dependency>

备注:一般都是直接上传到maven上。

8.配置application.yml

white:
  list:
    enabled: true
    user-str: 1001,1002,1003

9.在controller中添加自定义注解

    @GetMapping("/user")
    @DoDoor(key = "userId")
    public String getUser(@RequestParam String userId){
        return "获取到用户信息";
    }

总结

通过中间件的方式实现外观模式,这种设计可以很好的增强代码的隔离性及复用性,不仅使用非常灵活,也降低了对每一个系统开发白名单拦截服务带来的风险及测试成本。

责任链模式

文章目录


请假审批需求

请假业务:员工1天内的请假直接主管可以直接审批;3天内的请假需要部门经理的审批;30天内的请假需要总经理的审批;大于30天,正常不会批准。

先思考一下,如果是你会如何设计这个业务?


违背设计模式的设计

用if…else实现的伪代码如下:

 public  String approve (int day){
 	if(day==1){
 		return "直接主管:已经处理;流程结束。";
 	} else if (1<day<=3){
 		return "部门经理:已经处理;流程结束。";
 	} else if(3<day<=30){
 		return "总经理:已经处理;流程结束。";
 	}else{
 		return "拒绝审批;流程结束。";
 	}
 }

备注:模拟的是一个简单流程,正常业务流比这个复杂得多。

责任链模式

1.定义

为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

2.模式的结构

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

结构相对比较简单。

3.模式的结构图

在这里插入图片描述

责任链模式

1.请假对象

/**
 *
 * 现实中,请假的OA申请,请假天数如果是半天到1天,可能直接主管批准即可;
 * 如果是1到3天的假期,需要部门经理批准;
 * 如果是3天到30天,则需要总经理审批;
 * 大于30天,正常不会批准。
 * @author winter
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LeaveRequest {
    /**
     * 天数
     *
     * */
    private int leaveDays;

    /**
     * 姓名
     * */
    private String name;
}

2.抽象处理者

/**
 *
 * 请假责任链抽象处理类
 * @author winter
 */
public class AbstractLeaveHandler {
    /**直接主管审批处理的请假天数*/
    protected int MIN = 1;
    /**部门经理处理的请假天数*/
    protected int MIDDLE = 3;
    /**总经理处理的请假天数*/
    protected int MAX = 30;

    /**领导名称*/
    protected String handlerName;

    /**下一个处理节点(即更高级别的领导)*/
    protected AbstractLeaveHandler nextHandler;

    /**
     * 设置下一个处理节点
     * @param handler
     */
    protected void setNextHandler(AbstractLeaveHandler handler){
        this.nextHandler = handler;
    }

    /**
     * 处理请假的请求,具体业务由子类实现
     * @param request
     */
    protected void handlerRequest(LeaveRequest request){

    }
}

主要定义了请求接口、下一个处理节点。

3.具体处理者

/**
 * 直接主管处理类
 * @author winter
 */
public class DirectLeaderLeaveHandler extends AbstractLeaveHandler {

    public DirectLeaderLeaveHandler(String name) {
        this.handlerName = name;
    }

    @Override
    protected void handlerRequest(LeaveRequest request) {
        //自己的权限内 直接主管可以直接审批 流程结束
        if(request.getLeaveDays() <= this.MIN){
            System.out.println("直接主管:" + handlerName + ",已经处理;流程结束。");
            return;
        }

        //超过自己权限的审批 判断是否还存在下一个处理人,如果存在则给下一个处理人处理
        if(null != this.nextHandler){
            this.nextHandler.handlerRequest(request);
        }else{
            System.out.println("审批拒绝!");
        }
    }
}
/**
 * 部门经理处理类
 * @author winter
 */
public class DeptManagerLeaveHandler extends AbstractLeaveHandler {

    public DeptManagerLeaveHandler(String name) {
        this.handlerName = name;
    }

    @Override
    protected void handlerRequest(LeaveRequest request) {
        //自己的权限内 可以直接审批 流程结束
        if(request.getLeaveDays() >this.MIN && request.getLeaveDays() <= this.MIDDLE){
            System.out.println("部门经理:" + handlerName + ",已经处理;流程结束。");
            return;
        }

        //超过自己权限的审批 判断是否还存在下一个处理人,如果存在则给下一个处理人处理
        if(null != this.nextHandler){
            this.nextHandler.handlerRequest(request);
        }else{
            System.out.println("审批拒绝!");
        }
    }
}
/**
 * 总经理处理类
 * @author winter
 */
public class GManagerLeaveHandler extends AbstractLeaveHandler {

    public GManagerLeaveHandler(String name) {
        this.handlerName = name;
    }

    @Override
    protected void handlerRequest(LeaveRequest request) {
        //自己的权限内 直接主管可以直接审批 流程结束
        if(request.getLeaveDays() > this.MIDDLE && request.getLeaveDays() <= this.MAX){
            System.out.println("总经理:" + handlerName + ",已经处理;流程结束。");
            return;
        }

        //超过自己权限的审批 判断是否还存在下一个处理人,如果存在则给下一个处理人处理
        if(null != this.nextHandler){
            this.nextHandler.handlerRequest(request);
        }else{
            System.out.println("审批拒绝!");
        }
    }
}

4.客户类角色

/**
 * 测试执行
 * @author winter
 */
public class Client {
    public static void main(String[] args) {
        LeaveRequest request = LeaveRequest.builder().leaveDays(1).name("小明").build();

        //处理人
        AbstractLeaveHandler directLeaderLeaveHandler = new DirectLeaderLeaveHandler("张组长");
        DeptManagerLeaveHandler deptManagerLeaveHandler = new DeptManagerLeaveHandler("李经理");
        GManagerLeaveHandler gManagerLeaveHandler = new GManagerLeaveHandler("王总");
        //处理人顺序
        directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler);
        deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler);

        directLeaderLeaveHandler.handlerRequest(request);

    }
}

测试结果:直接主管:张组长,已经处理;流程结束。

/**
 * 测试执行
 * @author winter
 */
public class Client {
    public static void main(String[] args) {
        LeaveRequest request = LeaveRequest.builder().leaveDays(3).name("小明").build();

        //处理人
        AbstractLeaveHandler directLeaderLeaveHandler = new DirectLeaderLeaveHandler("张组长");
        DeptManagerLeaveHandler deptManagerLeaveHandler = new DeptManagerLeaveHandler("李经理");
        GManagerLeaveHandler gManagerLeaveHandler = new GManagerLeaveHandler("王总");
        //处理人顺序
        directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler);
        deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler);

        directLeaderLeaveHandler.handlerRequest(request);

    }
}

测试结果:部门经理:李经理,已经处理;流程结束。

总结

优点:

  1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点:

  1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

应用场景:OA系统 请假审批流程、风控系统 审批流程 等等。

文章目录


前言

目前在整理总结设计模式,准备将设计模式当一个专题完成,作者写博客的目的只有是为了总结归纳自己的理解,如果有任何问题请帮忙指出,谢谢!
本篇要介绍的就是适配器模式。

需求

目前市面上大多数充电器都是将220V电压进行适配为5V电压供手机使用,但是在日本的电压是110V,为了出口到日本现在需要充电器也要适配110V电压。

这个业务场景用if…else方式很容易实现,就略过了。

适配器模式

1.简介

类适配器模式可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

简单来说就是,将原本不兼容的接口通过适配修改到统一,方便调用方使用。

2.结构

  1. 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  2. 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  3. 它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

2.分类

具体实现方式有:类适配器、对象适配器和接口适配器。
一般的业务场景不太推荐类适配器和对象适配器,以下是用接口适配器实现。

设计模式实现

1.电压实现

/**
 * 定义一个抽象的电源
 * @author winter
 */
public abstract class Power {

    /**
     * 默认为220V
     * @return
     */
    public int output(){
        return 220;
    }
}

/**
 * 220V电压
 * @author winter
 */
public class NormalPower extends Power{

    @Override
    public int output() {
        return 220;
    }
}
/**
 * 110V电压
 * @author winter
 */
public class LowPower extends Power {
    @Override
    public int output() {
        return 110;
    }
}

2.目标角色

定义一个将电压转成目标电压的接口

/**
 * 目标接口 定义一个转换为5V电压的方法
 * @author winter
 */
public interface Target {

    int change();
}

3.适配器角色

适配110V和220V电压转换功能

/**
 * 适配器
 * @author winter
 */
public class PowerAdapter implements Target {

    private Power power;

    public PowerAdapter(Power power) {
        this.power = power;
    }

    @Override
    public int change() {
        if (power instanceof NormalPower) {
            System.out.println("处理220V的电压转换业务...");
            return power.output()/44;
        }else {
            System.out.println("处理110V的电压转换业务...");
            return power.output()/22;
        }
    }
}

4.测试

    public static void main(String[] args) {
        Target target = new PowerAdapter(new NormalPower());
        int dc = target.change();
        System.out.println("转换后的电压为:" + dc + " 伏...");
    }

处理220V的电压转换业务…
转换后的电压为:5 伏…

总结

1.模式优缺点

优点:

  1. 客户端通过适配器可以透明地调用目标接口。
  2. 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
  3. 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
  4. 符合开闭原则。

缺点:

  1. 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
  2. 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

2.业务场景

  1. 版本兼容:如老接口改造,不影响旧版本项目使用。
  2. 输出接口统一,但是输入类型却不可预知。

3.其他

其实适配器模式和策略模式处理的业务非常相似,之前在实际项目中遇到了类似的业务,作者基本上都是用策略模式来设计的,通过这篇简单归纳对适配器模式有了更深了解,牢记设计模式学习的是设计思想而不是实现方式。

前言

作者目前在复习总结设计模式,便计划写一个设计模式的专题,本篇介绍的是代理模式,为了加深对代理模式认知,作者会记录实际项目中代理模式的实战。作者写博客主要是为了归纳总结技术,加深对它的理解,如有任何错误地方请帮忙指出。


业务需求

电商系统需要实现一个文件上传功能,考虑到成本和文件可能会较多前提下给出了以下方案:

  1. 大文件上传到阿里云SSO服务器上,如视频等;
  2. 小文件上传到本地FastDFS文件服务器上,如图片等。

功能设计

根据以上业务场景分析,得出的设计方案如下:

  1. 代理模式实现不同服务的文件上传 ;
  2. 策略模式降低程序中条件判断。

代理模式

模式定义

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

模式结构

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

模式优缺点

优点:

  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  2. 代理对象可以扩展目标对象的功能;
  3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。

缺点:

  1. 代理模式会造成系统设计中类的数量增加;
  2. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  3. 增加了系统的复杂度。

其他

设计模式是一种设计思想,不要将JDK动态代理、CGLIB及AOP等同于代理模式,这几种只是代理模式的实现方式而已。

设计模式实现

FileUpload抽象接口,定义了文件上传方法,分别给它写了2种实现

/**
 * FileUpload抽象接口,定义了文件上传方法,分别给它写了2种实现
 * @author winter
 */
public interface FileUpload {
    /***
     * 文件上传
     * @param buffers:文件字节数组
     * @param extName:后缀名
     * @return
     */
    String upload(byte[] buffers,String extName);
}

AliyunOSSFileUpload是将文件上传到aliyunOSS,主要上传mp4和avi的视频大文件

/**
 * AliyunOSSFileUpload是将文件上传到aliyunOSS,主要上传mp4和avi的视频大文件
 * @author winter
 */
@Component(value = "aliyunOSSFileUpload")
public class AliyunOSSFileUpload implements FileUpload {

    @Value("${aliyun.oss.backurl}")
    private String backurl;


    @Override
    public String upload(byte[] buffers, String extName) {
        String realName = UUID.randomUUID().toString()+"."+extName ;
        System.out.println("阿里云SSO上传。。。");
        return backurl+realName;
    }
}

FastdfsFileUpoad是将文件上传到FastDFS,主要上传png/jpg等图片小文件

/**
 * FastdfsFileUpoad是将文件上传到FastDFS,主要上传png/jpg等图片小文件。
 * @author winter
 */
@Component(value = "fastdfsFileUpoad")
public class FastdfsFileUpoad implements FileUpload {

    @Value("${fastdfs.url}")
    private String url;

    @Override
    public String upload(byte[] buffers, String extName) {
        String realName = UUID.randomUUID().toString() + "." + extName;
        System.out.println("fastdfs上传。。。");
        return url + realName;
    }
}

FileUploadProxy是代理对象,供用户访问,调用了FileUpload的文件上传方法,为用户提供不同文件上传调用。

@Data
@Component
@ConfigurationProperties(prefix = "upload")
public class FileUploadProxy implements ApplicationContextAware {
	//继承ApplicationContextAware就可以拿到容器
    private ApplicationContext act;

    /**
     * 读取配置文件中定义的配置
     */
    private Map<String,List<String>> filemap;

    /**
     *  文件上传
     * @param file 上传的文件
     * @return
     * @throws Exception
     */
    public String upload(MultipartFile file) throws Exception {
        //文件名字 1.mp4
        String fileName = file.getOriginalFilename();
        //扩展名 mp4,jpg
        String extName = StringUtils.getFilenameExtension(fileName);

        //循环filemap   策略模式降低程序中条件判断
        for (Map.Entry<String, List<String>> entry : filemap.entrySet()) {
            for (String suffix : entry.getValue()) {
                //匹配当前extName和当前map中对应的类型是否匹配
                if (extName.equalsIgnoreCase(suffix)) {
                    //一旦匹配,则把key作为唯一值,从容器中获取对应实例
                    return act.getBean(entry.getKey(),
                            FileUpload.class).upload(file.getBytes(), extName);
                }
            }
        }
        return null;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.act=applicationContext;
    }
}

yml配置

filemap:
  aliyunOSSFileUpload: mp4,avi
  fastdfsFileUpload: png,jpg
#FastDFS配置 fastdfs:
  url: http://localhost:28181/
#aliyun
aliyun:
  oss:
    backurl: https://sklll.oss-cn-beijing.aliyuncs.com/video/ #访问地址配置

spring:
  servlet:
    multipart:
      max-file-size: 100MB #上传文件大小配置

FileController是控制器,用于接收用户提交的文件,并调用代理FileUploadProxy实现文件上传。

/**
 * 文件上传
 * @author winter
 */
@RestController
@RequestMapping(value = "/file")
public class FileController {

    @Autowired
    private FileUploadProxy fileUploadProxy;

    /***
     * 文件上传
     * @param file
     * @return
     * @throws Exception
     *
     */
    @PostMapping(value = "/upload")
    public String upload(MultipartFile file) throws Exception {
        return fileUploadProxy.upload(file);
    }
}

总结

代理模式的应用场景除了代码级别,还可以将代理模式迁移到应用以及架构级别,针对一 些图片小文件,我们可以直接把文件存储到FastDFS服务,针对大文件,例如商品视频介绍,我们可以把它存储到第 三方OSS。
用户通过文件上传代理服务可以间接访问OSS和本地FastDFS,这种分布式海量文件管理解决方案,这里不仅在代码 层面充分运用了代理模式,在架构层面也充分运用了代理模式。

文章目录


前言

我写博客主要是为了梳理知识及归纳总结,我的每一篇博客都会超过3000字,并且都会附上相应的代码或图片,如有任何问题请帮忙指出,谢谢!


需求模拟

在电商系统中支付模块至关重要,支付一般包括支付宝支付、微信支付和银联支付,如何优雅设计支付模块?

违背设计模式的实现

使用if…else实现,伪代码如下:

	public String toPay(String type){
		if("ALI_PAY".equals(type)){
			return "支付宝支付";
		}else if("WECHAT_PAY".equals(type)){
			return "微信支付";
		}else if("UNION_PAY".equals(type)){
			return "银联支付";
		}
		return "支付类型异常";
	}

策略模式

1.定义

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理

2.优点

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

划重点:解决复杂业务场景下多重条件语句;避免重复代码;开闭原则;隔离性;高扩展。

3.模式结构

策略模式的主要角色如下:

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

4.模式结构图

在这里插入图片描述

设计模式重构

1.抽象策略

/**
 * 支付定义接口
 * @author winter
 */
public interface PayInterface {
    /**
     * 支付接口
     * @param payRequest
     * @return
     */
    String toPay(String payRequest);
}

2.具体策略

/**
 * 阿里支付业务类
 * @author winter
 */
@Service
public class AliPayService implements PayInterface {
    @Override
    public String toPay(String payRequest) {
        System.out.println("阿里支付执行业务。。。。");
        return "阿里支付";
    }
}
/**
 * 微信支付业务类
 * @author winter
 */
@Service
public class WechatPayService implements PayInterface {
    @Override
    public String toPay(String payRequest) {
        System.out.println("微信支付执行业务。。。。");
        return "微信支付";
    }
}
/**
 * 银联支付业务类
 * @author winter
 */
@Service
public class UnionPayService implements PayInterface {
    @Override
    public String toPay(String payRequest) {
        System.out.println("银联支付执行业务。。。。");
        return "银联支付";
    }
}

3.环境类

环境类的设计我采用了反射机制通过类型来直接获取正确的支付方式,具体如下:
枚举类:存放支付类型和对应的类名

/**
 * 策略枚举
 * @author winter
 */
@Getter
public enum StrategyEnum {
    /**
     * 支付宝支付
     */
    ALI_PAY("ALI_PAY","aliPayService"),
    /**
     * 微信支付
     */
    WECHAT_PAY("WECHAT_PAY","wechatPayService"),
    /**
     * 银联支付
     */
    UNION_PAY("UNION_PAY","unionPayService");
    /**
     * 方法名
     */
    private String name;
    /**
     * 类名  beanName
     */
    private String beanName;

    StrategyEnum(String name, String beanName) {
        this.name = name;
        this.beanName = beanName;
    }
}

Bean工具类:通过beanName获取到实例

@Component
public class SpringContextUtils implements ApplicationContextAware {
    /**
     * 上下文对象实例
     */
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    /**
     * 获取applicationContext
     * @return
     */
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    /**
     * 通过name获取 Bean.
     * @param name beanId
     * @return
     */
    public Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     * @param clazz  类名
     * @return
     */
    public <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }
    /**
     * 通过name,以及Clazz返回指定的Bean
     * @param name beanId
     * @param clazz 类名
     * @return
     */
    public <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }
}

环境类:

/**
 * 环境类
 * @since JDK 1.8
 * @author winter
 **/
@Component
public class PayContext {
    @Autowired
    private SpringContextUtils springContextUtils;
    /**
     * 发送短信
     * @param payRequest 入参
     * @return
     */
    public String toPay(String payRequest) {
        //通过支付类型获取到支付类的beanId
        String beanId = StrategyEnum.valueOf(payRequest).getBeanName();
        //初始化bean对象
        PayInterface payInterface = springContextUtils.getBean(beanId, PayInterface.class);
        return payInterface.toPay(payRequest);
    }
}

4.客户端调用

@RestController
@RequestMapping("/test")
public class PayController {
    @Autowired
    private PayContext payContext;
    @GetMapping("/pay")
    public String pay(String payRequest){
        return payContext.toPay(payRequest);
    }
}

以上就是通过策略模式优雅完成了支付模块的设计。

总结

策略模式一般都会和方法模板模式一起使用,策略模式是最典型可以优化if…else的复杂业务,通过使用这种设计模式后可以很好的满足隔离性和扩展性要求,也方便承接不断新增的需求。
如果深入了解会发现 策略模式、适配器模式和组合模式的使用方式非常相似。

文章目录


前言

作者目前在复习总结设计模式,便计划写一个设计模式的专题,本篇介绍的是单例模式,主要介绍的是7种单例模式的实现。作者写博客主要是为了归纳总结技术,加深对它的理解,如有任何错误地方请帮忙指出。

单例模式

1.定义

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

2.特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

3.优缺点

优点:

  1. 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  2. 可以避免对资源的多重占用。
  3. 单例模式设置全局访问点,可以优化和共享资源的访问。

缺点:

  1. 单例模式一般没有接口,扩展困难。
  2. 在并发测试中,单例模式不利于代码调试。
  3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

4.应用场景

  1. 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  2. 某类只要求生成一个对象的时候,如每个人的身份证号等。
  3. 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  4. 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
    5.当对象需要被共享的场合。

实现方式

1.懒汉模式 (线程不安全)

/**
 * 懒汉模式 (线程不安全)
 * @author winter
 */
public class Singleton_01 {
    private static Singleton_01 instance;
    private Singleton_01() {
    }
    public static Singleton_01 getInstance(){
        if (null != instance) {
            return instance;
        }
        return new Singleton_01();
    }
}

特点:不能再外部创建,也就是new Singleton_01()。
问题:线程不安全,多个访问者同时获取对象实例,就会造成多个同样的实例并存。

2.懒汉模式 (线程安全)

/**
 * 懒汉模式 (线程安全)
 * @author winter
 */
public class Singleton_02 {

    private static Singleton_02 instance;

    private Singleton_02() {
    }

    public static synchronized Singleton_02 getInstance() {
        if (null != instance) {
            return instance;
        }
        return new Singleton_02();
    }

}

线程安全,直接将锁加在方法上,所有的访问都会抢锁占用,导致资源浪费,效率低,不推荐。

3.饿汉模式 (线程安全)

/**
 * 饿汉模式 (线程安全)
 * @author winter
 */
public class Singleton_03 {

    private static Singleton_03 instance = new Singleton_03();

    private Singleton_03() {
    }

    public static Singleton_03 getInstance() {
        return instance;
    }

}

在启动时候直接运行加载,后续有外部需要使用时直接获取即可,会造成一些内存浪费问题。

4.使用类的内部类 (线程安全)

/**
 * 使用类的内部类 (线程安全)
 * @author winter
 */
public class Singleton_04 {

    private static class SingletonHolder {
        private static Singleton_04 instance = new Singleton_04();
    }

    private Singleton_04() {
    }

    public static Singleton_04 getInstance() {
        return SingletonHolder.instance;
    }

}

既保证了线程安全,又保证了懒汉模式,同时不会因为加锁而降低性能。比较推荐的一种单例模式。

5.双重锁校验 (线程安全)

/**
 * 双重锁校验 (线程安全)
 * @author winter
 */
public class Singleton_05 {

    private static Singleton_05 instance;

    private Singleton_05() {
    }

    public static Singleton_05 getInstance(){
       if(null != instance) return instance;
       synchronized (Singleton_05.class){
           if (null == instance){
               instance = new Singleton_05();
           }
       }
       return instance;
    }

}

实现了对方法级锁的优化,减少了获取实例的耗时,也满足了懒汉模式。

6.CAS (线程安全)

/**
 * CAS (线程安全)
 * @author winter
 */
public class Singleton_06 {

    private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();

    private static Singleton_06 instance;

    private Singleton_06() {
    }

    public static final Singleton_06 getInstance() {
        for (; ; ) {
            Singleton_06 instance = INSTANCE.get();
            if (null != instance) return instance;
            INSTANCE.compareAndSet(null, new Singleton_06());
            return INSTANCE.get();
        }
    }
    
}

在线程竞争不激烈时,相对其他锁的实现,没有线程的切换和阻塞也没有了额外的开销,并且可以支持较大的并发。但在在线程竞争激烈时,CAS会一直自旋。

7.枚举单例 (线程安全)

/**
 * 枚举单例 (线程安全)
 * @author winter
 */
public enum Singleton_07 {
    INSTANCE;
    public void doSomething() {
        System.out.println("doSomething");
    }
    //调用方式
    public static void main(String[] args) {
        Singleton_07.INSTANCE.doSomething();
    }
}

这种方式解决了线程安全、自由穿行化和单一实例问题,并且非常简洁,是Joshua.J.Bloch推荐的单例模式。

总结

单例是设计模式中最简单的一种模式,但在各种的实现上却需要用到java的基本功,也是面试中高频率问到一种设计模式,如 1. 请写出一种高效安全的单例模式? 2. 懒汉模式是线程安全吗?为什么?

观察者模式

文章目录

前言

本篇主要介绍spring封装后的观察者模式的实际应用,在写作的过程中作者会尽量控制篇幅,有任何有问题的地方请帮忙指出,谢谢。

需求

电商项目中,用户购买商品(订单支付成功)后,同时还得给用户加积分、写流水和其他操作。

接下来会用观察者模式实现该业务。

观察者模式

1.定义

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

MQ就是标准按照观察者模式设计的中间件。

2.优缺点

优点:

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。

缺点:

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

3.spring观察者结构

  1. Event 事件,相当于是消息体。
  2. Listener 监听者,观察者角色,相当于是消息消费者。
  3. Publisher 发送者,被观察者角色,相当于消息生产者。

非常类似于MQ。

4.应用场景

  1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  3. 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
  4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

设计模式实现

1.Event 事件

/**
 *  Event事件
 * @author winter
 */
@Component
public class OrderEvent extends ApplicationEvent {

    public OrderEvent(ApplicationContext source) {
        super(source);
    }

    public String getOrderId() {
        return "o1234";
    }
}

2.Listener 监听者

/**
 *  Listener监听者 处理积分
 * @author winter
 */
@Component
public class IntegralListener implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent orderEvent) {
        String message = orderEvent.getOrderId();
        System.out.println("用户支付订单:"+message+"成功,用户积分+1");
    }
}
/**
 *  Listener监听者 处理账号的流水
 * @author winter
 */
@Component
public class AccountListener implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent orderEvent) {
        String message = orderEvent.getOrderId();
        System.out.println("用户支付订单:"+message+"成功,添加用户流水");
    }
}

3.Listener 监听者

/**
 * 订单创建的发送者
 * @author winter
 */
@Component
public class OrderPublisher implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 发布事件
     * 监听该事件的监听者都可以获取消息
     *
     * @param orderEvent
     */
    public void publisherEvent(OrderEvent orderEvent) {
        //订单下发成功
        System.out.println("订单:"+ orderEvent.getOrderId()+"支付成功。。。");
        applicationContext.publishEvent(orderEvent);
    }
}

4.测试

@SpringBootTest
class ObserveApplicationTests {
    @Autowired
    private OrderPublisher orderPublisher;
    @Autowired
    private OrderEvent orderEvent;
    @Test
    void contextLoads() {
        orderPublisher.publisherEvent(orderEvent);
    }

}

测试结果:
订单:o1234支付成功。。。
用户支付订单:o1234成功,添加用户流水
用户支付订单:o1234成功,用户积分+1

总结

类似的业务场景还是推荐使用MQ,如果受资源限制还是可以使用观察者模式来设计业务。

代理模式

文章目录


前言

装饰模式在生活中随处可见,如蛋炒饭中可以加咸菜、香肠、牛肉等,但不管怎么加其他配菜,它都还是蛋炒饭;还有车子为了美观贴膜、手机为了防摔、房子装修、相片加相框、王者荣耀中李白的各种皮肤等等,都是装饰者模式。
本篇要介绍的就是装饰者设计模式

实战需求

在订单提交的时候,订单价格和结算价格其实是两码事,订单价格是当前商品成交价格,而结算价格是用户最终需要 支付的金额,最终支付的金额并不是一成不变,它也并不是商品成交价格,能改变结算价格的因素很多,比如满100 减10元,VIP用户再减5块。

装饰者模式

定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

模式结构:

  1. 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  2. 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  3. 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  4. 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

装饰器模式的结构图:
在这里插入图片描述

模式实现功能

抽象构件:

/**
 *定义计算订单金额的方法
 * @author winter
 */
public interface GoodsSum {

    /**
     * 订单金额计算
     * @param order
     */
    void sum (Order order);
}

具体构件:

/**
 * 订单金额计算类
 * @author winter
 */
@Service("orderMoneySum")
public class OrderMoneySum implements GoodsSum {
    @Override
    public void sum(Order order) {
        //商品单价*总数量
        order.setPayMoney(order.getItem().getPrice()*order.getNum());
        order.setMoney(order.getItem().getPrice()*order.getNum());
    }
}

抽象装饰:

/**
 * 创建装饰者类
 * @author winter
 */
public class DecoratorMoneySum implements GoodsSum {

    private GoodsSum goodsSum;

    public void setMoneySum(GoodsSum goodsSum) {
        this.goodsSum = goodsSum;
    }

    @Override
    public void sum(Order order) {
        goodsSum.sum(order);
    }
}

具体装饰 :

/**
 * 满减价格计算 (增强)
 * @author winter
 */
@Component
public class FullMoneySum extends DecoratorMoneySum {

    /**
     * 原来的功能上进行增强
     * @param order
     */
    @Override
    public void sum(Order order) {
        //原有功能
        super.sum(order);
        //增强
        moneySum(order);
    }

    //满100减10块
    public void moneySum(Order order) {
        Double paymoney = order.getPayMoney();
        if (paymoney >= 100) {
            order.setPayMoney(paymoney - 10);
        }
    }

}
/**
 * VIP价格计算 (增强)
 * @author winter
 */
@Component
public class VipMoneySum extends DecoratorMoneySum {

    /**
     * 原来的功能上进行增强
     * @param order
     */
    @Override
    public void sum(Order order) {
        //原有功能
        super.sum(order);
        //增强
        vipMoneySum(order);
    }

    /**
     * Vip价格优惠-5
     * @param order
     */
    public void vipMoneySum(Order order){
        order.setPayMoney(order.getPayMoney()-5);
    }
}

客户端:

/**
 * 测试
 * @author winter
 */
@RequestMapping("/test")
@RestController
public class DecorateController {

    @Autowired
    private GoodsSum orderMoneySum;

    @Autowired
    private FullMoneySum fullMoneySum;

    @Autowired
    private VipMoneySum vipMoneySum;

    @PostMapping("/sum")
    public Double sumOrder(@RequestBody Order order){
        //满减
        fullMoneySum.setMoneySum(orderMoneySum);
        fullMoneySum.sum(order);
        System.out.println("满减后金额:"+order.getPayMoney());

        //VIP优惠
        vipMoneySum.setMoneySum(fullMoneySum);
        vipMoneySum.sum(order);
        System.out.println("VIP优惠后金额:"+order.getPayMoney());
        return order.getPayMoney();
    }
}

测试结果:
在这里插入图片描述
在这里插入图片描述

总结

优点:

  1. 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;
  2. 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果;
  3. 装饰器模式完全遵守开闭原则。
    缺点:
    装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。

面试题:代理模式和装饰模式的区别?
代理模式,在原主题业务上新增额外功能;装饰模式,根据各种不同主题业务上来增强。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ariel小葵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值