厚积薄发打卡Day113:Debug设计模式:设计原则(一)<开闭原则、依赖倒置、单一职责>
开闭原则
定义
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。其优点:提高软件系统的可复用性及可维护性
用抽象构建框架,用实现扩展细节,实现 面向抽象编程。
中心思想:抽象构建框架,实现扩展细节
场景举例Coding
在线课程购买例子,课程在线购买:
/**
* 课程接口
*/
public interface ICourse {
// 课程id
Integer getId();
// 课程名字
String getName();
// 课程价格
Double getPrice();
}
/**
* Java课程
*/
public class JavaCourse implements ICourse {
private Integer Id;
private String name;
private Double price;
public JavaCourse(Integer id, String name, Double price) {
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;
}
}
测试类:
public class Test {
public static void main(String[] args) {
ICourse course = new JavaCourse(1415, "Java企业级别开发", 299d);
System.out.printf("课程id: %d,课程名称:%s,课程价格:%f 元\n", course.getId(), course.getName(), course.getPrice());
}
}
课程id: 1415,课程名称:Java企业级别开发,课程价格:299.000000 元
场景变更:现在遇到活动,线上课程通通八折!那应该怎么修改呢?
~~修改接口?~~那会影响全部实现类,如果有一个实现类则修改一个实现类,那如果实现类很多是否都需要一个个进行修改呢?
**软件开发设计重点:接口不应该轻易修改,稳定且可靠,作为契约。**同时,越底层的模块变化影响越大,越高层的模块变化影响则越小。
那不在影响原有代码的基础上进行扩展,最好的方法就是使用Java的多态特性:继承。
场景实现:
public class DiscountCourse extends JavaCourse {
public DiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getOriginPrice() {
return super.getPrice();
}
public Double getDiscountPrice() {
// 同时这里可以对折扣价格方案进行处理
return super.getPrice() * 0.8;
}
}
测试类:
public class Test {
public static void main(String[] args) {
ICourse course = new JavaCourse(1415, "Java企业级别开发", 299d);
System.out.printf("课程id: %d,课程名称:%s,课程价格:%f 元\n", course.getId(), course.getName(), course.getPrice());
DiscountCourse course2 = new DiscountCourse(1415, "Java企业级别开发", 299d);
System.out.printf("课程id: %d,课程名称:%s,课程原始价格:%f 元,618活动折扣价为:%f 元\n", course2.getId(), course2.getName(), course2.getOriginPrice(), course2.getDiscountPrice());
}
}
课程id: 1415,课程名称:Java企业级别开发,课程价格:299.000000 元
课程id: 1415,课程名称:Java企业级别开发,课程原始价格:299.000000 元,618活动折扣价为:239.200000 元
这样通过继承的方式,不改变原有代码的基础上,实现了折扣价方案的修改实现。
依赖倒置
定义:
-
高层模块不应该依赖低层模块,二者都应该依赖其抽象。
-
抽象不应该依赖细节;细节应该依赖抽象
-
核心思想:针对接口编程,面向接口编程,不要针对实现编程
优点:
- 可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险
场景Coding:
-
举例:面向实现类编程
public class Wayne { public void studyJavaCourse(){ System.out.println("Wayne在学习Java课程"); } public void studyPythonCourse(){ System.out.println("Wayne在学习Python课程"); } }
public class Test { public static void main(String[] args) { // v1 Wayne wayne = new Wayne(); wayne.studyJavaCourse(); wayne.studyPythonCourse(); } }
Wayne在学习Java课程 Wayne在学习Python课程
- 此时Test是应用层,Wayne是底层模块,如果这时候wayne要学习更多课程,则在底层模块进行修改,违背了
高层模块不应该依赖低层模块
,直接引用了而不是通过抽象依赖。
- 此时Test是应用层,Wayne是底层模块,如果这时候wayne要学习更多课程,则在底层模块进行修改,违背了
-
引用抽象
// 抽象依赖 public interface IStudy { void studyCourse(); }
依赖实现:
public class GoStudy implements IStudy { @Override public void studyCourse() { System.out.println("Wayne在学习Go语言"); } }
public class JavaStudy implements IStudy{ @Override public void studyCourse() { System.out.println("Wayne在学习Java"); } }
public class PythonStudy implements IStudy { @Override public void studyCourse() { System.out.println("Wayne在学习Python"); } }
底层:
public class Wayne { public void studyCourse(IStudy study) { study.studyCourse(); } }
应用层:
public class Test { public static void main(String[] args) { //v2 Wayne wayne = new Wayne(); wayne.studyCourse(new PythonStudy()); wayne.studyCourse(new JavaStudy()); wayne.studyCourse(new GoStudy()); } }
Wayne在学习Python Wayne在学习Java Wayne在学习Go语言
-
使用构造器注入(单例模式):有点Spring的味道了
public class Wayne {
// ---单例:
private IStudy iStudy;
public Wayne(IStudy iStudy) {
this.iStudy = iStudy;
}
public void studyCourse() {
iStudy.studyCourse();
}
}
//v3:单例模式:
new Wayne(new JavaStudy()).studyCourse();
new Wayne(new PythonStudy()).studyCourse();
-
set方法注入:注入方式无需修改底层模块,即后续想要学习什么课程直接扩展IStudy即可。
public class Wayne { // set注入 private IStudy iStudy; public void setiStudy(IStudy iStudy) { this.iStudy = iStudy; } public void studyCourse() { iStudy.studyCourse(); } }
//v4:set注入 Wayne wayne = new Wayne(); wayne.setiStudy(new JavaStudy()); wayne.studyCourse(); wayne.setiStudy(new GoStudy()); wayne.studyCourse();
综上总结:面向接口编程
单一职责
定义:
不要存在多于一个导致类变更的原因,一个类/接口/方法只负责一项职责
优点:降低类的复杂度、提高类的可读性,提高系统的可维护性、降低变更引起的风险
场景Coding
-
场景耦合
public class Bird { public void mainMoveMode(String birdName) { //使用边界判断 if ("鸵鸟".equals(birdName)) { System.out.println(birdName + "用脚走"); } else { System.out.println(birdName + "用翅膀飞"); } } }
public class Test { public static void main(String[] args) { Bird bird = new Bird(); bird.mainMoveMode("鸵鸟"); bird.mainMoveMode("大雁"); bird.mainMoveMode("鸽子🕊"); } }
鸵鸟用脚走 大雁用翅膀飞 鸽子🕊用翅膀飞
如果后面增加飞行动物,则需要不断修改Bird里面的代码,同时功能并不单一,违背单一职责原则。
-
类扩展,实现单一原则
public class FlyBird {
public void mainMoveMode(String birdName){
System.out.println(birdName+"用翅膀飞");
}
}
public class WalkBird {
public void mainMoveMode(String birdName){
System.out.println(birdName+"用脚走");
}
}
public class Test {
public static void main(String[] args) {
WalkBird walkBird = new WalkBird();
walkBird.mainMoveMode("鸵鸟");
FlyBird flyBird = new FlyBird();
flyBird.mainMoveMode("大雁");
}
}
鸵鸟用脚走
大雁用翅膀飞
-
接口扩展,以课程举例
public interface ICourse { // 课程内容管理 String getCourseName(); byte[] getCourseVideo(); // 课程中心管理 void studyCourse(); void refundCourse(); }
接口在实现类中进行细分:
public interface ICourseContent {
String getCourseName();
byte[] getCourseVideo();
}
public interface ICourseManager {
void studyCourse();
void refundCourse();
}
public class CourseImpl implements ICourseManager, ICourseContent {
@Override
public void studyCourse() {
}
@Override
public void refundCourse() {
}
@Override
public String getCourseName() {
return null;
}
@Override
public byte[] getCourseVideo() {
return new byte[0];
}
}
-
方法级别,单一职责,方法一般通过对象实现,酌情参考。
public class Method { private void updateUserInfo(String userName, String address) { userName = "wayne"; address = "beijing"; } private void updateUserInfo(String userName, String... properties) { userName = "wayne"; // address = "beijing"; } private void updateUsername(String userName) { userName = "wayne"; } private void updateUserAddress(String address) { address = "beijing"; } private void updateUserInfo(String userName, String address, boolean bool) { if (bool) { //todo something1 } else { //todo something2 } userName = "wayne"; address = "beijing"; } }
总结:单一职责会新增很多类,若盲目使用则会造成类爆炸要根据实际情况,尽量做到接口和方法的单一职责