JAVA几种常见设计模式

一、工厂模式

1.1、场景解析
  • 定义一个对象,根据子类唯一标识,获取对应的实现进行业务操作
1.2、使用示例
  • 以下策略模式、责任链模式、模板方法模式所用到的对象都会交给spring管理,并定义工厂类对外服务。

二、策略模式

2.1、场景解析
  • if...else条件一多,可以封装起来替换。
2.2、解决问题
  • 如果分支变多,代码就会变得臃肿,难以维护,可读性低。
  • 如果你需要新增一种类型,那只能在原有代码上修改。
2.3、专业术语
  • 违背了面向对象的开闭原则和单一责任原则

开闭原则(对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码。

单一原则(规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。

2.4、使用示例
2.4.1 逻辑分析
  • 定义一个接口或者抽象类,里面存放两部分:一部分是唯一类型指定,另一部分是该类型相关业务逻辑实现。
2.4.2 场景解析
  • 每个人在社会某个时间段某个领域都有相应的角色以及相对应的工作;如学生和程序员,学生需要读书,而程序需要打码。
2.4.3 代码实现
  • 策略接口对象
public interface IPersonStrategy {
    // 类型
    PersonRoleEnum type();
    // 业务逻辑
    void work();
}
  • 实现
@Slf4j
@Component
public class StudentStrategy implements IPersonStrategy {

    @Override
    public PersonRoleEnum type() {
        return PersonRoleEnum.STUDENT;
    }

    @Override
    public void work() {
        log.info("学生要努力学习");
    }
}

@Slf4j
@Component
public class ProgrammerStrategy implements IPersonStrategy{
    @Override
    public PersonRoleEnum type() {
        return PersonRoleEnum.PROGRAMMER;
    }

    @Override
    public void work() {
        log.info("程序员要努力打码");
    }
}
  • 工厂
@Component
public class PersonStrategyFactory {

    private Map<PersonRoleEnum, IPersonStrategy> iPersonStrategyMap = Maps.newConcurrentMap();

    public void work(PersonRoleEnum personRoleEnum) {
        Optional.of(iPersonStrategyMap.get(personRoleEnum)).ifPresent(bean -> bean.work());
    }

    @PostConstruct
    public void init() {
        Map<String, IPersonStrategy> beansOfType = SpringUtil.getBeansOfType(IPersonStrategy.class);
        beansOfType.values().forEach(bean -> iPersonStrategyMap.put(bean.type(), bean));
    }
}
  • 使用
@SpringBootTest
public class PersonStrategyUsage {

    @Autowired
    private PersonStrategyFactory personStrategyFactory;

    @Test
    public void test() {
        personStrategyFactory.work(PersonRoleEnum.STUDENT);
        personStrategyFactory.work(PersonRoleEnum.PROGRAMMER);
    }
}
  • 输出
2022-08-11 15:50:14,728 c.j.s.s.i.model.cl.StudentStrategy- 学生要努力学习
2022-08-11 15:50:14,728 c.j.s.s.i.m.cl.ProgrammerStrategy- 程序员要努力打码

三、责任链模式

3.1、场景解析
  • 多个参与者依序处理一个东西。
3.2、解决问题
  • 请求者和接收者完全解耦:在责任链模式中,请求者只需发送请求,无须知道具体的处理逻辑;而每一个接收者只处理自己的部分,无须知道请求者和其它接收者。
  • 动态组合职责:在使用时可以灵活的组合接收对象,也可以根据需要拓展接收对象。
3.3、使用示例
3.3.1 逻辑分析
  • 定义一个接口或者抽象类
  • 实现继承对象差异化处理
  • 处理对象集合初始化(随意组合)
3.3.2 场景解析
  • OA申请:在平时的工作中,难免遇到突发事情需要请假,当请假天数在[1,2]范围时,由部门经理审核;当请假天数在[3,5]范围时,由总经理审核;
    为了简便没行政主管啥事,可自行拓展组合。
3.3.3 代码实现
  • 责任抽象对象
@Data
public abstract class AbstractOAHandler {

    /**
     * 是否需要传递给下个处理者处理
     */
    private AbstractOAHandler nextHandler;

    /**
     * 处理请求,并返回处理结果
     *
     * @param user 请求者
     * @param day  请假天数
     * @return 处理结果
     */
    public abstract String handlerRequest(String user, Integer day);
}
  • 实现
@Slf4j
@Order(1) // 数字越小排越前,为了测试方便
@Component
public class DepartmentManagerOAHandler extends AbstractOAHandler{
    @Override
    public String handlerRequest(String user, Integer day) {
        log.info("经过了部门经理");
        if (day > 0 && day < 3) {
            return String.format("【部门经理】通过了【%s】%d天请假申请!", user, day);
        }else {
            if (Objects.nonNull(this.getNextHandler())) {
                return this.getNextHandler().handlerRequest(user, day);
            }
        }
        return String.format("不通过【%s】%d天请假申请!", user, day);
    }
}

@Slf4j
@Order(0)
@Component
public class GeneralManagerOAHandler extends AbstractOAHandler{
    @Override
    public String handlerRequest(String user, Integer day) {
        log.info("经过了总经理");
        if (day >= 3 && day <= 5) {
            return String.format("【总经理】通过了【%s】%d天请假申请!", user, day);
        }else {
            if (Objects.nonNull(this.getNextHandler())) {
                return this.getNextHandler().handlerRequest(user, day);
            }
        }
        return String.format("不通过【%s】%d天请假申请!", user, day);
    }
}
  • 工厂
@Component
public class OAHandlerFactory {
    @Autowired
    private List<AbstractOAHandler> handlers;
    private AbstractOAHandler handler;

    @PostConstruct
    public void init() {
        for (int i = 0; i < handlers.size(); i++) {
            if (i == 0) {
                handler = handlers.get(0);
            }else {
                handler.setNextHandler(handlers.get(i));
            }
        }
    }
    public String request(String user, Integer day) {
        return handler.handlerRequest(user, day);
    }
}
  • 使用
@SpringBootTest
public class OAHandlerUsage {

    @Autowired
    private OAHandlerFactory oaHandler;

    @Test
    public void test() {
        System.out.println(oaHandler.request("小明", 0));
        System.out.println(oaHandler.request("小明", 1));
        System.out.println(oaHandler.request("小明", 3));
    }
}
  • 输出
2022-08-11 15:45:48,550 c.j.s.s.i.m.z.GeneralManagerOAHandler- 经过了总经理
2022-08-11 15:45:48,550 c.j.s.s.i.m.z.DepartmentManagerOAHandler- 经过了部门经理
不通过【小明】0天请假申请!
2022-08-11 15:45:48,550 c.j.s.s.i.m.z.GeneralManagerOAHandler- 经过了总经理
2022-08-11 15:45:48,550 c.j.s.s.i.m.z.DepartmentManagerOAHandler- 经过了部门经理
【部门经理】通过了【小明】1天请假申请!
2022-08-11 15:45:48,550 c.j.s.s.i.m.z.GeneralManagerOAHandler- 经过了总经理
【总经理】通过了【小明】3天请假申请!

四、模板方法

4.1、场景解析
  • 父类定义骨架,子类可以差异化实现某些细节。
4.2、解决问题
  • 把不变的行为写在父类,去除子类重复代码,提高代码的复用性,符合开闭原则
4.3、使用示例
4.3.1 逻辑分析
  • 定义一个抽象类
  • 继承对象差异化实现抽象方法
4.3.2 场景解析
  • 在我们学习爬取网站商品入库分析时,假如以爬取A|B网站为例,通常A|B网站的商品字段以及请求接口数据是不相同的(授权鉴权等操作忽略),但是我们的整个爬取步骤是相同的。
    如:1、请求接口,拿到商品数据 2、将数据处理成我们需要的格式 3、持久化到数据库。
4.3.3 代码实现
  • 模板方法抽象对象
@Slf4j
public abstract class AbstractCrawlTemplate<T> {

    public final void crawl() {

        // 1、请求接口,拿到商品数据
        List<T> list = request();
        log.info("接口请求完成,数据量:{}", list.size());

        // 2、将数据处理成我们需要的格式t
        List<GoodsInfo> goodsInfos = list.stream().map(item -> convert(item)).collect(Collectors.toList());
        log.info("数据转换成功");

        // 3、持久化到数据库
        log.info("持久化成功");
    }

    protected abstract List<T> request();
    protected abstract GoodsInfo convert(T t);
}
  • 实现
@Component("A")
public class ACrawl extends AbstractCrawlTemplate<AGoodsInfo> {
    @Override
    protected List<AGoodsInfo> request() {
        return new ArrayList<AGoodsInfo>() {{
            add(AGoodsInfo.builder().id(1L).name("牛奶").price(BigDecimal.TEN).stock(100).build());
            add(AGoodsInfo.builder().id(1L).name("糖").price(BigDecimal.TEN).stock(100).build());
        }};
    }

    @Override
    protected GoodsInfo convert(AGoodsInfo aGoodsInfo) {
        GoodsInfo goodsInfo = new GoodsInfo();
        goodsInfo.setId("a_" + aGoodsInfo.getId());
        goodsInfo.setName(aGoodsInfo.getName());
        goodsInfo.setPrice(aGoodsInfo.getPrice());
        goodsInfo.setStock(BigDecimal.valueOf(aGoodsInfo.getStock()));
        return goodsInfo;
    }
}

@Component("B")
public class BCrawl extends AbstractCrawlTemplate<BGoodsInfo> {
    @Override
    protected List<BGoodsInfo> request() {
        return new ArrayList<BGoodsInfo>() {{
            add(BGoodsInfo.builder().bh("1").mc("牛奶").jg(12d).kc(10d).build());
        }};
    }

    @Override
    protected GoodsInfo convert(BGoodsInfo bGoodsInfo) {
        GoodsInfo goodsInfo = new GoodsInfo();
        goodsInfo.setId("b_" + bGoodsInfo.getBh());
        goodsInfo.setName(bGoodsInfo.getMc());
        goodsInfo.setPrice(BigDecimal.valueOf(bGoodsInfo.getJg()));
        goodsInfo.setStock(BigDecimal.valueOf(bGoodsInfo.getKc()));
        return goodsInfo;
    }
}
  • 工厂
@Component
public class CrawlTemplateFactory {
    @Resource
    private Map<String, AbstractCrawlTemplate> crawlMap;

    public void exec(WebsiteEnum websiteEnum) {
        crawlMap.get(websiteEnum.name()).crawl();
    }
}
  • 使用
@SpringBootTest
public class CrawlTemplateUsage {

    @Autowired
    private CrawlTemplateFactory crawlTemplateFactory;

    @Test
    public void test() {
        crawlTemplateFactory.exec(WebsiteEnum.A);
    }
}
  • 输出
2022-08-13 16:42:02,069 c.j.s.s.i.m.m.AbstractCrawlTemplate- 接口请求完成,数据量:2
2022-08-13 16:42:02,070 c.j.s.s.i.m.m.AbstractCrawlTemplate- 数据转换成功
2022-08-13 16:42:02,070 c.j.s.s.i.m.m.AbstractCrawlTemplate- 持久化成功

五、单例模式

5.1、场景解析
  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
5.2 常见单列模式
  • 懒汉模式:实例在需要用的时候才会去创建,存在线程安全问题。
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LanHanSingleton {
    private static LanHanSingleton instance;
    public static LanHanSingleton getInstance() {
        if (instance == null) {
            instance = new LanHanSingleton();
        }
        return instance;
    }
}
  • 饿汉模式:实例在初始化的时候已经创建好了。没有线程安全问题,但是浪费内存。
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EHanSingleton {
    private static EHanSingleton instance = new EHanSingleton();
    public static EHanSingleton getInstance() {
        return instance;
    }
}
  • 双重校验锁:综合了懒汉模式和饿汉模式的优缺点,通过在synchronized关键字外加多一层if条件判断,既保证了线程安全也提高了效率、节省了内存。
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instance;
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}
  • 枚举:代码简洁,线程安全
public enum EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容