文章目录
- 一、定义:依赖倒置原则
- 二、模拟场景:依赖倒置原则
- 三、违背方案:依赖倒置原则
-
- [3.1 工程结构](#3.1 工程结构)
- [3.2 抽奖系统](#3.2 抽奖系统)
-
- [**3.2.1 定义抽奖用户类**](#3.2.1 定义抽奖用户类)
- [3.2.2 抽奖控制](#3.2.2 抽奖控制)
- [3.3 单元测试](#3.3 单元测试)
- 四、改善代码:依赖倒置原则
-
- [4.1 工程结构](#4.1 工程结构)
- [4.2 抽奖控制改善](#4.2 抽奖控制改善)
-
- [4.2.1 定义抽奖用户类](#4.2.1 定义抽奖用户类)
- [4.2.2 抽奖接口](#4.2.2 抽奖接口)
- [4.2.3 随机抽奖实现](#4.2.3 随机抽奖实现)
- [4.2.4 权重抽奖实现](#4.2.4 权重抽奖实现)
- [4.2.5 创建抽奖服务](#4.2.5 创建抽奖服务)
- [4.3 单元测试](#4.3 单元测试)
一、定义:依赖倒置原则
- 依赖倒置原则 :
Dependence Inversion Principle,DIP
。- 在设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象 。抽象不应该依赖于细节,细节应该依赖于抽象。
- 依赖倒置原则是实现开闭原则的重要途经之一,它降低了类之间的耦合,提高了系统的稳定性和可维护性,同时这样的代码一般更易读,且便于传承。
二、模拟场景:依赖倒置原则
- 在互联网的营销活动中,经常为了拉新和促活,会做一些抽奖活动。这些抽奖活动的规则会随着业务的不断发展而调整。
- 如随机抽奖、权重抽奖等。其中权重是指用户在当前系统中的一个综合排名,比如活跃度、贡献度等。
- 模拟出抽奖的一个系统服务。
- 如果是初次搭建这样的系统会怎么实现呢?
- 这个系统是否有良好的扩展性和可维护性,同时在变动和新增业务时测试的复杂度是否高?
三、违背方案:依赖倒置原则
3.1 工程结构
java
design-1.6-0
|------src
|------main
|--java
|--com.lino.design
|--BetUser.java
|--DrawControl.java
|------test
|--java
|--com.lino.design.test
|--ApiTest.java
3.2 抽奖系统
3.2.1 定义抽奖用户类
BetUser.java
java
package com.lino.design;
/**
* @description: 投注用户
*/
public class BetUser {
/**
* 用户姓名
*/
private String userName;
/**
* 用户权重
*/
private int userWeight;
public BetUser() {
}
public BetUser(String userName, int userWeight) {
this.userName = userName;
this.userWeight = userWeight;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getUserWeight() {
return userWeight;
}
public void setUserWeight(int userWeight) {
this.userWeight = userWeight;
}
}
- 这个类就是一个普通的对象类,其中包括了用户姓名和对应的权重,方便满足不同的抽奖方式。
3.2.2 抽奖控制
DrawControl.java
java
package com.lino.design;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @description: 抽奖控制
*/
public class DrawControl {
/**
* 随机抽取指定数量的用户,作为中奖用户
*
* @param list 用户列表
* @param count 中奖用户数量
* @return 中奖用户列表
*/
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;
}
}
- 在一个类中实现两种不同的抽奖逻辑。随机抽奖和权重抽奖。
3.3 单元测试
ApiTest.java
java
@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));
}
- 在初始化数据后分别调用两个接口方法进行测试。
测试结果
java
14:50:54.496 [main] INFO com.lino.design.test.ApiTest - 随机抽奖,中奖用户名单:[{"userWeight":89,"userName":"笨笨"},{"userWeight":10,"userName":"丑蛋"},{"userWeight":65,"userName":"花花"}]
14:50:54.527 [main] INFO com.lino.design.test.ApiTest - 权重抽奖,中奖用户名单:[{"userWeight":89,"userName":"笨笨"},{"userWeight":72,"userName":"小白"},{"userWeight":65,"userName":"花花"}]
- 从测试结果上看,程序没有问题,验证结果正常。但是这样实现有什么问题呢?
- 如果程序是一次性的、几乎不变的,那么不考虑很多的扩展性和可维护性因素。
- 但如果这些程序具有不确定性,或者当业务发展时需要不断地调整和新增,那么这样的实现方式就很不友好了。
- 首先,这样的实现方式扩展起来很麻烦,每次扩展都需要新增接口,同时对于调用方来说需要新增调用接口的代码。
- 其次,对于这个服务类来说,随着接口数量的增加,代码行数会不断地暴增,最后难以维护。
四、改善代码:依赖倒置原则
4.1 工程结构
java
design-1.6-1
|------src
|------main
|--java
|--com.lino.design
|--BetUser.java
|--DrawControl.java
|--DrawRandom.java
|--DrawWeightRank.java
|--IDraw.java
|------test
|--java
|--com.lino.design.test
|--ApiTest.java
4.2 抽奖控制改善
4.2.1 定义抽奖用户类
BetUser.java
java
package com.lino.design;
/**
* @description: 投注用户
*/
public class BetUser {
/**
* 用户姓名
*/
private String userName;
/**
* 用户权重
*/
private int userWeight;
public BetUser() {
}
public BetUser(String userName, int userWeight) {
this.userName = userName;
this.userWeight = userWeight;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getUserWeight() {
return userWeight;
}
public void setUserWeight(int userWeight) {
this.userWeight = userWeight;
}
}
- 这个类就是一个普通的对象类,其中包括了用户姓名和对应的权重,方便满足不同的抽奖方式。
4.2.2 抽奖接口
IDraw.java
java
package com.lino.design;
import java.util.List;
/**
* @description: 抽奖接口
*/
public interface IDraw {
/**
* 抽奖
*
* @param list 用户列表
* @param count 中奖数量
* @return 中奖用户列表
*/
List<BetUser> prize(List<BetUser> list, int count);
}
- 定义一个抽奖接口,接口中包括了需要传输的
list
集合,以及中奖用户数量。
4.2.3 随机抽奖实现
DrawRandom.java
java
package com.lino.design;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @description: 随机抽奖
*/
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;
}
}
4.2.4 权重抽奖实现
DrawWeightRank.java
java
package com.lino.design;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 权重抽奖
*/
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;
}
}
4.2.5 创建抽奖服务
DrawControl.java
java
package com.lino.design;
import java.util.List;
/**
* @description: 抽奖控制
*/
public class DrawControl {
/**
* 抽奖接口
*/
private IDraw draw;
/**
* 发起抽奖
*
* @param draw 抽奖类型
* @param betUserList 用户列表
* @param count 中奖数量
* @return 中奖的用户列表
*/
public List<BetUser> doDraw(IDraw draw, List<BetUser> betUserList, int count) {
return draw.prize(betUserList, count);
}
}
- 这个类中提现了依赖倒置的重要性,可以把任何一种抽奖逻辑传递给这个类。
- 这样实现的好处是可以不断地扩展,但是不需要在外部新增调用接口,降低了一套代码的维护成本,并提高了可扩展性及可维护性。
4.3 单元测试
ApiTest.java
java
@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));
}
测试结果
java
15:06:03.242 [main] INFO com.lino.design.test.ApiTest - 随机抽奖,中奖用户名单:[{"userWeight":72,"userName":"小白"},{"userWeight":43,"userName":"豆豆"},{"userWeight":10,"userName":"丑蛋"}]
15:06:03.273 [main] INFO com.lino.design.test.ApiTest - 权重抽奖,中奖用户名单:[{"userWeight":89,"userName":"笨笨"},{"userWeight":72,"userName":"小白"},{"userWeight":65,"userName":"花花"}]
- 这里与前面代码唯一不同的是新增了实现抽奖的入参
new DrawRandom()
、new DrawWeightRank()
。- 在这两个抽奖的功能逻辑作为入参后,扩展起来会非常的方便。
- 以这种抽象接口为基准搭建起来的框架结构会更加稳定,算程已经建设好,外部只需要实现自己的算子即可,最终把算子交给算程处理。