基本问题
1. 最常用的模式 和 概念
模板 + 策略 + 工厂
模板,策略,工厂(交给spring),桥接,观察者(重要)
解耦 和 职责边界分离的设计原则
定义抽象的模板,提供数据支撑的继承和
- 对应策略的配置以及调用处理。
也可以直接是工厂 + 策略的组合。
概念
设计模式的概念最早是由 克里斯托佛·亚历山大
在其著作 《建筑模式语言》
中首次提出的。
本书介绍了城市设计的 “语言”,提供了253个描述城镇、邻里、住宅、花园、房间及西部构造的模式, 而此类 “语言” 的基本单元就是模式。后来,埃里希·伽玛
、 约翰·弗利赛德斯
、 拉尔夫·约翰逊
和 理查德·赫尔姆
这四位作者接受了模式的概念。 1994 年, 他们出版了 《设计模式: 可复用面向对象软件的基础》
一书, 将设计模式的概念应用到程序开发领域中。
目的
设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成
- 解决问题的一些思路。
- 通过设计模式可以帮助我们增强代码的
- 可重用性、
- 可扩充性、
- 可维护性、
- 灵活性好。
- 我们使用设计模式最终的目的是实现代码的
- 高内聚、
- 低耦合、
- 可扩展、
- 可复用。
2. 分3类 和 具体名称
https://blog.csdn.net/guorui_java/article/details/104026988
基本名字 和 说明
创建型 模式 (5种)
-
√ 工厂方法
- 多种类型商品不同接口,统一发奖 服务搭建场景
-
√ 抽象工厂
-
替换Redis双集群 升级,代理类 抽象场景
-
使用 代理类 提供抽象工厂 替换redis,集群升级。
-
-
生成器
- 各项装饰 物料组合 套餐 选配 场景
-
原型
- 上机考试 多套试(题?),每人题目和答案 乱序排列 场景
-
√ 单例
- 7种 单例 模式 案例,Effective Java 作者推荐枚举 单例模式
- 工抽 生 原单
结构型 模式(7种)
-
√ 适配器
- 从 多个 MQ 消息体中,抽取指定字段值 场景
-
桥接
- 多个支付去掉(微信,支付宝)与 多支付模式(刷脸,指纹)场景
-
√ 组合
- 营销差异化 人群发券,决策树引擎搭建场景
- 组合模式 搭建决策树,处理营销 差异化的 发放优惠券
-
装饰
- SSO 单点登录功能 扩展,增强 拦截用户 访问方法 范围场景
-
外观
- 基于Spring Boot 开发 门面模式 中间件,统一 控制接口 白名单 场景
-
享原(享元)
- 基于Redis 秒杀,提供活动 与 库存 信息 查询场景
-
√ 代理
- 模拟 mybatis-spring中 定义Dao接口,使用代理类 方式,操作数据库 原理 实现场景
-
适桥 组装 外 享代
行为型 模式(10种)
-
责任链
- 模拟618电商 大促销 期间,项目上线 流程 多级负责人 审批 场景
-
命令
- 模拟高档餐厅,八大菜系,小二点单 厨师烹饪 场景
-
迭代器
- 模拟公司 组织 架构树 结构关系,深度迭代 遍历 人员 信息 输出场景
-
中介者
- 按照Mybatis 原理,手写 ORM框架,给JDBC方式操作数据库,增加多个中介者 场景
-
备忘录
- 模拟互联网 系统上线过程中,配置文件回滚 场景
-
观察者
- 模拟类似 小客车 指标 摇号过程,监听消息 通知用户中签 场景
-
状态
- 模拟系统营销活动,状态流程 审核发布上线 场景
-
√ 策略
- 模拟多种 营销类型 优惠券,折扣金额 计算策略 场景
-
√ 模板方法
- 模拟爬虫 各类电商商品,生成营销推广 海报场景
- 抽奖流程 标准化,模板模式
-
√ 访问者
- 模拟家长 与 校长,对学生 和 老是的 不同视角信息,访问场景。
-
还有作者忘掉的 解释器 模式
-
责 命 迭 中 备 观 状 策 模 访
-
责命 迭中备 观状策 模仿
解释
1、创建型模式
对象实例化的模式,创建型模式用于解耦对象的实例化过程。
单例模式:某个类智能有一个实例,提供一个全局的访问点。
工厂模式:一个工厂类根据传入的参量决定创建出哪一种产品类的实例。
抽象工厂模式:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的创建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
2、结构型模式
把类或对象结合在一起形成一个更大的结构。
装饰器模式:动态的给对象添加新的功能。
代理模式:为其它对象提供一个代理以便控制这个对象的访问。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
适配器模式:将一个类的方法接口转换成客户希望的另一个接口。
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
享元模式:通过共享技术来有效的支持大量细粒度的对象。
3、行为型模式
类和对象如何交互,及划分责任和算法。
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
观察者模式:对象间的一对多的依赖关系。
仲裁者模式:用一个中介对象来封装一系列的对象交互。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
访问者模式:不改变数据结构的前提下,增加作用于一组对象元素的新功能。
图解
关系
3. 7个设计 原则
3、依赖倒转原则:一流公司制定标准,皇帝写好圣旨,太监念,自有人告诉皇帝事情办成了
4、里式代换原则:长江后浪推前浪,后浪的我,前浪的都会
6、迪米特法则:各人自扫门前雪
- 单接 依里 开笛
- 依里 接单 开奥迪
解释
1、单一职责原则
对于一个类,只有一个引起该类变化的原因;该类的职责是唯一的,且这个职责是唯一引起其他类变化的原因。
2、接口隔离原则
客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
3、依赖倒转原则
依赖倒转原则是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
4、里式代换原则
任何基类可以出现的地方,子类一定可以出现。里氏代换原则是继承复用的基石,只有当衍生类可以替换基类,软件单位的功能不受影响时,基类才能真正的被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
5、开闭原则
(1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
(2)对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。
6、迪米特法则
迪米特法则又叫做最少知识原则,就是说一个对象应当对其它对象又尽可能少的了解,不和陌生人说话。
7、合成复用原则
合成复用原则要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
设计模式七大原则总结(超详细)
4. 感想
一共有23种设计模式,可以说都是为了提高代码的
- 可读性、可扩展性、可复用性、类的可替换性、组件化、可移植性等等特性。
- 通过接口、抽象类、继承、实现、委托、抽象、面向接口编程、多态、重载、重写等方式使得代码的这些特性得以彰显,
- 可以说只有深刻的理解了这些概念背后的哲学思想才能更好的理解设计模式。
- 在设计模式中有很多思想,
- 比如可以使用委托的不要使用继承、开闭原则,
- 面向扩展开放,面向修改关闭,
- 里式代换原则,父类一定能被子类代替并使用,反置则不然,
- 面向接口编程,功能层次 和 实现层次分离(桥接模式)、
- 高内聚低耦合等思想,
- 使得代码的 更新换代的时候能够尽可能少的甚至不用修改之前的代码,直接加入新的内容。
- 提高软件的开发周期,便于维护和升级,便于查找和纠错,易于扩展和使用。
有的设计模式内部其实是使用了别的设计模式作为支撑的
回顾
从迭代器开始,我们将类中数据结构的遍历和类的功能实现分离出来,本质上使用了工厂模式;
其次我们学习了适配器模式,它将不同的接口进行适配,从而便于版本的兼容性以及其他功能;
然后我们学习了模板方法,使用模板面向抽象编程,便于新的子类的实现和管理;
之后学习了工厂模式,其实借用了模板模式来创建产品,是一种非常重要用处很广的一种方法;
然后我们学习了单例模式,有懒汉式、饿汉式等,生成关于某个类全局唯一的对象,注意多线程的影响;
之后是原型模式,用来复制复杂的对象,使用了clone方法,然后是builder模式,用一个新的类对已有的抽象接口进行整合和编程,从而构建出我们想要的东西;
然后是抽象工厂模式,使用了工厂模式,组合模式等模式,面向抽象编程,将抽象零件组装成抽象产品,便于具体工厂的创建,提高了代码的组件化和复用性;
然后是桥接模式,将类的功能层次和实现层次分割开来,便于对应的扩展和使用;
然后是策略模式,可以整体的替换策略,使用也很广泛;然后是组合模式,保证了同根同源,通过委托添加自己构成递归,树形结构,将具有树形特点的对象组合起来;
然后是装饰器模式,和组合模式的结构类似,同样是递归结构,从而可以不断的装饰,增加新的功能,很好用;
接着是visitor访问者模式,通过在类外访问类中的数据结构从而得到想要的结果,便于程序的可扩展性和组件化;
接着是责任链模式,推卸责任,根据问题的大小来考虑自己释放处理,本质是链表,便于职责分明;
然后是外观模式,通过整合各个类之间的调用关系,组建成了统一的接口(API),便于外部类的调用;
接着是仲裁者模式,将很多类之间互相关联的关系交给仲裁者处理,省去了各个类之间的嵌套和调动,有利于高内聚和低耦合,思路清晰,便于扩展;
然后是观察者模式,通过互相委托从而能够在被观察的类发生改变的时候得到相应的改变的信息并且处理;
然后是备忘录模式,通过在某一时刻的状态保存下来,便于恢复,在游戏中使用的比较多;
然后是状态模式,将状态当做类,从而职责分明,解除了很多繁琐的if和else这些分支逻辑,便于扩展;
然后是享元模式,轻量级对象,通过共用不变对象来实现;
然后是代理模式,懒加载真正的服务器,加快访问速度,代理是帮助服务器代理的;
然后是命令模式,将命令当做类,通过保存一些列命令,从而能够随时执行这些命令,需要清除命令的本质就是一些操作和数据;
最后是解释器模式,利用编程原理的方法,来更高层次的封装代码,将自己开发的java代码当做编译系统,从而不用改变java代码只修改更高语言层次的代码就能实现不同的功能。
正式开始
1. 单一职责
照着流程写
public void serveGrade(String userType){
if ("VIP用户".equals(userType)){
System.out.println("VIP用户,视频1080P蓝光");
} else if ("普通用户".equals(userType)){
System.out.println("普通用户,视频720P超清");
} else if ("访客用户".equals(userType)){
System.out.println("访客用户,视频480P高清");
}
}
具体的实现
public interface IVideoUserService {
void definition();
void advertisement();
}
public class VipVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("VIP用户,视频1080P蓝光");
}
public void advertisement() {
System.out.println("VIP会员,视频无广告");
}
}
//这里是 访客用户,有广告
IVideoUserService guest = new GuestVideoUserService();
guest.advertisement();
guest.definition();
2. 开闭原则
已有接口和实现
public interface ICalculationArea {
/**
* 计算面积,长方形
*
* @param x 长
* @param y 宽
* @return 面积
*/
double rectangle(double x, double y);
/**
* 计算面积,三角形
* @param x 边长x
* @param y 边长y
* @param z 边长z
* @return 面积
*
* 海伦公式:S=√[p(p-a)(p-b)(p-c)] 其中:p=(a+b+c)/2
*/
double triangle(double x, double y, double z);
/**
* 计算面积,圆形
* @param r 半径
* @return 面积
*
* 圆面积公式:S=πr²
*/
double circular(double r);
}
public class CalculationArea implements ICalculationArea {
private final static double π = 3.14D;
public double rectangle(double x, double y) {
return x * y;
}
public double triangle(double x, double y, double z) {
double p = (x + y + z) / 2;
return Math.sqrt(p * (p - x) * (p - y) * (p - z));
}
public double circular(double r) {
return π * r * r;
}
}
开闭原则 扩展
public class CalculationAreaExt extends CalculationArea {
private final static double π = 3.141592653D;
@Override
public double circular(double r) {
return π * r * r;
}
}
ICalculationArea area = new CalculationAreaExt();
double circular = area.circular(10);
System.out.println(circular);
3. 里式替换
- 继承必须确保 超类所拥有的性质
- 在子类中 依然成立
正常写代码
父类 储蓄卡
- 有 提现,充值,查询流水 的功能
public class CashCard {
private Logger logger = LoggerFactory.getLogger(CashCard.class);
/**
* 提现
*
* @param orderId 单号
* @param amount 金额
* @return 状态码 0000成功、0001失败、0002重复
*/
public String withdrawal(String orderId, BigDecimal amount) {
// 模拟支付成功
logger.info("提现成功,单号:{} 金额:{}", orderId, amount);
return "0000";
}
/**
* 储蓄
*
* @param orderId 单号
* @param amount 金额
*/
public String recharge(String orderId, BigDecimal amount) {
// 模拟充值成功
logger.info("储蓄成功,单号:{} 金额:{}", orderId, amount);
return "0000";
}
/**
* 交易流水查询
* @return 交易流水
*/
public List<String> tradeFlow() {
logger.info("交易流水查询成功");
List<String> tradeList = new ArrayList<String>();
tradeList.add("100001,100.00");
tradeList.add("100001,126.00");
return tradeList;
}
}
子类 信用卡
public class CreditCard extends CashCard {
private Logger logger = LoggerFactory.getLogger(CashCard.class);
//生成贷款单,类似 提现
@Override
public String withdrawal(String orderId, BigDecimal amount) {
// 校验
if (amount.compareTo(new BigDecimal(1000)) >= 0){
logger.info("贷款金额校验(限额1000元),单号:{} 金额:{}", orderId, amount);
return "0001";
}
// 模拟生成贷款单
logger.info("生成贷款单,单号:{} 金额:{}", orderId, amount);
// 模拟支付成功
logger.info("贷款成功,单号:{} 金额:{}", orderId, amount);
return "0000";
}
//生成还款单,类似 充值
@Override
public String recharge(String orderId, BigDecimal amount) {
// 模拟生成还款单
logger.info("生成还款单,单号:{} 金额:{}", orderId, amount);
// 模拟还款成功
logger.info("还款成功,单号:{} 金额:{}", orderId, amount);
return "0000";
}
//查询流水
@Override
public List<String> tradeFlow() {
return super.tradeFlow();
}
}
使用规则
提取父类
public abstract class BankCard {
private Logger logger = LoggerFactory.getLogger(BankCard.class);
private String cardNo; // 卡号
private String cardDate; // 开卡时间
//全参 构造
public BankCard(String cardNo, String cardDate) {
this.cardNo = cardNo;
this.cardDate = cardDate;
}
//定义抽象方法
abstract boolean rule(BigDecimal amount);
// 正向入账,+ 钱
public String positive(String orderId, BigDecimal amount) {
// 入款成功,存款、还款
logger.info("卡号{} 入款成功,单号:{} 金额:{}", cardNo, orderId, amount);
return "0000";
}
// 逆向入账,- 钱
public String negative(String orderId, BigDecimal amount) {
// 入款成功,存款、还款
logger.info("卡号{} 出款成功,单号:{} 金额:{}", cardNo, orderId, amount);
return "0000";
}
/**
* 交易流水查询
*
* @return 交易流水
*/
public List<String> tradeFlow() {
logger.info("交易流水查询成功");
List<String> tradeList = new ArrayList<String>();
tradeList.add("100001,100.00");
tradeList.add("100001,126.00");
return tradeList;
}
//获取卡号
public String getCardNo() {
return cardNo;
}
//获取日期
public String getCardDate() {
return cardDate;
}
}
储蓄卡 super.xx接口()
public class CashCard extends BankCard {
private Logger logger = LoggerFactory.getLogger(CashCard.class);
public CashCard(String cardNo, String cardDate) {
super(cardNo, cardDate);
}
boolean rule(BigDecimal amount) {
return true;
}
/**
* 提现
*
* @param orderId 单号
* @param amount 金额
* @return 状态码 0000成功、0001失败、0002重复
*/
public String withdrawal(String orderId, BigDecimal amount) {
// 模拟支付成功
logger.info("提现成功,单号:{} 金额:{}", orderId, amount);
return super.negative(orderId, amount);
}
/**
* 储蓄
*
* @param orderId 单号
* @param amount 金额
*/
public String recharge(String orderId, BigDecimal amount) {
// 模拟充值成功
logger.info("储蓄成功,单号:{} 金额:{}", orderId, amount);
return super.positive(orderId, amount);
}
/**
* 风险校验
*
* @param cardNo 卡号
* @param orderId 单号
* @param amount 金额
* @return 状态
*/
public boolean checkRisk(String cardNo, String orderId, BigDecimal amount) {
// 模拟风控校验
logger.info("风控校验,卡号:{} 单号:{} 金额:{}", cardNo, orderId, amount);
return true;
}
}
信用卡 继承储蓄卡 依然调用父类的功能
- 信用卡 不破坏储蓄卡的功能
public class CreditCard extends CashCard {
private Logger logger = LoggerFactory.getLogger(CreditCard.class);
public CreditCard(String cardNo, String cardDate) {
super(cardNo, cardDate);
}
boolean rule2(BigDecimal amount) {
return amount.compareTo(new BigDecimal(1000)) <= 0;
}
/**
* 提现,信用卡贷款
*
* @param orderId 单号
* @param amount 金额
* @return 状态码
*/
public String loan(String orderId, BigDecimal amount) {
boolean rule = rule2(amount);
if (!rule) {
logger.info("生成贷款单失败,金额超限。单号:{} 金额:{}", orderId, amount);
return "0001";
}
// 模拟生成贷款单
logger.info("生成贷款单,单号:{} 金额:{}", orderId, amount);
// 模拟支付成功
logger.info("贷款成功,单号:{} 金额:{}", orderId, amount);
return super.negative(orderId, amount);
}
/**
* 还款,信用卡还款
*
* @param orderId 单号
* @param amount 金额
* @return 状态码
*/
public String repayment(String orderId, BigDecimal amount) {
// 模拟生成还款单
logger.info("生成还款单,单号:{} 金额:{}", orderId, amount);
// 模拟还款成功
logger.info("还款成功,单号:{} 金额:{}", orderId, amount);
return super.positive(orderId, amount);
}
}
进行测试
@Test
public void test_bankCard() {
logger.info("里氏替换前,CashCard类:");
//储蓄卡的功能
CashCard bankCard = new CashCard("6214567800989876", "2022-03-05");
// 提现
bankCard.withdrawal("100001", new BigDecimal(100));
// 储蓄
bankCard.recharge("100001", new BigDecimal(100));
//储蓄卡内 存的是 子类,信用卡
logger.info("里氏替换后,CreditCard类:");
CashCard creditCard = new CreditCard("6214567800989876", "2022-03-05");
// 提现
creditCard.withdrawal("100001", new BigDecimal(1000000));
// 储蓄
creditCard.recharge("100001", new BigDecimal(100));
}
4. 迪米特 原则
意义在于 降低类 之间的耦合。
- 由于每个对象 尽量减少对 其他对象的了解,因此
- 很容易使得系统的功能模块 对应独立
- 相互之间,不存在 (或很少有)依赖关系
学生类
public class Student {
private String name; // 学生姓名
private int rank; // 考试排名(总排名)
private double grade; // 考试分数(总分)
//无参构造
//全参构造
//set 和 get 方法
}
不使用规则
老师类
public class Teacher {
private String name; // 老师名称
private String clazz; // 班级
private static List<Student> studentList; // 学生
//无参
public Teacher() {
}
//2个参数构造
public Teacher(String name, String clazz) {
this.name = name;
this.clazz = clazz;
}
//学生赋值
static {
studentList = new ArrayList<>();
studentList.add(new Student("花花", 10, 589));
studentList.add(new Student("蛋蛋", 19, 502));
}
//返回list
public static List<Student> getStudentList() {
return studentList;
}
//返回 name
public String getName() {
return name;
}
//返回 class
public String getClazz() {
return clazz;
}
}
校长类
public class Principal {
private Teacher teacher = new Teacher("丽华", "3年1班");
// 查询班级信息,总分数、学生人数、平均值
public Map<String, Object> queryClazzInfo(String clazzId) {
// 获取班级信息;学生总人数、总分、平均分
int stuCount = clazzStudentCount();
double totalScore = clazzTotalScore();
double averageScore = clazzAverageScore();
// 组装对象,实际业务开发会有对应的类
Map<String, Object> mapObj = new HashMap<>();
mapObj.put("班级", teacher.getClazz());
mapObj.put("老师", teacher.getName());
mapObj.put("学生人数", stuCount);
mapObj.put("班级总分数", totalScore);
mapObj.put("班级平均分", averageScore);
return mapObj;
}
// 总分。遍历
public double clazzTotalScore() {
double totalScore = 0;
//遍历 每一个学生,加载一起
for (Student stu : Teacher.getStudentList()) {
totalScore += stu.getGrade();
}
return totalScore;
}
// 平均分
public double clazzAverageScore(){
double totalScore = 0;
//求出 总分
for (Student stu : Teacher.getStudentList()) {
totalScore += stu.getGrade();
}
// 除 学生的数量,得出平均分
return totalScore / Teacher.getStudentList().size();
}
// 班级人数。 返回学生的人数
public int clazzStudentCount(){
return Teacher.getStudentList().size();
}
}
Principal principal = new Principal();
Map<String, Object> map = principal.queryClazzInfo("3年1班");
logger.info("查询结果:{}", JSON.toJSONString(map));
查询结果:{"学生人数":5,"班级平均分":510.2,"班级":"3年1班","老师":"丽华","班级总分数":2551.0}
使用规则
老师类,提供 总分 平均分 人数
public class Teacher {
private String name; // 老师名称
private String clazz; // 班级
private static List<Student> studentList; // 学生
static {
studentList = new ArrayList<>();
studentList.add(new Student("花花", 10, 589));
studentList.add(new Student("蛋蛋", 19, 502));
}
// 总分
public double clazzTotalScore() {
double totalScore = 0;
for (Student stu : studentList) {
totalScore += stu.getGrade();
}
return totalScore;
}
// 平均分
public double clazzAverageScore(){
double totalScore = 0;
for (Student stu : studentList) {
totalScore += stu.getGrade();
}
return totalScore / studentList.size();
}
// 班级人数
public int clazzStudentCount(){
return studentList.size();
}
}
校长类 使用老师的数据
public class Principal {
private Teacher teacher = new Teacher("丽华", "3年1班");
// 查询班级信息,总分数、学生人数、平均值
public Map<String, Object> queryClazzInfo(String clazzId) {
// 获取班级信息;学生总人数、总分、平均分
int stuCount = teacher.clazzStudentCount();
double totalScore = teacher.clazzTotalScore();
double averageScore = teacher.clazzAverageScore();
// 组装对象,实际业务开发会有对应的类
Map<String, Object> mapObj = new HashMap<>();
mapObj.put("班级", teacher.getClazz());
mapObj.put("老师", teacher.getName());
mapObj.put("学生人数", stuCount);
mapObj.put("班级总分数", totalScore);
mapObj.put("班级平均分", averageScore);
return mapObj;
}
}
5. 接口隔离
要求程序员 尽量将 臃肿庞大的接口 拆分为 更小的和更具体的接口,
让接口中 只包含 客户感兴趣的方法。
不使用原则
定义接口
public interface ISkill {
// 射箭
void doArchery();
// 隐袭
void doInvisible();
// 沉默
void doSilent();
// 眩晕
void doVertigo();
}
具体的英雄
public class HeroHouYi implements ISkill{
@Override
public void doArchery() {
System.out.println("后裔的灼日之矢");
}
@Override
public void doInvisible() {
System.out.println("后裔的隐身技能");
}
@Override
public void doSilent() {
System.out.println("后裔的沉默技能");
}
@Override
public void doVertigo() {
// 无此技能的实现
}
}
public class HeroLianPo implements ISkill{
@Override
public void doArchery() {
// 无此技能的实现
}
@Override
public void doInvisible() {
System.out.println("廉颇的隐身技能");
}
@Override
public void doSilent() {
System.out.println("廉颇的沉默技能");
}
@Override
public void doVertigo() {
System.out.println("廉颇的眩晕技能");
}
}
使用规则 拆分接口
拆分后的接口
public interface ISkillArchery {
// 射箭
void doArchery();
}
public interface ISkillInvisible {
// 隐袭
void doInvisible();
}
public interface ISkillSilent {
// 沉默
void doSilent();
}
public interface ISkillVertigo {
// 眩晕
void doVertigo();
}
英雄的实现
public class HeroHouYi implements ISkillArchery, ISkillInvisible, ISkillSilent {
@Override
public void doArchery() {
System.out.println("后裔的灼日之矢");
}
@Override
public void doInvisible() {
System.out.println("后裔的隐身技能");
}
@Override
public void doSilent() {
System.out.println("后裔的沉默技能");
}
}
6. 依赖倒置原则
高层模块,不应该依赖于 底层模块。
-
二者应该依赖:接口 或 抽象类
-
上级需要下级办事,而不是到人家家门口,(皇帝,圣旨给太监)
上级 依赖于 下级
- 改成:上级指定标准,
- 下级(依赖于上级说的话)做完后,
- 交上来(倒置)
解释:
- 程序要 依赖于 抽象接口,
- 不要依赖于 具体实现
- 要求对 抽象进行编程,不要对 实现进行编程
- 降低了:客户与实现模块的耦合
基本用户类
public class BetUser {
private String userName; // 用户姓名
private int userWeight; // 用户权重
} //无参构造,全参构造,get set
不使用规则
抽奖的实现
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) -> {
//o2权重 - o1 权重
int e = o2.getUserWeight() - o1.getUserWeight();
// == 0,返回0
if (0 == e) return 0;
//大于0,返回1
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 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);
}
}
进行抽奖测试
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));
开始了,参考下一个文档
产品需求文档 Product Requirements Document,PRD
所求皆如愿,所行化坦途