搞定设计模式之依赖倒置原则一篇文章就够了!!!

Posted 南淮北安

tags:

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

一、定义

依赖倒置原则(Dependence Inversion Principle,DIP)是指在设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。

抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置原则是实现开闭原则的重要途径之一,它降低了类之间的耦合,提高了系统的稳定性和可维护性,同时这样的代码一般更易读,且便于传承。

二、示例

在互联网的营销活动中,经常为了拉新和促活,会做一些抽奖活动。这些抽奖活动的规则会随着业务的不断发展而调整,如随机抽奖、权重抽奖等。
其中,权重是指用户在当前系统中的一个综合排名,比如活跃度、贡献度等。

定义抽奖用户:

@Data
public class BetUser {
    private String userName;
    private int userWeight;
}

这个类就是一个普通的对象类,其中包括了用户姓名和对应的权重,方便满足不同的抽奖方式。

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

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 temp = o2.getUserWeight() - o1.getUserWeight();
            if (temp == 0) {
                return 0;
            }
            return temp > 0 ? 1 : -1;
        });
        // 取出指定数量的中奖用户
        List<BetUser> prizeList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            prizeList.add(list.get(i));
        }
        return prizeList;
    }
}

在这个抽奖逻辑类中包括了两个接口,一个是随机抽奖,另一个是按照权重排序。

如果程序是一次性的、几乎不变的,那么可以不考虑很多的扩展性和可维护性因素;
但如果这些程序具有不确定性,或者当业务发展时需要不断地调整和新增,那么这样的实现方式就很不友好了。

首先,这样的实现方式扩展起来很麻烦,每次扩展都需要新增接口,同时对于调用方来说需要新增调用接口的代码。

其次,对于这个服务类来说,随着接口数量的增加,代码行数会不断地暴增,最后难以维护。

接口即方法

三、问题改进

既然上述方式不具备良好的扩展性,那么用依赖倒置、面向抽象编程的方式实现。

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

抽奖接口:

这里只有一个抽奖接口,接口中包括了需要传输的 list 集合,以及中奖用户数量。

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 temp = o2.getUserWeight() - o1.getUserWeight();
            if (temp == 0) {
                return 0;
            }
            return temp > 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> betUsers, int count) {
        return draw.prize(betUsers, count);
    }
}

在这个类中体现了依赖倒置的重要性,可以把任何一种抽奖逻辑传递给这个类。

这样实现的好处是可以不断地扩展,但是不需要在外部新增调用接口,降低了一套代码的维护成本,并提高了可扩展性及可维护性。

另外,这里的重点是把实现逻辑的接口作为参数传递,在一些框架源码中经常会有这种做法。

具体调用

List<BetUser> prizeControl = new DrawControl();
//调用随机抽奖,将随机抽奖作为参数传递
List<BetUser> prizeRandomUserList = drawControl.doDraw(new DrawRandom(),betUserList,3);
//调用权重抽奖,将权重抽奖作为参数传递
List<BetUser> prizeWeightUserList = drawControl.doDraw(new DrawWeightRank(),betUserList,3);

以上是关于搞定设计模式之依赖倒置原则一篇文章就够了!!!的主要内容,如果未能解决你的问题,请参考以下文章

搞定设计模式之迪米特法则一篇文章就够了!!!

搞定里式替换原则的设计模式一篇文章就够了!!!

搞定开闭原则的设计模式一篇文章就够了!!!

搞定设计模式之工厂模式一篇文章就够了!!!

搞定单一职责的设计模式一篇文章就够了!!!

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