软件设计七大原则3-1 本章导航3-2 开闭原则讲解3-3 开闭原则coding3-4 依赖倒置原则讲解+coding3-5 单一职责原则讲解3-6 单一职责原则coding3-7 接口隔离原则讲解+coding3-8 迪米特法则讲解+coding3-9 里氏替换原则讲解3-10 里氏替换原则coding3-11 合成复用原则讲解+coding
3-1 本章导航
3-2 开闭原则讲解
实现开闭原则的核心思想就是面向抽象编程而不是面向具体的实现编程。
3-3 开闭原则coding
首先定义一个课程的接口:
public interface ICourse {
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) {
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;
}
}
定义一个Test测试类:
public class Test {
public static void main(String[]args){
ICourse javaCourse = new JavaCourse(96, "Java 从零开始到企业级开发", 348d);
System.out.println("课程Id:" + javaCourse.getId() + "课程名称:" + javaCourse.getName() + "课程价格:" + javaCourse.getPrice());
}
}
输出结果如下:
课程Id:96课程名称:Java 从零开始到企业级开发课程价格:348.0
现在的类结构图如图所示:
假如现在双十一的时候,要进行课程的打折活动:我们应该如何去做呢 ?
我们可以这样来做:
添加一个计算打折价格的方法:
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
Double getDiscountPrice();
}
同样实现类也要实现这个方法:
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;
}
@Override
public Double getDiscountPrice() {
return this.price*0.8;
}
}
在Test类里面,我们就是可以这样来进行获取:
public class Test {
public static void main(String[]args){
ICourse javaCourse = new JavaCourse(96, "Java 从零开始到企业级开发", 348d);
System.out.println("课程Id:" + javaCourse.getId() + "课程名称:" + javaCourse.getName() + "课程价格:" + javaCourse.getPrice()+"双十一打折价格:"+javaCourse.getDiscountPrice());
}
}
输出结果如下:
课程Id:96课程名称:Java 从零开始到企业级开发课程价格:348.0双十一打折价格:278.40000000000003
但是,这种方法不好,假如课程很多,那么所有的课程的实现类都要重写一下方法,接口应该是稳定的,不应该是经常修改的。
我们再换一种方法:
我们写一个Java课程打折类并且继承于Java课程类,之前的课程类里面的价格就不打折了:
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
@Override
public Double getPrice() {
return super.getPrice()*0.8;
}
}
然后,我们在测试的时候,就是可以直接指向Java课程打折类的这个对象就可以了:
public class Test {
public static void main(String[]args){
ICourse javaCourse = new JavaDiscountCourse(96, "Java 从零开始到企业级开发", 348d);
System.out.println("课程Id:" + javaCourse.getId() + "课程名称:" + javaCourse.getName() + "课程价格:" + javaCourse.getPrice());
}
}
这个时候的结果就是:这里有丢失精度的问题,可以使用String构造器的BigDecimal来解决
课程Id:96课程名称:Java 从零开始到企业级开发课程价格:222.72000000000003
如果我们还想要原价,我们可以这样来做:
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.8;
}
}
这个时候,我们就是可以这样来调用:
public class Test {
public static void main(String[]args){
ICourse iCourse = new JavaDiscountCourse(96, "Java 从零开始到企业级开发", 348d);
/** 如果调用实现类里面的方法,我们就必须要进行强转一下 */
JavaDiscountCourse javaCourse = (JavaDiscountCourse)iCourse;
System.out.println("课程Id:" + javaCourse.getId() + "课程名称:" + javaCourse.getName() + "课程价格:"+javaCourse.getOriginPrice()+"课程折后价格:" + javaCourse.getPrice());
}
}
执行结果如下:
课程Id:96课程名称:Java 从零开始到企业级开发课程价格:348.0课程折后价格:278.40000000000003
现在的类图如图所示:
我们通过了继承了基类,然后对其进行扩展,对扩展是开发的,而对修改接口和基类是关闭的;
越是基层的模块的修改影响的范围是越大的。
3-4 依赖倒置原则讲解+coding
依赖倒置原则的核心就是:面向接口编程
有一个类:里面有两个方法,一个学习java课程的方法,一个是学习FE课程的方法:
public class Tom {
public void studyJavaCourse() {
System.out.println("Tom在学习Java课程");
}
public void studyFECourse() {
System.out.println("Tom在学习FE课程");
}
}
这个时候,我们写上一个测试类:
public class Test {
public static void main(String[]args){
Tom tom = new Tom();
tom.studyJavaCourse();
tom.studyFECourse();
}
}
如果这个时候,我还想要添加一个学习Python课程的方法,这个时候,我可以在基类里面进行添加方法:
public class Tom {
public void studyJavaCourse() {
System.out.println("Tom在学习Java课程");
}
public void studyFECourse() {
System.out.println("Tom在学习FE课程");
}
public void studyPythonCourse() {
System.out.println("Tom在学习Python课程");
}
}
以上我们的做法就是在面向实现来进行编程,面向实现类来进行编程的话, 扩展性比较的差,这个就是依赖于底层的实现的
现在我们来引入抽象来解决这个问题:
首先写上一个课程接口:
public interface ICourse {
void studyCourse();
}
有两个实现类:一个是学习Java的实现类,一个是学习前端的实现类,分别是:
public class JavaCourse implements ICourse {
@Override
public void studyCourse() {
System.out.println("Tom在学习Java课程");
}
}
public class FECourse implements ICourse {
@Override
public void studyCourse() {
System.out.println("Tom在学习FE课程");
}
}
原来的Tom类,我们就要进行重构了,写了一个学习课程的方法,传入了学习课程的接口,由具体的实现类来进行实现:
具体学了哪些课程,是由具体的实现类来决定的:
public class Tom {
public void studyCourse(ICourse iCourse) {
iCourse.studyCourse();
}
}
我们对其进行测试:
public class Test {
public static void main(String[]args){
Tom tom = new Tom();
tom.studyCourse(new JavaCourse());
tom.studyCourse(new FECourse());
}
}
执行结果为:
Tom在学习Java课程
Tom在学习FE课程
这个时候,如果还要学习Python的课程的话,那我们就可以再写上一个实现类来对接口进行实现即可:
public class PythonCourse implements ICourse {
@Override
public void studyCourse() {
System.out.println("Tom在学习Python课程");
}
}
我们就可以来调用了:
public class Test {
public static void main(String[]args){
Tom tom = new Tom();
tom.studyCourse(new JavaCourse());
tom.studyCourse(new FECourse());
tom.studyCourse(new PythonCourse());
}
}
输出结果:
Tom在学习Java课程
Tom在学习FE课程
Tom在学习Python课程
以上是通过接口方法的方式来注入具体的实现;
当然,我们也可以通过构造器的方式来注入具体的实现:
在Tom这个类里面写一个构造器,把接口作为Tom类里面的一个成员属性,然后通过构造器来对其进行赋值:
public class Tom {
private ICourse iCourse;
public Tom(ICourse iCourse) {
this.iCourse = iCourse;
}
/** 这里的方法,就只需要调用类成员变量ICourse里面的studyCourse()方法就可以了 */
public void studyCourse() {
iCourse.studyCourse();
}
}
测试:
public class Test {
public static void main(String[]args){
Tom tom = new Tom(new JavaCourse());
tom.studyCourse();
}
}
测试结果:
Tom在学习Java课程
以上的用构造器来进行传递接口的实现,也不是很好,每次学一个新的课程的时候,还有重新new一个类;
这个时候,我们可以利用set方法来进行注入:
public class Tom {
private ICourse iCourse;
public void setiCourse(ICourse iCourse) {
this.iCourse = iCourse;
}
public void studyCourse() {
iCourse.studyCourse();
}
}
这个时候,我们就是可以这样来用了:
public class Test {
public static void main(String[]args){
Tom tom = new Tom();
tom.setiCourse(new JavaCourse());
tom.studyCourse();
tom.setiCourse(new FECourse());
tom.studyCourse();
}
}
执行结果:
Tom在学习Java课程
Tom在学习FE课程
现在的类图:
3-5 单一职责原则讲解
3-6 单一职责原则coding
我们写一个Bird类:
public class Bird {
public void mainMoveMode(String birdName) {
System.out.println(birdName+"用翅膀飞");
}
}
测试类:
public class Test {
public static void main(String[]args){
Bird bird = new Bird();
bird.mainMoveMode("大雁");
bird.mainMoveMode("鸵鸟");
}
}
输出结果:
大雁用翅膀飞
鸵鸟用翅膀飞
那么现在就是有问题的 ,鸵鸟不是用翅膀飞的。
这个时候,我们在原来Bird类里面进行扩展:这个时候,是不遵循单一职责原则的
public class Bird {
public void mainMoveMode(String birdName) {
if ("鸵鸟".equals(birdName)) {
System.out.println(birdName + "用脚走");
} else {
System.out.println(birdName+"用翅膀飞");
}
}
}
这个时候的运行结果就是:
大雁用翅膀飞
鸵鸟用脚走
这个时候,我们按照职责的不同来进行拆分:
会飞的鸟:
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){
FlyBird flyBird = new FlyBird();
flyBird.mainMoveMode("大雁");
WalkBird walkBird = new WalkBird();
walkBird.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 String getCourseName() {
return null;
}
@Override
public byte[] getCourseVideo() {
return new byte[0];
}
@Override
public void studyCourse() {
}
@Override
public void refundCourse() {
}
}
上面说的就是接口层面上的单一职责原则;
我们定义一个类,里面写上两个方法:
public class Method {
private void updateUserInfo(String userName, String address) {
/** 这个是一个伪代码,传进来的进来的值进行一个更新 */
userName = "Tom";
address = "beijing";
}
private void updateUserInfo(String userName, String... properties) {
/** 这个是一个伪代码,传进来的进来的值进行一个更新 */
userName = "Tom";
}
}
上面定义的两个方法,是没有遵循单一职责原则的:
下面我们把方法拆成两个:
public class Method {
private 2018-11-18 19:16