开闭原则
介绍
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
用抽象构建框架,用实现扩展细节
优点:提高软件系统的可复用性及可维护性
代码样例
类图
课程接口
package top.fjy8018.designpattern.principle.openclose;
import java.math.BigDecimal;
/**
* 课程接口
* 对于面向接口编程,接口应当是稳定的
* 根据开闭原则,在此体现为对接口的增加和修改时关闭的,因为会导致所有实现类的变化
* 而对课程类的扩展(extend)是开放的
*
* @author F嘉阳
* @date 2018-09-18 16:28
*/
public interface Course {
/**
* 获取课程ID
*
* @return
*/
Integer getId();
/**
* 获取课程名称
*
* @return
*/
String getName();
/**
* 获取课程价格
*
* @return
*/
BigDecimal getPrice();
}
课程实现类
package top.fjy8018.designpattern.principle.openclose;
import java.math.BigDecimal;
/**
* 课程实现类
*
* @author F嘉阳
* @date 2018-09-18 16:30
*/
public class JavaCourse implements Course {
private Integer id;
private String name;
private BigDecimal price;
public JavaCourse(Integer id, String name, BigDecimal price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public Integer getId() {
return this.id;
}
@Override
public String getName() {
return this.name;
}
@Override
public BigDecimal getPrice() {
return this.price;
}
@Override
public String toString() {
return "JavaCourse{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price.setScale(2, BigDecimal.ROUND_HALF_UP) +
'}';
}
}
Java课程促销类
package top.fjy8018.designpattern.principle.openclose;
import java.math.BigDecimal;
/**
* Java课程促销
* 根据开闭原则:对于扩展是开放的,对于修改是关闭的
* 故通过继承方式实现
*
* @author F嘉阳
* @date 2018-09-18 16:43
*/
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, BigDecimal price) {
super(id, name, price);
}
@Override
public Integer getId() {
return super.getId();
}
@Override
public String getName() {
return super.getName();
}
/**
* 实现打折价格逻辑
*
* @return
*/
public BigDecimal getDistcountPrice() {
// BigDecimal推荐使用String构造器,防止精度转换问题
return super.getPrice().multiply(new BigDecimal("0.8"));
}
/**
* 根据里氏替换原则:父类实现的方法不应该覆盖
* 获取原价
*
* @return
*/
@Override
public BigDecimal getPrice() {
return super.getPrice();
}
@Override
public String toString() {
return super.toString();
}
}
测试类
package top.fjy8018.designpattern.principle.openclose;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.io.PrintStream;
import java.math.BigDecimal;
/**
* 开闭原则单元测试
*
* 由于底层通过加锁实现,故生产环境严禁使用{@link PrintStream#println(String)}
*/
@Slf4j
class JavaCourseTest {
/**
* 普通情况
*/
@Test
public void normal() {
Course javaCourse = new JavaCourse(1, "Java课程", new BigDecimal("99"));
log.info("课程Id:{},课程名称:{},价格:{}", javaCourse.getId(), javaCourse.getName(), javaCourse.getPrice());
}
/**
* 促销打折情况
*/
@Test
public void discount() {
Course course = new JavaDiscountCourse(1, "Java课程", new BigDecimal("99"));
JavaDiscountCourse javaCourse = (JavaDiscountCourse) course;
log.info("课程Id:{},课程名称:{},原价:{},折后价格:{}",
javaCourse.getId(), javaCourse.getName(), javaCourse.getPrice(),
javaCourse.getDistcountPrice().setScale(2, BigDecimal.ROUND_HALF_UP));
}
}
依赖倒置原则
介绍
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象
抽象不应该依赖细节;细节应该依赖抽象
针对接口编程,不要针对实现编程
优点:可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险
代码样例
改造前
package top.fjy8018.designpattern.principle.dependenceinversion.before;
import lombok.extern.slf4j.Slf4j;
/**
* 低层模块(模块被调用方)
* <p>
* 此时如果要加一个课程,必须修改该类
* 而对低层模块的修改应该尽量避免
*
* @author F嘉阳
* @date 2018-09-18 17:14
*/
@Slf4j
public class Student {
public void studyJavaCourse() {
log.info("学习Java课程");
}
public void studyPythonCourse() {
log.info("学习Python课程");
}
public void studyVueCourse() {
log.info("学习Vue课程");
}
}
测试类
package top.fjy8018.designpattern.principle.dependenceinversion.before;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* 依赖倒置实现前单元测试
* 高层模块(模块调用方)
*/
class StudentTest {
/**
* 此时存在的问题:
* 如果高层模块要学多一门课程
* 则低层模块{@link Student} 也要对应修改,即存在高层模块依赖低层模块,面向具体实现类编程
*/
@Test
public void study() {
Student student = new Student();
student.studyJavaCourse();
student.studyPythonCourse();
student.studyVueCourse();
}
}
改造后
类图
抽象学习课程接口
package top.fjy8018.designpattern.principle.dependenceinversion.after;
/**
* 学习课程接口
* 接口作为抽象存在,成为一个契约
* 面向抽象编程比面向具体实现细节编程实现简单,灵活度高
*
* @author F嘉阳
* @date 2018-09-18 17:19
*/
public interface DCourse {
void study();
}
具体学习过程接口,java课程
package top.fjy8018.designpattern.principle.dependenceinversion.after;
import lombok.extern.slf4j.Slf4j;
/**
* 课程都面向接口,满足依赖倒置原则:高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象
*
* @author F嘉阳
* @date 2018-09-18 17:19
*/
@Slf4j
public class JavaDCourse implements DCourse {
@Override
public void study() {
log.info("学习Java课程");
}
}
Python课程
package top.fjy8018.designpattern.principle.dependenceinversion.after;
import lombok.extern.slf4j.Slf4j;
/**
* 所有课程实现类后面都不应该修改,添加新课程则增加新实现类,满足开闭原则
*
* @author F嘉阳
* @date 2018-09-18 17:19
*/
@Slf4j
public class PythonDCourse implements DCourse {
@Override
public void study() {
log.info("学习Python课程");
}
}
Vue课程
package top.fjy8018.designpattern.principle.dependenceinversion.after;
import lombok.extern.slf4j.Slf4j;
/**
* @author F嘉阳
* @date 2018-09-18 17:19
*/
@Slf4j
public class VueDCourse implements DCourse {
@Override
public void study() {
log.info("学习Vue课程");
}
}
三种注入方式——参数注入
package top.fjy8018.designpattern.principle.dependenceinversion.after;
/**
* @author F嘉阳
* @date 2018-09-18 17:20
*/
public class Student {
/**
* 具体学习哪个课程,交给高层模块通过参数注入
*
* @param course 课程
*/
public void studyCourse(DCourse course) {
course.study();
}
}
测试类
package top.fjy8018.designpattern.principle.dependenceinversion.after;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* 依赖倒置接口方法实现后单元测试
* 高层模块
*/
class StudentTest {
/**
* 修改后高层不依赖低层模块{@link Student}的实现
* 此时如果高层模块要学多一门课程
* 则不需要修改Student,直接增加一个实现{@link DCourse} 的接口课程即可
* 实现了高层模块与低层模块的解耦
*/
@Test
void study() {
Student student = new Student();
student.studyCourse(new JavaDCourse());
student.studyCourse(new PythonDCourse());
student.studyCourse(new VueDCourse());
}
}
三种注入方式——参数注入
package top.fjy8018.designpattern.principle.dependenceinversion.after;
/**
* 构造器注入课程
*
* @author F嘉阳
* @date 2018-09-18 17:40
*/
public class Student2 {
private DCourse dCourse;
public Student2(DCourse dCourse) {
this.dCourse = dCourse;
}
/**
* 具体学习哪个课程,交给高层模块通过构造器注入
*/
public void studyCourse() {
dCourse.study();
}
}
测试类
package top.fjy8018.designpattern.principle.dependenceinversion.after;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* 依赖倒置构造器实现实现后单元测试
* 高层模块
*/
class Student2Test {
@Test
void study() {
Student2 student = new Student2(new JavaDCourse());
student.studyCourse();
}
}
三种注入方式——setter注入
package top.fjy8018.designpattern.principle.dependenceinversion.after;
/**
* Setter注入课程
*
* @author F嘉阳
* @date 2018-09-18 17:40
*/
public class Student3 {
private DCourse dCourse;
public Student3() {
}
/**
* 具体学习哪个课程,交给高层模块通过构造器注入
*/
public void studyCourse() {
dCourse.study();
}
public void setdCourse(DCourse dCourse) {
this.dCourse = dCourse;
}
}
测试类
package top.fjy8018.designpattern.principle.dependenceinversion.after;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* 依赖倒置Setter实现实现后单元测试
* 高层模块
*/
class Student3Test {
@Test
void study() {
Student3 student = new Student3();
student.setdCourse(new JavaDCourse());
student.studyCourse();
student.setdCourse(new PythonDCourse());
student.studyCourse();
student.setdCourse(new VueDCourse());
student.studyCourse();
}
}
单一职责原则
介绍
定义:不要存在多于一个导致类变更的原因
一个类/接口/方法只负责一项职责
优点:降低类的复杂度、提高类的可读性提高系统的可维护性、降低变更引起的风险
代码样例
改造前
类单一职责
package top.fjy8018.designpattern.principle.singleresponsibility.before;
import lombok.extern.slf4j.Slf4j;
/**
* 单一职责:一个类应该只有一个发生变化的原因
*
* @author F嘉阳
* @date 2018-09-22 10:35
*/
@Slf4j
public class Bird {
/**
* 当前类有两个职责:走和飞
* 没有使用单一职责当业务变更必须修改代码,带来风险
*
* @param name
*/
public void mainMoveMode(String name) {
if ("鸵鸟".equals(name)) {
log.info("{}用脚走", name);
} else {
log.info("{}用翅膀飞", name);
}
// 当边界值越来越多(承担更多职责)的时候变更变得更加困难
}
}
方法单一职责
package top.fjy8018.designpattern.principle.singleresponsibility.before;
/**
* 方法单一职责
*
* @author F嘉阳
* @date 2018-09-22 10:57
*/
public class Method {
private String name;
private Integer age;
/**
* 此时该方法承担两个职责:更新名字和年龄
*
* @param name
* @param age
*/
public void updateUserInfo(String name, Integer age) {
this.name = name;
this.age = age;
}
/**
* 承担多个职责,同时包含可变参数,即不一定更新哪些属性
*
* @param name
* @param properties
*/
public void updateProperties(String name, String... properties) {
// TODO 更新用户名和属性
}
}
接口单一职责
package top.fjy8018.designpattern.principle.singleresponsibility.before;
/**
* 接口单一职责
* 当前接口承担多个职责
*
* @author F嘉阳
* @date 2018-09-22 10:43
*/
public interface SCourse {
/**
* 课程信息相关职责
*/
String getCourseName();
byte[] getCourseVideo();
/**
* 课程管理相关职责
*/
void paid();
void studyCourse();
}
改造后
类单一职责
package top.fjy8018.designpattern.principle.singleresponsibility.after;
import lombok.extern.slf4j.Slf4j;
/**
* 当前类只承担一个职责:飞
*
* @author F嘉阳
* @date 2018-09-22 10:40
*/
@Slf4j
public class FlyBird {
public void mainMoveMode(String name) {
log.info("{}用翅膀飞", name);
}
}
package top.fjy8018.designpattern.principle.singleresponsibility.after;
import lombok.extern.slf4j.Slf4j;
/**
* 当前类只承担一个职责:走
*
* @author F嘉阳
* @date 2018-09-22 10:40
*/
@Slf4j
public class WalkBird {
public void mainMoveMode(String name) {
log.info("{}用脚走", name);
}
}
方法单一职责
package top.fjy8018.designpattern.principle.singleresponsibility.after;
/**
* 方法单一职责
*
* @author F嘉阳
* @date 2018-09-22 10:57
*/
public class Method {
private String name;
private Integer age;
/**
* 此时该方法承担一个职责:更新名字
*
* @param name
*/
public void updateUserName(String name) {
this.name = name;
}
/**
* 只更新年龄
*
* @param age
*/
public void updateUserAge(Integer age) {
this.age = age;
}
/**
* 对于有布尔值的方法也建议拆分
*
* @param name
* @param bool
*/
public void updateProperties(String name, boolean bool) {
if (bool) {
// TODO
} else {
// TODO
}
this.name = name;
}
}
接口单一职责
类图
课程管理相关
package top.fjy8018.designpattern.principle.singleresponsibility.after;
/**
* 只承担课程管理相关职责
*
* @author F嘉阳
* @date 2018-09-22 10:46
*/
public interface CourseManagement {
void paid();
void studyCourse();
}
课程信息相关
package top.fjy8018.designpattern.principle.singleresponsibility.after;
/**
* 一个接口变更,只对一个实现类有影响,对其他接口无关
* 只承担课程信息相关职责
*
* @author F嘉阳
* @date 2018-09-22 10:45
*/
public interface CourseInfo {
String getCourseName();
byte[] getCourseVideo();
}
课程实现类
package top.fjy8018.designpattern.principle.singleresponsibility.after;
import lombok.extern.slf4j.Slf4j;
/**
* 课程实现类
* 接口实现单一职责后可以通过实现类多重实现的方式组合
*
* @author F嘉阳
* @date 2018-09-22 10:46
*/
@Slf4j
public class CourseImpl implements CourseInfo, CourseManagement {
@Override
public String getCourseName() {
return "设计模式课程";
}
@Override
public byte[] getCourseVideo() {
return new byte[0];
}
@Override
public void paid() {
log.info("支付成功");
}
@Override
public void studyCourse() {
log.info("学习课程");
}
}
测试类
package top.fjy8018.designpattern.principle.singleresponsibility.before;
import org.junit.jupiter.api.Test;
import top.fjy8018.designpattern.principle.singleresponsibility.after.FlyBird;
import top.fjy8018.designpattern.principle.singleresponsibility.after.WalkBird;
import static org.junit.jupiter.api.Assertions.*;
class BirdTest {
/**
* 没有使用单一职责情况
*/
@Test
void mainMoveModeBefore() {
Bird bird = new Bird();
bird.mainMoveMode("大雁");
// 业务变更
bird.mainMoveMode("鸵鸟");
}
/**
* 使用单一职责后
* 不同职责调用交由应用层负责
*/
@Test
void mainMoveModeAfter() {
FlyBird flyBird = new FlyBird();
flyBird.mainMoveMode("大雁");
WalkBird walkBird = new WalkBird();
walkBird.mainMoveMode("鸵鸟");
}
}
介绍
定义:用多个专门的接口,而不使用单一的总接口, 客户端不应该依赖它不需要的接口
一个类对一个类的依赖应该建立在最小的接口上 建立单一接口,不要建立庞大臃肿的接口
尽量细化接口,接口中的方法尽量少
注意适度原则,一定要适度
优点:符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。
代码样例
改造前
类图
接口
package top.fjy8018.designpattern.principle.interfacesegregation.before;
/**
* 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
* 旧版:一个接口承担过多功能,使得实现类对有些不需要的方法也必须实现
*
* @author F嘉阳
* @date 2018-09-22 11:40
*/
public interface IAnimalAction {
void eat();
void fly();
void swim();
}
实现类
package top.fjy8018.designpattern.principle.interfacesegregation.before;
/**
* @author F嘉阳
* @date 2018-09-22 11:41
*/
public class Bird implements IAnimalAction {
@Override
public void eat() {
}
@Override
public void fly() {
}
@Override
public void swim() {
// 不需要的接口,但被迫实现
}
}
package top.fjy8018.designpattern.principle.interfacesegregation.before;
/**
* @author F嘉阳
* @date 2018-09-22 11:44
*/
public class Dog implements IAnimalAction {
@Override
public void eat() {
}
@Override
public void fly() {
// 不需要的接口,但被迫实现
}
@Override
public void swim() {
}
}
改造后
类图
接口
package top.fjy8018.designpattern.principle.interfacesegregation.after;
/**
* 隔离接口方法,但注意适度,不是越少越好
*
* @author F嘉阳
* @date 2018-09-22 11:42
*/
public interface IEatAction {
void eat();
}
package top.fjy8018.designpattern.principle.interfacesegregation.after;
/**
* @author F嘉阳
* @date 2018-09-22 11:43
*/
public interface IFlyAction {
void fly();
}
package top.fjy8018.designpattern.principle.interfacesegregation.after;
/**
* @author F嘉阳
* @date 2018-09-22 11:43
*/
public interface ISwimAction {
void swim();
}
实现类
package top.fjy8018.designpattern.principle.interfacesegregation.after;
/**
* 实现类可以实现需要的接口
*
* @author F嘉阳
* @date 2018-09-22 11:43
*/
public class Bird implements IFlyAction, IEatAction {
@Override
public void eat() {
}
@Override
public void fly() {
}
}
package top.fjy8018.designpattern.principle.interfacesegregation.after;
/**
* 接口隔离注重接口方法之间的关系
* 单一职责关心类、方法、接口承担的职责,即一个接口只要职责单一就不关系方法之间的关系
*
* @author F嘉阳
* @date 2018-09-22 11:44
*/
public class Dog implements IEatAction, ISwimAction {
@Override
public void eat() {
}
@Override
public void swim() {
}
}
迪米特原则
介绍
定义:一个对象应该对其他对象保持最少的了解。又叫最少知道原则
尽量降低类与类之间的耦合
优点:降低类之间的耦合
特点:
- 强调只和朋友交流,不和陌生人说话
朋友:
- 出现在成员变量、方法的输入、输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
代码样例
类图
package top.fjy8018.designpattern.principle.demeter;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* 迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),
* 就是说一个对象应当对其他对象有尽可能少的了解
* 和朋友交流,不和陌生人说话
* 朋友指的是出现在类成员变量、方法参数、返回值的对象
*
* @author F嘉阳
* @date 2018-09-22 17:02
*/
@Slf4j
public class Boss {
/**
* 此处朋友为{@link TeamLeader},{@link DCourse}不是类成员变量、方法参数、返回值的对象,所以不应该在类内引用
* 即当前类知道的应该尽可能少
*
* @param teamLeader
*/
public void commandCheckNumber(TeamLeader teamLeader) {
List courses = teamLeader.checkNumberOfCourses();
log.info("课程的数量是:{}", courses.size());
}
}
package top.fjy8018.designpattern.principle.demeter;
/**
* @author F嘉阳
* @date 2018-09-22 17:04
*/
public class DCourse {
}
package top.fjy8018.designpattern.principle.demeter;
import java.util.ArrayList;
import java.util.List;
/**
* @author F嘉阳
* @date 2018-09-22 17:04
*/
public class TeamLeader {
/**
* {@link DCourse}作为方法返回值,属于“朋友”
*
* @return
*/
public List<DCourse> checkNumberOfCourses() {
List<DCourse> courseList = new ArrayList<DCourse>();
for (int i = 0; i < 20; i++) {
courseList.add(new DCourse());
}
return courseList;
}
}
测试类
package top.fjy8018.designpattern.principle.demeter;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class BossTest {
@Test
void commandCheckNumber() {
Boss boss = new Boss();
boss.commandCheckNumber(new TeamLeader());
}
}
里氏替换原则
介绍
定义: 如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成2时, 程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
定义扩展: 一个软件实体如果适用一个父类的话那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够 替换父类对象,而程序逻辑不变。
引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能。
- 含义1:子类可以实现父类的抽象方法但不能覆盖父类的非抽象方法。
- 含义2:子类中可以增加自己特有的方法。
代码样例——类级别
改造前
类图
package top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.before;
/**
* 里氏替换原则:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换为o2,程序P的行为没有发生变化,那么类型S是类型T的子类型
* 即:所有使用父类对象的地方替换成子类对象依然正常运行
*
* @author F嘉阳
* @date 2018-09-22 20:01
*/
public class Rectangle {
private long length;
private long width;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
}
package top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.before;
/**
* @author F嘉阳
* @date 2018-09-22 20:03
*/
public class Square extends Rectangle {
private long sideLength;
@Override
public long getLength() {
return sideLength;
}
/**
* 覆盖了父类的实现,不满足里氏替换原则
*
* @param length
*/
@Override
public void setLength(long length) {
this.sideLength = length;
}
@Override
public long getWidth() {
return sideLength;
}
@Override
public void setWidth(long width) {
this.sideLength = width;
}
}
改造后
类图
package top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.after;
/**
* 通过四边形接口连接,防止继承泛滥
*
* @author F嘉阳
* @date 2018-09-22 20:11
*/
public interface Quadrangle {
long getWidth();
long getLength();
}
package top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.after;
/**
* @author F嘉阳
* @date 2018-09-22 20:03
*/
public class ASquare implements Quadrangle {
private long sideLength;
@Override
public long getWidth() {
return sideLength;
}
@Override
public long getLength() {
return sideLength;
}
public void setLength(long sideLength) {
this.sideLength = sideLength;
}
}
package top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.after;
/**
* 里氏替换原则:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换为o2,程序P的行为没有发生变化,那么类型S是类型T的子类型
* 即:所有使用父类对象的地方替换成子类对象依然正常运行
*
* @author F嘉阳
* @date 2018-09-22 20:01
*/
public class ARectangle implements Quadrangle {
private long length;
private long width;
@Override
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
@Override
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
}
测试类
package top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.before;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.after.ARectangle;
import top.fjy8018.designpattern.principle.liskovsubstitution.classdemo.after.Quadrangle;
@Slf4j
class SquareTest {
/**
* resize要求传入父类对象,对长宽进行设置
*
* @param rectangle
*/
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() <= rectangle.getLength()) {
rectangle.setWidth(rectangle.getWidth() + 1);
log.info("width:{} length:{}", rectangle.getWidth(), rectangle.getLength());
if (rectangle.getWidth() > 50 || rectangle.getLength() > 50) {
log.error("方法溢出");
break;
}
}
log.info("resize方法结束 width:{} length:{}", rectangle.getWidth(), rectangle.getLength());
}
public static void resize(Quadrangle quadrangle) {
while (quadrangle.getWidth() <= quadrangle.getLength()) {
// quadrangle.setWidth(quadrangle.getWidth()+1);报错,原有方法已经不能正确运行,说明原有方法设计存在问题
log.info("width:{} length:{}", quadrangle.getWidth(), quadrangle.getLength());
if (quadrangle.getWidth() > 50 || quadrangle.getLength() > 50) {
log.error("方法溢出");
break;
}
}
log.info("resize方法结束 width:{} length:{}", quadrangle.getWidth(), quadrangle.getLength());
}
@Test
void before() {
// 是长方形时业务正常运行
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setLength(20);
resize(rectangle);
// 使用之类替换父类对象,业务无法运行,方法溢出
Square square = new Square();
square.setLength(40);
resize(square);
}
@Test
void after() {
// 通过里氏替换原则改造后
ARectangle aRectangle = new ARectangle();
aRectangle.setLength(20);
aRectangle.setWidth(10);
}
}
代码样例——方法级别
改造前
package top.fjy8018.designpattern.principle.liskovsubstitution.method.input.before;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* @author F嘉阳
* @date 2018-09-23 11:29
*/
@Slf4j
class Base {
public void method(Map map) {
log.info("父类被执行");
}
}
package top.fjy8018.designpattern.principle.liskovsubstitution.method.input.before;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
/**
* @author F嘉阳
* @date 2018-09-23 11:30
*/
@Slf4j
class Child extends Base {
public void method(HashMap map) {
log.info("子类HashMap入参方法被执行");
}
}
改造后
package top.fjy8018.designpattern.principle.liskovsubstitution.method.input.after;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
/**
* @author F嘉阳
* @date 2018-09-23 11:29
*/
@Slf4j
class Base {
public void method(HashMap map) {
log.info("父类被执行");
}
}
package top.fjy8018.designpattern.principle.liskovsubstitution.method.input.after;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
/**
* 里氏替换原则要求方法入参必须比父类范围更宽,返回值必须比父类更窄
*
* @author F嘉阳
* @date 2018-09-23 11:30
*/
@Slf4j
class Child extends Base {
/**
* 方法重写,入参必须等于父类或者比父类范围更宽
* 而返回值比父类更窄已经交由IDE判断,若不符合,则编译不通过
*
* @param map
*/
@Override
public void method(HashMap map) {
log.info("子类HashMap入参方法被执行");
}
/**
* 方法重写,是子类额外的方法
*
* @param map
*/
public void method(Map map) {
log.info("子类Map入参方法被执行");
}
}
测试类
package top.fjy8018.designpattern.principle.liskovsubstitution.method.input.after;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
class ChildTest {
@Test
void before() {
}
@Test
void after() {
Child child = new Child();
// 此时希望调用子类重写的方法,若子类没重写,则应执行父类的方法
child.method(new HashMap());
}
}
合成(组合)聚合复用原则
介绍
定义:尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的
聚合hasA和组合 contains-A
优点:可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少
何时使用合成/聚合、继承
聚合has-A、组合 contains-A继承is-A
代码样例
改造前
类图
package top.fjy8018.designpattern.principle.compositionaggregation.before;
/**
* 合成复用原则:要尽量使用组合,尽量不要使用继承
*
* @author F嘉阳
* @date 2018-09-23 15:50
*/
public class DBConnection {
public String getConnection() {
return "MySQL数据库连接";
}
}
package top.fjy8018.designpattern.principle.compositionaggregation.before;
import lombok.extern.slf4j.Slf4j;
/**
* 直接使用继承,属于聚合关系,业务变更困难
*
* @author F嘉阳
* @date 2018-09-23 15:51
*/
@Slf4j
public class ProductDao extends DBConnection {
public void addProduct() {
log.info("使用{}", super.getConnection());
}
}
改造后
类图
package top.fjy8018.designpattern.principle.compositionaggregation.after;
/**
* 合成复用原则:要尽量使用组合,尽量不要使用继承
*
* @author F嘉阳
* @date 2018-09-23 15:50
*/
public abstract class ADBConnection {
public abstract String getConnection();
}
package top.fjy8018.designpattern.principle.compositionaggregation.after;
import lombok.extern.slf4j.Slf4j;
/**
* @author F嘉阳
* @date 2018-09-23 15:51
*/
@Slf4j
public class AProductDao {
private final ADBConnection connection;
/**
* 通过构造器注入,解除继承关系,同时符合里氏替换原则和开闭原则
*
* @param connection
*/
public AProductDao(ADBConnection connection) {
this.connection = connection;
}
public void addProduct() {
log.info("使用{}", connection.getConnection());
}
}
package top.fjy8018.designpattern.principle.compositionaggregation.after;
/**
* @author F嘉阳
* @date 2018-09-23 15:56
*/
public class MysqlConnection extends ADBConnection {
@Override
public String getConnection() {
return "MySQL数据库连接";
}
}
package top.fjy8018.designpattern.principle.compositionaggregation.after;
/**
* @author F嘉阳
* @date 2018-09-23 15:57
*/
public class OracleConnection extends ADBConnection {
@Override
public String getConnection() {
return "Oracle数据库连接";
}
}
测试类
package top.fjy8018.designpattern.principle.compositionaggregation.before;
import org.junit.jupiter.api.Test;
import top.fjy8018.designpattern.principle.compositionaggregation.after.AProductDao;
import top.fjy8018.designpattern.principle.compositionaggregation.after.MysqlConnection;
import top.fjy8018.designpattern.principle.compositionaggregation.after.OracleConnection;
import static org.junit.jupiter.api.Assertions.*;
class ProductDaoTest {
@Test
void before() {
ProductDao productDao = new ProductDao();
productDao.addProduct();
}
@Test
void after() {
// 变更交由业务层决定
AProductDao productDao1 = new AProductDao(new MysqlConnection());
productDao1.addProduct();
AProductDao productDao2 = new AProductDao(new OracleConnection());
productDao2.addProduct();
}
}