开闭原则(Open-Closed Principle,OCP)是指一个软件实体(如类、模块和函数)应该对扩展开放,对修改关闭。
课程接口ICourser:
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}
Java架构课程的类JavaCourse:
public class JavaCourse implements ICourse {
private Integer Id;
private String name;
private Double price;
public JavaCourse(Integer id, String name, Double 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 Double getPrice() {
return this.price;
}
}
处理优惠逻辑的类JavaDiscountCurse:
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getOriginPrice() {
return super.getPrice();
}
@Override
public Double getPrice() {
return super.getPrice()*0.61;
}
}
测试类OpenCloseTest:
public class OpenCloseTest {
public static void main(String[] args) {
ICourse iCourse = new JavaDiscountCourse(233,"学院专属课", 8888D);
JavaDiscountCourse javaCourse = (JavaDiscountCourse) iCourse;
System.out.println("课程ID:" + javaCourse.getId() +
"\n课程名称:《" + javaCourse.getName() + "》" +
"\n原价:" + javaCourse.getOriginPrice() + "元" +
"\n折后价:" + javaCourse.getPrice() + "元");
}
}
输出:
课程ID:233
课程名称:《学院专属课》
原价:8888.0元
折后价:5421.68元
Dependence Inversion Principle,DIP。是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
创建一个Jack类:
public class Jack {
public void studyJavaCourse() {
System.out.println("Jack在学习Java的课程");
}
public void studyPythonCourse() {
System.out.println("Jack在学习Python的课程");
}
}
测试类:
public class DependencyInversionTest {
public static void main(String[] args) {
Jack jack = new Jack();
jack.studyJavaCourse();
jack.studyPythonCourse();
}
}
如果Jack还想学习AI的课程,则随着业务的扩展,要从低层到高层依次修改代码。在Jack类中增加studyAICourse()方法,在高层也要追加调用,实际上是很不稳定的,在修改代码的同时也会带来意想不到的风险。接下来优化代码,创建一个课程的抽象ICourse接口:
public interface ICourse {
void study();
}
然后编写JavaCourse类:
public class JavaCourse implements ICourse {
@Override
public void study() {
System.out.println("Jack在学习Java的课程");
}
}
再实现PythonCourse类:
public class PythonCourse implements ICourse {
@Override
public void study() {
System.out.println("Jack在学习Python的课程");
}
}
修改Jack类:
public class Jack {
public void study(ICourse course) {
course.study();
}
}
调用代码:
public class DependencyInversionTest {
public static void main(String[] args) {
Jack jack = new Jack();
jack.study(new JavaCourse());
jack.study(new PythonCourse());
}
}
输出:
Jack在学习Java的课程
Jack在学习Python的课程
如此,无论Jack的兴趣如何暴涨,对于新的课程,只需要新建一个类,通过传参的方式告诉Jack,而不需要修改底层代码。这种方式就叫依赖注入。
注入的方式还有构造器方式和Setter方式。构造器注入方式:
public class Jack {
private ICourse course;
public Jack(ICourse course) {
this.course = course;
}
public void study() {
course.study();
}
}
调用代码:
public class DependencyInversionTest {
public static void main(String[] args) {
Jack jack = new Jack(new JavaCourse());
jack.study();
}
}
Jack在学习Java的课程
根据构造器方式注入,在调用时,每次都要创建实例。如果Jack是全局单例,则我们只能选择用Setter方式来注入,继续修改Jack类:
public class Jack {
private ICourse course;
public void setCourse(ICourse course) {
this.course = course;
}
public void study() {
course.study();
}
}
调用代码:
public class DependencyInversionTest {
public static void main(String[] args) {
Jack jack = new Jack();
jack.setCourse(new JavaCourse());
jack.study();
jack.setCourse(new PythonCourse());
jack.study();
}
}
Jack在学习Java的课程
Jack在学习Python的课程
Simple Responsibility Principe,SRP:是指不要存在多于一个导致类变更的原因。一个类、接口或方法只负责一项职责。
以直播课和录播课为例子,创建一个Course类:
public class Course {
public void study(String courseName) {
if ("直播课".equals(courseName)) {
System.out.println(courseName + "不能快进");
} else {
System.out.println(courseName + "可以反复回看");
}
}
}
调用代码:
public class SingleResponsibilityTest {
public static void main(String[] args) {
Course course = new Course();
course.study("直播课");
course.study("录播课");
}
}
直播课不能快进
录播课可以反复回看
从代码来看,Course类承担了两种处理逻辑。如果现在需要对课程进行加密,直播课程和录播课程的加密逻辑不一样,必须修改代码。而修改代码的逻辑势必会一你会相互影响,容易带来不可控的风险。
需要对职责进行解耦,分别创建两个类:LiveCourse和ReplayCourse。
public class LiveCourse {
public void study(String courseName) {
System.out.println(courseName + "不能快进看");
}
}
public class ReplayCourse {
public void study(String courseName) {
System.out.println(courseName + "可以反复回看");
}
}
调用代码:
public class SingleResponsibilityTest {
public static void main(String[] args) {
LiveCourse liveCourse = new LiveCourse();
liveCourse.study("直播课");
ReplayCourse replayCourse = new ReplayCourse();
replayCourse.study("录播课");
}
}
直播课不能快进看
录播课可以反复回看
如果业务继续发展,课程要做权限。未付费学员可以获取基本信息,已付费学员可以获得学习权限。那么在控制课程层面至少有两个职责。我们可以将展示职责和管理职责分离开来,都实现同一个抽象依赖。设计一个顶层接口,创建ICourse接口:
public interface ICourse {
// 获得基本信息
String getCourseName();
// 获得视频流
byte[] getCourseVideo();
// 学习课程
void studyCourse();
// 退款
void refundCourse();
}
也可以将这个接口拆成两个接口:ICourseInfo和ICourseManager
public interface ICourseInfo {
String getCourseName();
byte[] getCourseVideo();
}
public interface ICourseManager {
void studyCourse();
void refundCourse();
}
Interface Segregation Principle,ISP:是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
对一个动物行为进行抽象描述:
public interface IAnimal {
void eat();
void fly();
void swim();
}
Bird类:
public class Bird implements IAnimal {
@Override
public void eat() {}
@Override
public void fly() {}
@Override
public void swim() {}
}
Dog类:
public class Dog implements IAnimal {
@Override
public void eat() {}
@Override
public void fly() {}
@Override
public void swim() {}
}
可以看出,Bird的swim()方法只能空着,Dog的fly()方法显然也是不可能的。这时需要对不同动物行为来设计不同的接口,分别设计IEatAnimal、IFlyAnimal和ISwimAnimal接口。代码如下:
public interface IEatAnimal {
void eat();
}
public interface IFlyAnimal {
void fly();
}
public interface ISwimAnimal {
void swim();
}
Dog只实现了IEatAnimal和ISwimAnimal接口,代码如下:
public class Dog implements IEatAnimal, ISwimAnimal {
@Override
public void eat() {}
@Override
public void swim() {}
}
Law of Demeter,LoD:是指一个对象应该对其他对象保持最小的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合度。
设计一个权限系统,Boss需要查看目前发布到线上的课程数量。
public class Course {
}
public class TeamLeader {
public void checkNumberOfCourses(List courseList) {
System.out.println("目前已发布的课程数量是:" + courseList.size());
}
}
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader) {
// 模拟Boss一页一页往下翻页,TeamLeader实时统计
List courseList = new ArrayList();
for (int i = 0; i < 20; i++) {
courseList.add(new Course());
}
teamLeader.checkNumberOfCourses(courseList);
}
}
public class LawOfDemeterTest {
public static void main(String[] args) {
Boss boss = new Boss();
TeamLeader teamLeader = new TeamLeader();
boss.commandCheckNumber(teamLeader);
}
}
目前已发布的课程数量是:20
根据迪米特原则,Boss只想要结果,不需要跟Course直接交流。改造如下:
public class TeamLeader {
public void checkNumberOfCourses() {
List courseList = new ArrayList();
for (int i = 0; i < 20; i++) {
courseList.add(new Course());
}
System.out.println("目前已发布的课程数量是:" + courseList.size());
}
}
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader) {
teamLeader.checkNumberOfCourses();
}
}
Liskov Substitution Principle,LSP:是指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
使用里氏替换原则有以下优点:
public interface Quadangle {
long getWidth();
long getHeight();
}
public class Rectangle implements Quadangle {
private long height;
private long width;
@Override
public long getWidth() {
return 0;
}
@Override
public long getHeight() {
return 0;
}
public void setHeight(long height) {
this.height = height;
}
public void setWidth(long width) {
this.width = width;
}
}
public class Square extends Rectangle {
private long length;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
@Override
public long getWidth() {
return getLength();
}
@Override
public long getHeight() {
return getLength();
}
@Override
public void setHeight(long height) {
setLength(height);
}
@Override
public void setWidth(long width) {
setLength(width);
}
}
public class LiskovSubstiutionTest {
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() >= rectangle.getHeight()) {
rectangle.setHeight(rectangle.getHeight()+1);
System.out.println("width:" + rectangle.getWidth() +
"\nheight:" + rectangle.getHeight());
}
System.out.println("resize方法结束:" +
"\nwidth:" + rectangle.getWidth() +
"\nheight:" + rectangle.getHeight());
}
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(20);
rectangle.setHeight(10);
resize(rectangle);
}
}
Composite/Aggregate Reuse Principle,CARP:是指尽量使用对象组合(has-a)/聚合(contains-a)而不是继承关系达到软件复用的目的。
继承叫作白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合称为黑箱复用,我们无法获取到类以外的对象的实现细节。
以数据库为例:
public class DBConnection {
public String getConnection() {
return "MySQL数据库连接";
}
}
public class ProductDao {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void addProduct() {
String conn = dbConnection.getConnection();
System.out.println("使用" + conn + "增加产品");
}
}
假设业务发生变化,数据库操作层要支持Oracle数据库,虽然可以在DBConnection中增加对Oracle数据库的支持,但是这违背了开闭原则。更改如下:
public abstract class DBConnection {
public abstract String getConnection();
}
public class MySQLConnection extends DBConnection {
@Override
public String getConnection() {
return "MySQL数据库连接";
}
}
public class OracleConnection extends DBConnection {
@Override
public String getConnection() {
return "Oracle数据库连接";
}
}
具体选择交给应用层。