一、单一职责原则
1、官方定义:
单一职责原则,英文缩写SRP,全称 Single Responsibility Principle。
《在架构整洁之道》一书中关于单一职责原则的描述是这样的:一个类
或者模块只负责一个职责(或者功能)
2、通俗解释:
一个类只负责一个职责或者功能;也就是说在类的设计中,我们不要设计
大而全的类,而是要设计粒度小功能单一的类。
3、作用
1)实现类的高内聚
2)实现代码的高内聚、低耦合
4、如何判断一个类的职责是否单一?
可以基于下边几个方向来判断类的职责是否单一,下边是不满足单一职责的4种情况:
1)类中的代码行数、属性个数或函数过多,这时需要思考一下该类的职责是否单一
2)类依赖的其他类过多
3)类中私有方法过多
4)类中大量的方法总是操作类中的几个属性
二、开闭原则
1、官方定义
一般认为最早提出开闭原则(Open-Close Principle,简称OCP)的是伯特兰-迈耶。
他在1988年发表的《面向对象软件构造》中给出的。
面向对象编程领域中。开闭原则规定软件中的对象、类、模块和函数 对扩展应该是
开放的,但对于修改是封闭的。这意味着应该用抽象定义结构,用具体实现扩展细节
,以此确保软件系统开发和维护过程的可靠性。
2、通俗解释
开闭原则就是 对扩展开放,对修改关闭;
开闭原则并不是完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发
3、开闭原则优点(作用):
1)新老逻辑解耦,需求发生改变不会影响老业务逻辑
2)改动成本小,只需要增加新逻辑,不需要改动老逻辑(不一定,但最好小
范围修改)
3)提供代码的稳定性和可扩展性
4、开闭原则场景示例
示例代码如下:
/**
* 定义通讯协议接口
*/
public interface ReportProtocol {
/**
* 解释保温
*/
void msgExplain();
/**
* 编制报文
*/
void msgCompile();
/**
* 校验报文
*/
void msgCheck();
}
/*******************************************************
* 5266通讯协议
*
*******************************************************/
public class ReportProtocol5266 implements ReportProtocol{
@Override
public void msgExplain() {
System.out.println("++++++++++++++++解释报文 5266 ++++++++++++++++++++");
}
@Override
public void msgCompile() {
System.out.println("++++++++++++++++编制报文 5266 ++++++++++++++++++++");
}
@Override
public void msgCheck() {
System.out.println("++++++++++++++++校验报文 5266 ++++++++++++++++++++");
}
}
/*******************************************************
* 现在要对旧的5266协议进行升级,但又不能影响老的业务?
* 只需要新建一个类,实现协议接口 ReportProtocol,然后在调用方的类型是接口类型就可以了
*
*******************************************************/
public class ReportProtocol5269 implements ReportProtocol{
@Override
public void msgExplain() {
System.out.println("++++++++++++++++解释报文 5269 ++++++++++++++++++++");
}
@Override
public void msgCompile() {
System.out.println("++++++++++++++++编制报文 5269 ++++++++++++++++++++");
}
@Override
public void msgCheck() {
System.out.println("++++++++++++++++校验报文 5266 ++++++++++++++++++++");
}
}
//调用方 CallContext
public class CallContext {
private ReportProtocol reportProtocol;
/**
* 解释保温
*/
public void msgExplain(){
reportProtocol.msgExplain();
}
/**
* 编制报文
*/
public void msgCompile(){
reportProtocol.msgCompile();
}
/**
* 校验报文
*/
public void msgCheck(){
reportProtocol.msgCheck();
}
public void setReportProtocol(ReportProtocol reportProtocol) {
this.reportProtocol = reportProtocol;
}
}
4、总结:
开闭原则是所有设计模式最核心的目标,通过开闭原则我们应该学习程序设计中的
顶层思维,大致方向如下:
1)抽象意识
2)封装意识
3)扩展意思
三、里氏替换原则
1、官方定义
里氏替换原则(Liskov Substitution Principle,简称 LSP)是由麻省理工学院计算机科学
系教授芭芭拉-利斯科夫于1987年在“面向对象技术的高峰会议”(OOPSLA)上发表的一篇
论文《数据抽象和层次》里提出的。
她在论文中提到:如果S是T的子类型,对于S类型的任意对象,如果将他们看作是T类型
对象,则对象的行为也理应与期望一致。
2、通俗解释
里氏替换原则就是:子类对象能够替换父类对象出现的任何地方,并且
保证原来程序的逻辑行为不变及正确性不被破坏。
要理解里氏替换原则,其实就要理解2个问题:
1)什么是替换
替换的前提是面向对象语言所支持的多太特性,同一个行为具有多个不同表现
形式或形态的能力。
简单的说:就是当一个方法的参数是接口类型时,这个方法可以接收所有实现过
这个接口的实现类。
2)什么是与期望行为一致的替换?
在不了解派生类的情况下,仅通过接口或基类的方法,即可清楚的知道方法的行
为,而不管哪种派生类的实现,都与接口或基类方法的期望行为一致。
4、里氏替换原则作用
1)为良好的继承定义了一个规范;
2)提高代码的健壮性,降低程序出错的可能性。
5、里氏替换与多太的区别?
1)多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法,是一种代码
实现的思路。
2)里氏替换是一种设计原则,用来指导继承关系中子类该如何设计;在替换父类的时 侯,不改变原有程序的逻辑及不破坏原有程序的正确性。
6、场景示例
在商城项目中,退出了三种促销方式:
1)满减活动、2百以上打八折
2)打折活动,全场9折
3)返现活动,消费超1000,返200
请设计一个计算接口以及实现类,该接口的结算方法能够对用户的最终消费金额
进行一个计算。计算结果需要满足上边多种促销方式。
示例代码如下:
/**
* 商品促销接口
*/
public interface Istraegy {
double realPrice(double consumer);
}
//促销分类,每个实现类表示一种促销
/*******************************************************
* 满减活动、2百以上打八折
*
*******************************************************/
public class PromotionnalStraegy implements Istraegy{
@Override
public double realPrice(double consumer) {
return Double.compare(consumer,200)>=0? consumer*0.8:consumer;
}
}
/*******************************************************
* 打折活动,全场9折
*******************************************************/
public class RebateStraegy implements Istraegy{
@Override
public double realPrice(double consumer) {
return consumer*0.9;
}
}
/*******************************************************
* 返现活动,消费超1000,返200
*
*******************************************************/
public class ReduceStraegy implements Istraegy{
@Override
public double realPrice(double consumer) {
return Double.compare(consumer,1000)>= 0? consumer-200:consumer;
}
}
/*******************************************************
*
* 消费者调用方
*******************************************************/
public class CallContext {
private Istraegy straegy;
/**
* 计算并返回最终费用
* @param price
* @return
*/
public double culPrice(double price){
//使用具体的商品促销策略获得实际消费金额
double culPrice = this.straegy.realPrice(price);
//格式化金额,保留小数点后2位
BigDecimal decimal = new BigDecimal(culPrice);
decimal.setScale(2,BigDecimal.ROUND_CEILING);
return decimal.doubleValue();
}
public void setStraegy(Istraegy straegy) {
this.straegy = straegy;
}
}
四、接口隔离原则(ISP)
1、官方定义
《代码整洁之道》作者罗伯特 C-马丁 为 “接口隔离原则” 的定义是:客户端不应该被迫
依赖于它不使用的方法(Clients should not be forced to depend on methods they do
not use)
接口隔离原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上。
2、通俗解释
上边2个定义的含义用一句话概括就是:要为各个类建立他们需要的专用接口,而不要
试图去建立一个很庞大的接口供所有依赖他的类去调用。
3、场景示例
微服务用户系统提供了一组跟用户相关的API给其他系统使用,比如:注册、登录、获取
用户信息等。
现在有个需求:后台管理系统要实现删除用户的功能,希望用户系统提供一个删除用户的
接口;应该如何设计这个接口?
UML类图,描述:类与类、类与接口之间的关系
3.1)方式1:直接在原来的用户接口 UserService 中增加一个删除用户方法del,如
代码如下所示:
/**
* 接口隔离原则
*/
public interface UserService {
/**
* 用户注册
* @param cellphone
* @param password
* @return
*/
boolean register(String cellphone,String password);
/**
* 用户登录
* @param cellphone
* @param password
* @return
*/
boolean login(String cellphone,String password);
/**
* 根据用户id查询
* @param userId
* @return
*/
UserInfo getUserInfoByUserId(Long userId);
/**
* 根据 cellphone 查询
* @param userId
* @return
*/
UserInfo getUserInfoByCellPhone(String cellphone);
/*
删除用户
*/
boolean delUserByUserId(Long userId);
boolean delUserByCellPhone(String cellphone);
}
/*******************************************************
* 接口隔离原则测试
*
*******************************************************/
public class UserServiceImpl implements UserService{
@Override
public boolean register(String cellphone, String password) {
return false;
}
@Override
public boolean login(String cellphone, String password) {
return false;
}
@Override
public UserInfo getUserInfoByUserId(Long userId) {
return null;
}
@Override
public UserInfo getUserInfoByCellPhone(String cellphone) {
return null;
}
@Override
public boolean delUserByUserId(Long userId) {
return false;
}
@Override
public boolean delUserByCellPhone(String cellphone) {
return false;
}
}
3.2)方式2:遵循接口隔离原则,为依赖接口的类定制服务,只是提供调用者需要的
方法,屏蔽不需要的方法
代码如下所示:
/**
* 接口隔离原则
*/
public interface UserService {
/**
* 用户注册
* @param cellphone
* @param password
* @return
*/
boolean register(String cellphone,String password);
/**
* 用户登录
* @param cellphone
* @param password
* @return
*/
boolean login(String cellphone,String password);
/**
* 根据用户id查询
* @param userId
* @return
*/
UserInfo getUserInfoByUserId(Long userId);
/**
* 根据 cellphone 查询
* @param userId
* @return
*/
UserInfo getUserInfoByCellPhone(String cellphone);
}
/**
* 接口隔离原则测试
* 为依赖接口的类定制服务,只是提供调用者需要的方法,屏蔽不需要的方法
*/
public interface RestrictedUserService {
/*
删除用户
*/
boolean delUserByUserId(Long userId);
boolean delUserByCellPhone(String cellphone);
}
public class UserServiceImpl implements UserService,RestrictedUserService {
}
3.3)在代码设计之初,我们可以根据数据是否更新数据,把用户接口拆分成更新接口和
查询接口,更新接口包含用户注册(新增)、修改和删除方法;查询接口包含 用户
登录、用户信息查询等查询相关的功能,示例代码如下:
/**
* 接口隔离原则测试
* 对 接口 UserService 进行拆分,拆分成2个接口:用户修改的接口 UserUpdateService 和 用户查询的接口 UserInfoService;
*
* UserUpdateService 包含 用户注册、用户删除方法
* 后边若有用户修改方法也可以放在这个接口中
*/
public interface UserUpdateService {
/**
* 用户注册
* @param cellphone
* @param password
* @return
*/
boolean register(String cellphone,String password);
//修改
boolean update(UserInfo userInfo);
/*
删除用户
*/
boolean delUserByUserId(Long userId);
boolean delUserByCellPhone(String cellphone);
}
/**
* 接口隔离原则测试
* 对 接口 UserService 进行拆分,拆分成2个接口:用户修改的接口 UserUpdateService 和 用户查询的接口 UserInfoService;
*
* UserInfoService 包含 用户登录和用户查询
*
*/
public interface UserInfoService {
/**
* 用户登录
*/
boolean login(String cellphone,String password);
/**
* 根据用户id查询
*/
UserInfo getUserInfoByUserId(Long userId);
/**
* 根据 cellphone 查询
*/
UserInfo getUserInfoByCellPhone(String cellphone);
}
/*******************************************************
* 接口隔离原则测试
* 对 接口 UserService 进行拆分,拆分成2个接口:用户修改的接口 UserUpdateService 和 用户查询的
* 接口 UserInfoService;
*
*
*******************************************************/
public class UserServiceImpl implements UserUpdateService,UserInfoService{
}
4、接口隔离原则的优势(作用):
1)将胖接口分解成多个粒度小的接口,可以提高系统的灵活性和可维护性
2)使用多个专门的接口,还能体现出对象的层次
3)能够减少项目中冗余的代码
5、接口隔离原则与单一职责之间的区别?
1)单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
2)单一职责原则主要是约束类,他针对的是程序中的实现细节;
接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
五、依赖倒置原则
1、官方定义
依赖倒置原则是罗伯特 C-马丁于1996年在C++Report 上发表的文章中提出的。
依赖倒置原则(Deependence Inversion Principle,简称DIP)是指在设计代码架构时,
高层模块不应该依赖于低层模块,二则都应该依赖于抽象。抽象不应该依赖于细节,
细节应该依赖于抽象。
2、通俗描述
依赖倒置原则想要表达的是:由于在软件设计中,细节具有多变性,而抽象层则相对
稳定,因此以抽象为基础搭建起来的架构 要 比以细节为基础搭建起来的架构 要稳定
的多。
在java语言中的表现就是:
1)模块之间的依赖通过抽象发生,实现类之间不直接发生依赖关系,实现类之间
的依赖关系是通过接口或抽象类产生的。
2)接口或抽象类不依赖于实现类。
3)实现类依赖接口或抽象类。
所以依赖倒置原则又称之为 “面向接口编程”。
3、场景示例
假设我们要组装一台电脑,需要的配件有cpu、硬盘、内存条。cpu 有很多选择,如:
Intel、AMD等,硬盘可以选择希捷、西部数据等,内存条可以选择金士顿、长江和
三星等等。该如何设计?
UML类图如下:
示例代码如下:
/**
* 依赖倒置原则测试
* 电脑的组成之一CPU
*/
public interface Cpu {
void run();
}
/**
* 依赖倒置原则测试
* 电脑的组成之一硬盘
*/
public interface HardDisk {
void set(String data);
String get();
}
/**
* 依赖倒置原则测试
* 电脑的组成之一内存条
*/
public interface Memory {
void run();
}
public class IntelCpu implements Cpu{
@Override
public void run() {
System.out.println("++++++++++++++++++IntelCpu run +++++++++++++++++++++++");
}
}
public class XiJieHardDisk implements HardDisk{
@Override
public void set(String data) {
System.out.println("=========================== set =============================");
}
@Override
public String get() {
System.out.println("=========================== get =============================");
return null;
}
}
public class KingstonMemory implements Memory{
@Override
public void run() {
System.out.println("++++++++++++++++++ KingstonMemory run ++++++++++++++++++++");
}
}
/*******************************************************
* 电脑与cpu、硬盘disk、内存条memory 之间是组合关系
*******************************************************/
public class Computer {
private Cpu cpu;
private HardDisk disk;
private Memory memory;
public Computer(){}
public Computer(Cpu cpu, HardDisk disk, Memory memory) {
this.cpu = cpu;
this.disk = disk;
this.memory = memory;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
public void setDisk(HardDisk disk) {
this.disk = disk;
}
public void setMemory(Memory memory) {
this.memory = memory;
}
public void run(String data){
cpu.run();
disk.set(data);
memory.run();
disk.get();
}
}
4、依赖倒置原则、控制反转和依赖注入之间的区别
4.1)依赖倒置是一种然健的设计原则,主要用来指导框架层面的设计。
4.2)控制反转是一种框架设计常用的模式,但不是具体的方法。
控制反转的控制是对程序执行流程的一种的控制;反转指的是在
使用框架之前程序员自己控制程序的执行流程,使用框架后程序执行
交给框架控制。
控制反转指的是流程控制的反转。
4.3)依赖注入,是控制反转的一种具体实现,是一种具体的编码技巧。
六、迪米特法则(LKP)
1、官方定义
迪米特法则是1987年由美国Northeastern University的 Ian Holland(伊恩 霍兰德)提出,
被UML创始人之一Booch(布奇)等人普及。
迪米特法则又叫最少知识原则(Least Knowledge Principle,简称:LKP),指的是一个
类或者模块对其他 类或模块 了解的越少越好。
2、通俗描述
简单的说 迪米特法则想要表达的思想就是:不该有直接依赖关系之间的类之间,不要
有依赖;有依赖关系的类之间,尽量只依赖必要的接口。
迪米特法则又可理解为 类或模块与其他 类或模块 之间互不了解,通过中间人的方式
将他们组合起来,从而实现弱耦合
注意:在项目中不要过多的使用迪米特法则,否则可能会产生大量的“中间类”,从而
加重代码的维护成本
3、场景示例
娱乐明细wang,由于全身心投入艺术,所有日常事务由经纪人负责处理,如与粉丝
见面会、和媒体公司业务洽谈等等。这里的经济人是明星的朋友,粉丝和媒体公司
是陌生人,所以适用迪米特法则。
用UML图描述上边明星、经纪人,粉丝/媒体公司之间的关系
经纪人与 明星、粉丝和媒体公司是聚合关系,经纪人将明星、粉丝和媒体公司三者
聚合在一起。示例代码如下:
/*******************************************************
*
* 明星类
*
*******************************************************/
@Data
public class Star {
private String name;
}
/*******************************************************
* 粉丝类
*
*******************************************************/
@Data
public class Fans {
private String name;
private String address;
}
/*******************************************************
*
* 媒体公司类
*
*******************************************************/
public class Compony {
private String name;
}
/*******************************************************
* 迪米特法则测试
* 经纪人类,
* 经纪人把明星、粉丝和媒体公司整合在一起,Agent相当于一个中介
*
*******************************************************/
@Data
public class Agent {
private Star star;
private Fans fans;
private Compony compony;
//粉丝见面会
public void metting(){
System.out.println(star.getName() +"与"+fans.toString() +"见面");
}
//洽谈合作
public void business(){
System.out.println(star.getName()+"与"+compony.toString()+"洽谈业务");
}
}