设计模式 - 六大设计原则之DIP(依赖倒置原则)

Posted 小小工匠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式 - 六大设计原则之DIP(依赖倒置原则)相关的知识,希望对你有一定的参考价值。

文章目录


概述

设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。 抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置原则是实现开闭原则的重要途径之一, 它降低了类之间的耦合,提高了系统的稳定性和可维护性。


Case

抽奖系统服务


Bad Impl

先用最直接的方式,即按照不同的抽奖逻辑定义出不同的接口,让外部服务调用

【抽奖用户类】

public class BetUser 

    private String userName;  // 用户姓名
    private int userWeight;   // 用户权重

    public BetUser() 
    

    public BetUser(String userName, int userWeight) 
        this.userName = userName;
        this.userWeight = userWeight;
    

   // set get 


普通对象,包括了姓名和权重 。

接下来实现两种不同的抽奖逻辑,在一个类中用两个接口实现

public class DrawControl 

    // 随机抽取指定数量的用户,作为中奖用户
    public List<BetUser> doDrawRandom(List<BetUser> list, int count) 
        // 集合数量很小直接返回
        if (list.size() <= count) return list;
        // 乱序集合
        Collections.shuffle(list);
        // 取出指定数量的中奖用户
        List<BetUser> prizeList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) 
            prizeList.add(list.get(i));
        
        return prizeList;
    

    // 权重排名获取指定数量的用户,作为中奖用户
    public List<BetUser> doDrawWeight(List<BetUser> list, int count) 
        // 按照权重排序
        list.sort((o1, o2) -> 
            int e = o2.getUserWeight() - o1.getUserWeight();
            if (0 == e) return 0;
            return e > 0 ? 1 : -1;
        );
        // 取出指定数量的中奖用户
        List<BetUser> prizeList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) 
            prizeList.add(list.get(i));
        
        return prizeList;
    



包含了两个接口,一个随机抽奖,另外一个按照权重排序

【单元测试】

 @Test
    public void test_DrawControl()
        List<BetUser> betUserList = new ArrayList<>();
        betUserList.add(new BetUser("花花", 65));
        betUserList.add(new BetUser("豆豆", 43));
        betUserList.add(new BetUser("小白", 72));
        betUserList.add(new BetUser("笨笨", 89));
        betUserList.add(new BetUser("丑蛋", 10));

        DrawControl drawControl = new DrawControl();
        List<BetUser> prizeRandomUserList = drawControl.doDrawRandom(betUserList, 3);
        logger.info("随机抽奖,中奖用户名单:", JSON.toJSON(prizeRandomUserList));

        List<BetUser> prizeWeightUserList = drawControl.doDrawWeight(betUserList, 3);
        logger.info("权重抽奖,中奖用户名单:", JSON.toJSON(prizeWeightUserList));
    

从测试结果上没啥问题。

但如果考虑扩展性的话,就不太友好了。 每次扩展都需要新增接口,同时对于调用方来说需要新增调用的接口代码。

其次,说着接口数量的增加,代码行数就会爆炸,难以维护。


Better Impl

为了良好的扩展性,可以采用依赖倒置、面向抽象编程的方式实现

首先定义抽奖接口,任何一个实现方都可实现自己的抽奖逻辑。

【抽奖接口】

public interface IDraw 

    // 获取中奖用户接口
    List<BetUser> prize(List<BetUser> list, int count);



【随机抽象实现】

public class DrawRandom implements IDraw 

    @Override
    public List<BetUser> prize(List<BetUser> list, int count) 
        // 集合数量很小直接返回
        if (list.size() <= count) return list;
        // 乱序集合
        Collections.shuffle(list);
        // 取出指定数量的中奖用户
        List<BetUser> prizeList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) 
            prizeList.add(list.get(i));
        
        return prizeList;
    



【权重抽奖实现】

public class DrawWeightRank implements IDraw 

    @Override
    public List<BetUser> prize(List<BetUser> list, int count) 
        // 按照权重排序
        list.sort((o1, o2) -> 
            int e = o2.getUserWeight() - o1.getUserWeight();
            if (0 == e) return 0;
            return e > 0 ? 1 : -1;
        );
        // 取出指定数量的中奖用户
        List<BetUser> prizeList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) 
            prizeList.add(list.get(i));
        
        return prizeList;
    




【创建抽奖服务】

public class DrawControl 

    private IDraw draw;

    public List<BetUser> doDraw(IDraw draw, List<BetUser> betUserList, int count) 
        return draw.prize(betUserList, count);
    



这个类中体现了依赖倒置的重要性,可以把任何一种抽奖逻辑传递给这个类。 这样的好处是可以不断的扩展,但是不需要在外部新增调用接口,降低了一套代码的维护成本,提高了可扩展性和可维护性。

这里的重点是把实现逻辑的接口作为参数参数

【单元测试】

   @Test
    public void test_DrawControl() 

        List<BetUser> betUserList = new ArrayList<>();
        betUserList.add(new BetUser("花花", 65));
        betUserList.add(new BetUser("豆豆", 43));
        betUserList.add(new BetUser("小白", 72));
        betUserList.add(new BetUser("笨笨", 89));
        betUserList.add(new BetUser("丑蛋", 10));

        DrawControl drawControl = new DrawControl();
        List<BetUser> prizeRandomUserList = drawControl.doDraw(new DrawRandom(), betUserList, 3);
        logger.info("随机抽奖,中奖用户名单:", JSON.toJSON(prizeRandomUserList));

        List<BetUser> prizeWeightUserList = drawControl.doDraw(new DrawWeightRank(), betUserList, 3);
        logger.info("权重抽奖,中奖用户名单:", JSON.toJSON(prizeWeightUserList));
    

可以看到,入参新增了 new DrawRandom()new DrawWeightRank() 。 在这两个抽奖的功能逻辑作为入参后,扩展起来非常方便。

以这种抽象接口为基准搭建起来的框架会更加的稳定,算程已经建设好,外部只需要实现自己的算子即可,最终把算子交给算程处理。

以上是关于设计模式 - 六大设计原则之DIP(依赖倒置原则)的主要内容,如果未能解决你的问题,请参考以下文章

设计模式 - 六大设计原则之DIP(依赖倒置原则)

「设计模式」六大原则之五:依赖倒置原则小结

「设计模式」六大原则之五:依赖倒置原则小结

「设计模式」六大原则之五:依赖倒置原则小结

「设计模式」六大原则之五:依赖倒置原则小结

六大设计原则DIP依赖倒置原则