文章目录
本篇博客主要是学习 韩顺平_Java设计模式 做一个学习笔记使用
设计模式的作用
- 代码重用性 (即:相同功能的代码,不用多次编写)
- 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
- 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现高内聚,低耦合的特性
设计模式包含了面向对象的精髓, “懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”
设计模式常用的七大原则(6+1):
- 单一职责原则
- 接口隔离原则
- 依赖倒转(倒置)原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
1. 单一职责原则(通常对于类级别而言)
对类来说的,即一个类应该只负责一项职责,如类 A 负责两个不同职责:职责 1 ,职责 2,当职责 1 需求变更 而改变 A 时,可能造成职责 2 执行错误,所以需要将类 A 的粒度分解为 A1,A2
…
简单的理解就是: 按照实际的功能切分成不同的类
问题的引入
/**
* 单一职责
*
* @author houyu
* @createTime 2019/11/10 14:31
*/
public class Demo1 {
/**
* 交通工具类
*/
public static class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在公路上跑...");
}
}
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("火车");
/*
汽车 在公路上跑...
火车 在公路上跑...
分析问题:
Demo1 违反了单一职责原则
因为汽车在公路上跑可以,但是火车就不是在公路上跑了呀,
解决问题:
1. 根据交通工具运行方法不同,分解成不同方法即可(Demo2) [类简单的活可以,但是一般情况下不推荐]
2. 根据交通工具运行方法不同,分解成不同类即可(Demo3) [推荐]
*/
}
}
解决问题1
/**
* 单一职责
*
* @author houyu
* @createTime 2019/11/10 14:31
*/
public class Demo2 {
/**
* 交通工具类
*/
public static class Vehicle {
public void runRoad(String vehicle) {
System.out.println(vehicle + " 在公路上跑...");
}
public void runTrack(String vehicle) {
System.out.println(vehicle + " 在轨道上跑...");
}
}
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.runRoad("汽车");
vehicle.runTrack("火车");
/*
解决问题:
1. 根据交通工具运行方法不同,分解成不同方法即可(Demo2) [类简单的活可以,但是一般情况下不推荐]
*/
}
}
解决问题2
/**
* 单一职责
*
* @author houyu
* @createTime 2019/11/10 14:31
*/
public class Demo3 {
/**
* 公路交通工具类
*/
public static class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在公路上跑...");
}
}
/**
* 轨道交通工具类
*/
public static class TrackVehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在轨道上跑...");
}
}
public static void main(String[] args) {
RoadVehicle vehicle = new RoadVehicle();
vehicle.run("汽车");
TrackVehicle trackVehicle = new TrackVehicle();
vehicle.run("火车");
/*
解决问题:
2. 根据交通工具运行方法不同,分解成不同类即可(Demo3) [推荐]
*/
}
}
注意事项和细节
- 降低类的复杂度,一个类只负责一项职责.
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中 方法数量足够少,可以在方法级别保持单一职责原则
2. 接口隔离原则
客户端(距离实现层)不应该依赖它不需要的接口(方法) , 即一个类对另一个类的依赖应该建立在最小的接口上
…
简单的理解就是: (指的是类的方法) 如果一个 Interface 有3个方法,其中一个A类如果需要 Interface 的2个方法, 一个B类需要 Interface 的2个方法, 那么就不满足接口接口隔离原则( 不应该依赖它不需要的接口,方法 )
问题的引入
/**
* 接口隔离原则(指的是类的方法)
*
* @author houyu
* @createTime 2019/11/10 15:26
*/
public class Demo1 {
/**
* 定义一个 interface
*/
public interface MyInterface {
void method1();
void method2();
void method3();
}
/**
* MyInterface 的实现1
*
* 实质上只需要 method1() 和 method2()
*
*/
public static class MyInterfaceImpl1 implements MyInterface {
@Override
public void method1() {
System.out.println("MyInterfaceImpl1.method1()");
}
@Override
public void method2() {
System.out.println("MyInterfaceImpl1.method2()");
}
/** 这个方法是没有用到的 */
@Override
public void method3() {
}
}
/**
* MyInterface 的实现2
*
* 实质上只需要 method1() 和 method3()
*
*/
public static class MyInterfaceImpl2 implements MyInterface {
@Override
public void method1() {
System.out.println("MyInterfaceImpl2.method1()");
}
/** 这个方法是没有用到的 */
@Override
public void method2() {
}
@Override
public void method3() {
System.out.println("MyInterfaceImpl2.method3()");
}
}
}
解决问题
/**
* 接口隔离原则(指的是类的方法)
*
* 解决问题:
* 将接口 MyInterface 拆分为独立的几个接口,类 MyInterfaceImpl1 和类 MyInterfaceImpl2 分别与他们需要的接口建立依赖关系.也就是采用接口 隔离原则
* 接口 MyInterface 中出现的方法,根据实际情况拆分为三个接口
*
* public interface MyInterface {
* void method1();
* void method2();
* void method3();
* }
*
* 变成下面的3个接口:
*
* public interface BaseInterface {
* void method1();
* }
*
* public interface MyInterface1 extends BaseInterface {
* void method2();
* }
*
* public interface MyInterface2 extends BaseInterface {
* void method3();
* }
*
* @author houyu
* @createTime 2019/11/10 15:26
*/
public class Demo2 {
/**
* 定义一个 interface ( 基础 )
*/
public interface BaseInterface {
void method1();
}
/**
* 定义一个 interface (继承 基础 的1)
*/
public interface MyInterface1 extends BaseInterface {
void method2();
}
/**
* 定义一个 interface (继承 基础 的2)
*/
public interface MyInterface2 extends BaseInterface {
void method3();
}
/**
* MyInterface 的实现1
*
* 实质上只需要 method1() 和 method2()
*
*/
public static class MyInterfaceImpl1 implements MyInterface1 {
@Override
public void method1() {
System.out.println("MyInterfaceImpl1.method1()");
}
@Override
public void method2() {
System.out.println("MyInterfaceImpl1.method2()");
}
}
/**
* MyInterface 的实现2
*
* 实质上只需要 method1() 和 method3()
*
*/
public static class MyInterfaceImpl2 implements MyInterface2 {
@Override
public void method1() {
System.out.println("MyInterfaceImpl2.method1()");
}
@Override
public void method3() {
System.out.println("MyInterfaceImpl2.method3()");
}
}
}
3. 依赖倒转原则
基本介绍 依赖倒转原则(Dependence Inversion Principle)是指:
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多.以抽象为基础搭建的架 构比以细节为基础的架构要稳定的多.在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
…简单的理解就是: 比如一个方法需要传递一个类参数进来,参数类型优先不应该写具体的实现类, 而是应该写具体实现类的抽象层(抽象类或者接口), 这样子这个方法就可以更加灵活。
问题的引入
public class Demo1 {
/**
* 邮件信息
*/
public static class Email {
public String getInfo() {
return "电子邮件: hello world";
}
}
/**
* 人物
*/
public static class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
/**
* 分析
* 1. 简单,比较容易想到
* 2. 如果我们获取的对象是 微信,短信等等,则新增类,同时 Person 也要增加相应的接收方法
* 3. 解决思路:引入一个抽象的接口 IReceiver, 表示接收者, 这样 Person 类与接口 IReceiver 发生依赖
* 因为 Email, WeiXin 等等属于接收的范围,他们各自实现 IReceiver 接口就 ok, 这样我们就符号依赖倒转原则
*/
}
}
解决问题
/**
* @author houyu
* @createTime 2019/11/10 15:59
*/
public class Demo2 {
/**
* 定义一个抽象层 如果接受的信息实现 IReceive 即可
*/
public interface IReceive {
String getInfo();
}
/**
* 邮件信息
*/
public static class Email implements IReceive {
@Override
public String getInfo() {
return "电子邮件: hello world";
}
}
/**
* 微信信息
*/
public static class Wechat implements IReceive {
@Override
public String getInfo() {
return "微信信息: hello world";
}
}
/**
* 人物
*/
public static class Person {
public void receive(IReceive receive) {
System.out.println(receive.getInfo());
}
}
public static void main(String[] args) {
Person person = new Person();
// 接受邮件信息
person.receive(new Email());
// 接受微信信息
person.receive(new Wechat());
// 电子邮件: hello world
// 微信信息: hello world
}
}
依赖关系传递的三种方式和应用案例
-
接口传递(方法参数传递)
public interface IReceive { String getInfo(); } public static class Email implements IReceive { @Override public String getInfo() { return "电子邮件: hello world"; } } public static class Person { /** 在这里体现了 接口传递(方法参数传递) */ public void receive(IReceive receive) { System.out.println(receive.getInfo()); } } public static void main(String[] args) { Person person = new Person(); // 接受邮件信息 person.receive(new Email()); }
-
构造方法传递
public interface IReceive { String getInfo(); } public static class Email implements IReceive { @Override public String getInfo() { return "电子邮件: hello world"; } } public static class Person { private IReceive receive; /** 在这里体现了 构造参数传递 */ public Person(IReceive receive) { this.receive = receive; } public void receive() { System.out.println(receive.getInfo()); } } public static void main(String[] args) { Person person = new Person(new Email()); // 接受邮件信息 person.receive(); }
-
setter 方式传递
public interface IReceive { String getInfo(); } public static class Email implements IReceive { @Override public String getInfo() { return "电子邮件: hello world"; } } public static class Person { private IReceive receive; /** 在这里体现了 setter 传递 */ public void setReceive(IReceive receive) { this.receive = receive; } public void receive() { System.out.println(receive.getInfo()); } } public static void main(String[] args) { Person person = new Person(); person.setReceive(new Email()); // 接受邮件信息 person.receive(); }
注意事项和细节
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
- 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序 扩展 和优化
- 继承时遵循里氏替换原则
4. 里氏替换原则
OO 中的继承性的思考和说明
-
继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有 的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
-
继承在给程序设计带来便利的同时,也带来了弊端.比如使用继承会给程序带来侵入性,程序的可移植性降低, 增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且 父类修改后,所有涉及到子类的功能都有可能产生故障
-
问题提出:在编程中,如何正确的使用继承? => 里氏替换原则
基本介绍:
- 里氏替换原则(Liskov Substitution Principle)在 1988 年,由麻省理工学院的以为姓里的女士提出的.
- 如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都 代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型.换句话说,所有引用基类的地方必须能透明地使用其子类的对象.
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
- 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过 “聚合”, “组合”, “依赖” 来 解决问题
…
简单的理解就是: 尽量不要重写父类的方法,使用 “聚合”, “组合”, “依赖” 来 解决
问题的引入
/**
* @author houyu
* @createTime 2019/11/10 17:58
*/
public class Demo1 {
/**
* 定义一个类 NumberTool
*/
public static class NumberTool {
public int add(int num1, int num2) {
return num1 + num2;
}
}
/**
* 定义 NumberTool2 继承 NumberTool
*/
public static class NumberTool2 extends NumberTool {
/**
* 不小心重写了 NumberTool add(), 可能就是无意识的, 因此就会导致一些列问题
*/
@Override
public int add(int num1, int num2) {
return num1 + num2 + num2;
}
public int subtract(int num1, int num2) {
return num1 - num2;
}
}
public static void main(String[] args) {
NumberTool numberTool = new NumberTool();
int add = numberTool.add(1, 2);
// add = 3
System.out.println("add = " + add);
//
NumberTool2 numberTool2 = new NumberTool2();
add = numberTool2.add(1, 2);
// add = 5 (这个并不是我们希望看到的)
System.out.println("add = " + add);
int subtract = numberTool2.subtract(1, 2);
// subtract = -1
System.out.println("subtract = " + subtract);
/*
* 分析问题
* 我们发现原来运行正常的相减功能发生了错误.原因就是类 NumberTool2 无意中重写了父类的方法,造成原有功能出现错误
* 在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,
* 但整个继承体系的复用性会比较差.特别是运行多态比较频繁的时候
*/
}
}
解决问题
public class Demo2 {
/**
* 解决问题:
*
* 原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等 关系代替.
*/
/**
* 定义一个基类
*/
public static class BaseTool {
// 把更加基础的方法和成员写到 Base 类
}
/**
* 定义一个类 NumberTool
*/
public static class NumberTool extends BaseTool {
public int add(int num1, int num2) {
return num1 + num2;
}
}
/**
* 定义 NumberTool2
*/
public static class NumberTool2 extends BaseTool {
private NumberTool numberTool = new NumberTool();
/**
* 依然想使用 NumberTool 的 add(),
*/
public int add(int num1, int num2) {
return numberTool.add(num1, num2);
}
public int subtract(int num1, int num2) {
return num1 - num2;
}
}
public static void main(String[] args) {
NumberTool numberTool = new NumberTool();
int add = numberTool.add(1, 2);
// add = 3
System.out.println("add = " + add);
//
NumberTool2 numberTool2 = new NumberTool2();
add = numberTool2.add(1, 2);
// add = 3
System.out.println("add = " + add);
int subtract = numberTool2.subtract(1, 2);
// subtract = -1
System.out.println("subtract = " + subtract);
}
}
5. 开闭原则
基本介绍
- 开闭原则(Open Closed Principle)是编程中最基础,最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方).用抽象构建框架,用实现扩展细节.
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化.
- 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则.
…
简单的理解就是: 使用者调用抽象层,具体实现者继承抽象层进行实现,那样子使用者就不用修改代码就可以进行扩展
问题的引入
/**
* @author houyu
* @createTime 2019/11/10 18:28
*/
public class Demo1 {
/**
* 定义一个形状 基类
*/
public static abstract class Shape {
int type;
}
/**
* 矩形
*/
public static class Rectangle extends Shape {
public Rectangle() {
super.type = 1;
}
}
/**
* 圆形
*/
public static class Circle extends Shape {
public Circle() {
super.type = 2;
}
}
/**
* 图形编辑器, (使用方)
*/
public static class GraphicEditor {
public void draw(Shape shape) {
if (shape.type == 1) {
System.out.println("绘制矩形");
} else if (shape.type == 2) {
System.out.println("绘制圆形");
}
}
}
public static void main(String[] args) {
GraphicEditor editor = new GraphicEditor();
editor.draw(new Rectangle());
editor.draw(new Circle());
/**
* 绘制矩形
* 绘制圆形
*
* 分析问题:
* 这里有一个问题,就是随着时间的推移,可能 GraphicEditor 需要绘制的东西越来越多了, 那怎么办?
* 那就需要不断的扩张 draw() 方法了, 添加else if 等一系列的代码了
*
* 这里就违背了开闭原则了(使用者就不用修改代码就可以进行扩展)
*
*
* 结局问题:
* 把创建 Shape 类做成抽象类,并提供一个抽象的 draw 方法,让子类去实现即可,
* 这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可,使用方的代码就不需要修 -> 满足了开闭原则
*/
}
}
解决问题
public class Demo2 {
/**
* 定义一个形状 基类
*/
public static abstract class Shape {
int type;
/**
* 定义抽象方法, 要求继承的子类去实现
*/
public abstract void draw();
}
/**
* 矩形
*/
public static class Rectangle extends Shape {
public Rectangle() {
super.type = 1;
}
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
/**
* 圆形
*/
public static class Circle extends Shape {
public Circle() {
super.type = 2;
}
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
/**
* 图形编辑器, (使用方)
*/
public static class GraphicEditor {
public void draw(Shape shape) {
shape.draw();
}
}
public static void main(String[] args) {
GraphicEditor editor = new GraphicEditor();
editor.draw(new Rectangle());
editor.draw(new Circle());
}
}
注意事项
- 这里的 开闭原则 和 依赖倒转原则 实则是很类似的一个东西
6. 迪米特法则(最少知道原则)
基本介绍
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好.也就是说,对于 被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部.对外除了提供的 public 方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
- 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间 是朋友关系.耦合的方式很多,依赖,关联,组合,聚合等.其中,我们称出现成员变量,方法参数,方法返 回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友.也就是说,陌生的类最好不要以局部变量(方法内部)的形式出现在类的内部.
…
简单的理解就是: 自己的事情自己做,不要暴露过多的东西,也不要过多的引入别人的东西,否则别人变你就要变,你说惨不惨!
问题的引入
import java.util.ArrayList;
import java.util.List;
/**
* @author houyu
* @createTime 2019/11/10 20:18
*/
public class Demo1 {
/**
* 需求是:
* 公司需要获取所有的员工信息
*
* 员工 => 项目 => 公司
*/
/**
* 员工类
*/
public static class Personnel {
private String id;
public Personnel(String id) {
this.id = id;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Personnel{");
sb.append("id='").append(id).append('\'');
sb.append('}');
return sb.toString();
}
}
/**
* 项目管理者
*/
public static class CommunityManager {
public List<Personnel> findAll() {
List<Personnel> list = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
list.add(new Personnel("community1_" + i));
}
return list;
}
}
/**
* 公司管理者
*/
public static class CompanyManager {
public void printAll(CommunityManager communityManager) {
List<Personnel> list = communityManager.findAll();
list.forEach(System.out::println);
}
}
public static void main(String[] args) {
CompanyManager companyManager = new CompanyManager();
companyManager.printAll(new CommunityManager());
/**
* 分析问题:
* 在演示的代码中, CompanyManager中的printAll()方法中
* 参数: communityManager是直接朋友,
* 内部的局部变量 List<Personnel> list 中的 Personnel 是陌生朋友
*
* 考虑一种情况, 如果 CommunityManager 中 findAll() 方法返回的方法改变的话
* 那么 CompanyManager 也需要调整, 哪怕是 findAll() 返回一个Set, CommunityManager都需要调整代码适配
*
* 因此这个案例中不符合迪米特法则(最少知道原则)
*
*/
}
}
解决问题
import java.util.ArrayList;
import java.util.List;
/**
* @author houyu
* @createTime 2019/11/10 20:18
*/
public class Demo2 {
/**
* 需求是:
* 公司需要获取所有的员工信息
*
* 员工 => 项目 => 公司
*/
/**
* 员工类
*/
public static class Personnel {
private String id;
public Personnel(String id) {
this.id = id;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Personnel{");
sb.append("id='").append(id).append('\'');
sb.append('}');
return sb.toString();
}
}
/**
* 项目管理者
*/
public static class CommunityManager {
public List<Personnel> findAll() {
List<Personnel> list = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
list.add(new Personnel("community1_" + i));
}
return list;
}
public void printAll() {
List<Personnel> list = this.findAll();
list.forEach(System.out::println);
}
}
/**
* 公司管理者
*/
public static class CompanyManager {
public void printAll(CommunityManager communityManager) {
communityManager.printAll();
}
}
public static void main(String[] args) {
CompanyManager companyManager = new CompanyManager();
companyManager.printAll(new CommunityManager());
}
}
注意事项和细节
- 迪米特法则的核心是降低类之间的耦合
- 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是 要求完全没有依赖关系
7. 复合复用原理(Composite Reuse Principle)
类于类的依赖尽量使用关联,组合,聚合的方式,而不是使用继承
…
简单的理解就是: 能不继承添加关系就尽量少继承,尽量通过成员变量(类变量)等形式添加依赖,继承太多难以维护
设计原则核心思想
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起.
- 针对接口编程,而不是针对实现编程.
- 为了交互对象之间的松耦合设计而努力