学习Java设计模式之前,有必要先了解设计模式原则。
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
优点:提高软件系统的可复用性及可维护性
创建接口
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}
创建实现类
@ToString
@AllArgsConstructor
public class JavaCourse implements ICourse {
@Getter
private Integer id;
@Getter
private String name;
@Getter
private Double price;
}
测试类
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());
}
}
控制台输出
课程ID: 96 课程名称: 我的Java课程课程价格: 348.0
如果现在要打折出售课程,按照开闭原则来设计,对扩展开放,对修改关闭。
创建打折类
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 javaCourse = new JavaDiscountCourse(96, "我的Java课程", 348d);
JavaDiscountCourse iCourse = (JavaDiscountCourse) javaCourse;
System.out.println("课程ID: " + iCourse.getId() + " 课程名称: " + iCourse.getName() + "课程原价: " + iCourse.getOriginPrice() + " 课程折后价格: " + iCourse.getPrice());
}
}
控制台输出
课程ID: 96 课程名称: 我的Java课程课程原价: 348.0 课程折后价格: 278.40000000000003
这里有个要注意的地方,Double * 0.8
后输出的浮点数精度有丢失的情况,可以使用BigDecimal
的String
构造器public BigDecimal(String val)
来解决。
修改JavaDiscountCourse
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();
}
}
控制台输出
课程ID: 96 课程名称: 我的Java课程课程原价: 348.0 课程折后价格: 278.4
创建类
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课程");
}
}
测试类
public class Test {
// v1
public static void main(String[] args) {
Geely geely = new Geely();
geely.studyFECourse();
geely.studyJavaCourse();
}
}
控制台输出
Geely在学习FE课程
Geely在学习Java课程
这时候,如果我们要让Geely学习Ruby课程,我们只能在Geely
类中添加
public void studyRubyCourse() {
System.out.println("Geely在学习Ruby课程");
}
然后,在Test类中添加
geely.studyRubyCourse();
{% note warning %}
不符合依赖倒置原则
{% endnote %}
创建接口
public interface ICourse {
void studyCourse();
}
创建类,带有成员变量ICourse course
@AllArgsConstructor
public class Geely {
@Setter
private ICourse course;
public void studyImoocCourse() {
course.studyCourse();
}
}
创建实现类
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课程");
}
}
测试类
public class Test {
public static void main(String[] args) {
Geely geely = new Geely(new JavaCourse());
geely.studyImoocCourse();
geely.setCourse(new FECourse());
geely.studyImoocCourse();
}
}
控制台输出
Geely在学习Java课程
Geely在学习FE课程
这样一来,如果要添加新的课程,只需要创建实现类即可。然后应用类设置实现类,无需改动其他代码,符合依赖倒置原则。
创建类
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 + " 用翅膀飞");
}
}
}
如果有更多的鸟类,我们还要写更多的else代码。
我们修改下反例中的例子
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("鸵鸟");
}
}
控制台输出
大雁 用翅膀飞
鸵鸟 用脚走
再举一个例子
创建接口
/**
* 课程内容
*
* @author gaochen
* Created on 2019/7/27.
*/
public interface ICourseContent {
String getCoursName();
byte[] getCourseVideo();
}
/**
* 课程管理
*
* @author gaochen
* Created on 2019/7/27.
*/
public interface ICourseManager {
void studyCourse();
void refundCourse();
}
创建实现类,有着课程内容和课程管理两种职能
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() {
}
}
{% note warning %}
注意适度原则,一定要适度
{% endnote %}
创建接口
public interface IAnimalAction {
void eat();
void fly();
void swim();
}
创建实现类
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方法,得到空的实现。
将反例中的接口接口拆分为三个独立的接口
public interface IEatAnimalAction {
void eat();
}
public interface IFlyAnimalAction {
void fly();
}
public interface ISwimAnimalAction {
void swim();
}
Dog
改为
public class Dog implements IEatAnimalAction,ISwimAnimalAction {
@Override
public void eat() {
System.out.println("狗 吃饭");
}
@Override
public void swim() {
System.out.println("狗 游泳");
}
}
Bird
改为
public class Bird implements IEatAnimalAction,IFlyAnimalAction {
@Override
public void eat() {
System.out.println("鸟 吃饭");
}
@Override
public void fly() {
System.out.println("鸟 飞");
}
}
这样就成功的将一个大接口,优化为分摊职责的小接口,实现类可以根据需要实现多个职能接口。
创建课程类
public class Course {
}
创建项目经理类
public class TeamLeader {
public void checkNumberOfCourse(List<Course> courseList) {
System.out.println("在线课程的数量是 :" + courseList.size());
}
}
创建老板类
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
我们仔细分析一下,其实老板并不需要知道课程的细节,只需要问一下项目经理,有多少课程,项目经理直接告诉老板有20节在线课程。而不是老板将课程列出,让项目经理统计。
我们看下UML类图
项目经理类修改为
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());
}
}
老板类
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader) {
teamLeader.checkNumberOfCourse();
}
}
这时候运行一下,结果一样。但是从UML类图上来看,是有很大的优化的。
原文:https://www.cnblogs.com/gcdd/p/12292118.html