学习Java设计模式之前,有必要先了解设计模式原则。
开闭原则
定义
Coding
创建接口
1 2 3 4 5 6 7
| public interface ICourse { Integer getId();
String getName();
Double getPrice(); }
|
创建实现类
1 2 3 4 5 6 7 8 9 10 11 12 13
| @ToString @AllArgsConstructor public class JavaCourse implements ICourse {
@Getter private Integer id;
@Getter private String name;
@Getter private Double price; }
|
测试类
1 2 3 4 5 6 7
| public class Test {
public static void main(String[] args) { ICourse iCourse = new JavaCourse(96, "我的Java课程", 348d); System.out.println("课程ID: " + iCourse.getId() + " 课程名称: " + iCourse.getName() + "课程价格: " + iCourse.getPrice()); } }
|
控制台输出
1
| 课程ID: 96 课程名称: 我的Java课程课程价格: 348.0
|
如果现在要打折出售课程,按照开闭原则来设计,对扩展开放,对修改关闭。
创建打折类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; } }
|
修改应用类
1 2 3 4 5 6 7 8 9
| public class Test {
public static void main(String[] args) { ICourse javaCourse = new JavaDiscountCourse(96, "我的Java课程", 348d);
JavaDiscountCourse iCourse = (JavaDiscountCourse) javaCourse; System.out.println("课程ID: " + iCourse.getId() + " 课程名称: " + iCourse.getName() + "课程原价: " + iCourse.getOriginPrice() + " 课程折后价格: " + iCourse.getPrice()); } }
|
控制台输出
1
| 课程ID: 96 课程名称: 我的Java课程课程原价: 348.0 课程折后价格: 278.40000000000003
|
这里有个要注意的地方,Double * 0.8
后输出的浮点数精度有丢失的情况,可以使用BigDecimal
的String
构造器public BigDecimal(String val)
来解决。
修改JavaDiscountCourse
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 new BigDecimal(super.getPrice().toString()).multiply(new BigDecimal("0.8")).doubleValue(); } }
|
控制台输出
1
| 课程ID: 96 课程名称: 我的Java课程课程原价: 348.0 课程折后价格: 278.4
|
依赖倒置原则
定义
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节;细节应该依赖抽象
- 针对接口编程,不要针对实现编程
- 优点:可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险
Coding
反例
创建类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Geely { public void studyJavaCourse() { System.out.println("Geely在学习Java课程"); }
public void studyFECourse() { System.out.println("Geely在学习FE课程"); }
public void studyPythonCourse() { System.out.println("Geely在学习Python课程"); } }
|
测试类
1 2 3 4 5 6 7 8
| public class Test { public static void main(String[] args) { Geely geely = new Geely(); geely.studyFECourse(); geely.studyJavaCourse(); } }
|
控制台输出
1 2
| Geely在学习FE课程 Geely在学习Java课程
|
这时候,如果我们要让Geely学习Ruby课程,我们只能在Geely
类中添加
1 2 3
| public void studyRubyCourse() { System.out.println("Geely在学习Ruby课程"); }
|
然后,在Test类中添加
1
| geely.studyRubyCourse();
|
正例
创建接口
1 2 3
| public interface ICourse { void studyCourse(); }
|
创建类,带有成员变量ICourse course
1 2 3 4 5 6 7 8 9 10 11
| @AllArgsConstructor public class Geely {
@Setter private ICourse course;
public void studyImoocCourse() { course.studyCourse(); }
}
|
创建实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class FECourse implements ICourse { @Override public void studyCourse() { System.out.println("Geely在学习FE课程"); } } public class JavaCourse implements ICourse { @Override public void studyCourse() { System.out.println("Geely在学习Java课程"); } } public class PythonCourse implements ICourse { @Override public void studyCourse() { System.out.println("Geely在学习Python课程"); } }
|
测试类
1 2 3 4 5 6 7 8 9
| public class Test { public static void main(String[] args) { Geely geely = new Geely(new JavaCourse()); geely.studyImoocCourse();
geely.setCourse(new FECourse()); geely.studyImoocCourse(); } }
|
控制台输出
1 2
| Geely在学习Java课程 Geely在学习FE课程
|
这样一来,如果要添加新的课程,只需要创建实现类即可。然后应用类设置实现类,无需改动其他代码,符合依赖倒置原则。
单一职责原则
定义
- 不要存在多于一个导致类变更的原因
- 一个类/接口/方法只负责一项职责
- 优点:降低类的复杂度、提高类的可读性、提高系统的可维护性、降低变更引起的风险
Coding
反例
创建类
1 2 3 4 5
| public class Bird { public void mainMoveMode(String birdName) { System.out.println(birdName + " 用翅膀飞"); } }
|
测试类
1 2 3 4 5 6 7 8
| public class Test {
public static void main(String[] args) { Bird bird = new Bird(); bird.mainMoveMode("大雁"); bird.mainMoveMode("鸵鸟"); } }
|
控制台输出
鸵鸟是用脚走的,所以我们更改Bird类
1 2 3 4 5 6 7 8 9
| public class Bird { public void mainMoveMode(String birdName) { if ("鸵鸟".equals(birdName)) { System.out.println(birdName + " 用脚走"); } else { System.out.println(birdName + " 用翅膀飞"); } } }
|
如果有更多的鸟类,我们还要写更多的else代码。
正例
我们修改下反例中的例子
1 2 3 4 5 6 7 8 9 10
| public class FlyBird { public void mainMoveMode(String birdName) { System.out.println(birdName + " 用翅膀飞"); } } public class WalkBird { public void mainMoveMode(String birdName) { System.out.println(birdName + " 用脚走"); } }
|
添加测试类
1 2 3 4 5 6 7 8 9 10
| public class Test {
public static void main(String[] args) { FlyBird flyBird = new FlyBird(); flyBird.mainMoveMode("大雁");
WalkBird walkBird = new WalkBird(); walkBird.mainMoveMode("鸵鸟"); } }
|
控制台输出
再举一个例子
创建接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public interface ICourseContent {
String getCoursName();
byte[] getCourseVideo();
}
public interface ICourseManager { void studyCourse();
void refundCourse(); }
|
创建实现类,有着课程内容和课程管理两种职能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class CourseImpl implements ICourseContent, ICourseManager { @Override public String getCoursName() { return null; }
@Override public byte[] getCourseVideo() { return new byte[0]; }
@Override public void studyCourse() {
}
@Override public void refundCourse() {
} }
|
接口隔离原则
定义
- 用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口
- 一个类对一个类的依赖应该建立在最小的接口上
- 建立单一接口,不要建立庞大臃肿的接口
- 尽量细化接口,接口中的方法尽量少
- 优点:符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。
Coding
反例
创建接口
1 2 3 4 5 6 7 8
| public interface IAnimalAction {
void eat();
void fly();
void swim(); }
|
创建实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Bird implements IAnimalAction { @Override public void eat() { System.out.println("鸟 吃饭"); }
@Override public void fly() { System.out.println("鸟 飞"); }
@Override public void swim() { } } public class Dog implements IAnimalAction { @Override public void eat() { System.out.println("狗 吃饭"); }
@Override public void fly() { }
@Override public void swim() { System.out.println("狗 游泳"); } }
|
我们可以看出,鸟和狗实现了接口后,各自都有无用的接口,所以违反了接口隔离原则,只能采取空实现的方式。但是对于使用方来说,还是可以调用狗的fly方法,得到空的实现。
正例
将反例中的接口接口拆分为三个独立的接口
1 2 3 4 5 6 7 8 9
| public interface IEatAnimalAction { void eat(); } public interface IFlyAnimalAction { void fly(); } public interface ISwimAnimalAction { void swim(); }
|
Dog
改为
1 2 3 4 5 6 7 8 9 10 11
| public class Dog implements IEatAnimalAction,ISwimAnimalAction { @Override public void eat() { System.out.println("狗 吃饭"); }
@Override public void swim() { System.out.println("狗 游泳"); } }
|
Bird
改为
1 2 3 4 5 6 7 8 9 10 11
| public class Bird implements IEatAnimalAction,IFlyAnimalAction { @Override public void eat() { System.out.println("鸟 吃饭"); }
@Override public void fly() { System.out.println("鸟 飞"); } }
|
这样就成功的将一个大接口,优化为分摊职责的小接口,实现类可以根据需要实现多个职能接口。
迪米特原则
定义
- 一个对象应该对其他对象保持最少的了解。又叫最少知道原则
- 尽量降低类与类之间的耦合
- 优点:降低类之间的耦合
Coding
反例
创建课程类
创建项目经理类
1 2 3 4 5 6
| public class TeamLeader {
public void checkNumberOfCourse(List<Course> courseList) { System.out.println("在线课程的数量是 :" + courseList.size()); } }
|
创建老板类
1 2 3 4 5 6 7 8 9 10
| public class Boss {
public void commandCheckNumber(TeamLeader teamLeader) { List<Course> courseList = new ArrayList<>(); for (int i = 0; i < 20; i++) { courseList.add(new Course()); } teamLeader.checkNumberOfCourse(courseList); } }
|
测试类
我们仔细分析一下,其实老板并不需要知道课程的细节,只需要问一下项目经理,有多少课程,项目经理直接告诉老板有20节在线课程。而不是老板将课程列出,让项目经理统计。
我们看下UML类图

正例
项目经理类修改为
1 2 3 4 5 6 7 8 9 10
| public class TeamLeader { public void checkNumberOfCourse() { List<Course> courseList = new ArrayList<>(); for (int i = 0; i < 20; i++) { courseList.add(new Course()); } System.out.println("在线课程的数量是 :" + courseList.size()); } }
|
老板类
1 2 3 4 5 6
| public class Boss {
public void commandCheckNumber(TeamLeader teamLeader) { teamLeader.checkNumberOfCourse(); } }
|
这时候运行一下,结果一样。但是从UML类图上来看,是有很大的优化的。
