1. 设计模式的七大原则
1.1 设计模式的目的
让程序软件具有更好的
- 代码重用性: 相同功能代码不要多次编写
- 可读性: 编程规范性, 便于他人阅读
- 可扩展性: 当需要增加新的功能时非常方便,成本低
- 可靠性: 当我们增加新的功能后,对原来的功能不能有影响
- 高内聚, 低耦合
1.2 设计模式七大原则
- 单一职责原则
- 接口隔离原则
- 依赖倒转原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成服用原则
1.2.1 单一职责原则
对类来说, 一个类应该只负责一项职责. 如果A类负责两项职责, 职责1和职责2, 那么职责1改变的时候可能会改变A类, 进而导致职责2执行错误
注意事项:
- 降低类的复杂度, 达到一个类只负责一项职责
- 提高类的可读性, 可维护性
- 降低变更引起的风险
- 通常情况下, 我们应当遵守单一职责原则, 只有逻辑足够简单才可以在代码级违反单一职责原则, 也就是说只有当类中的方法足够少的时候, 可以再方法级别保证单一职责原则
package com.test;
public class SingleResponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("飞机");
}
}
class Vehicle {
private String common = "在";
public void run(String vehicle) {
System.out.println(vehicle + common + "路上跑...");
}
public void fly(String vehicle) {
System.out.println(vehicle + common + "路上飞...");
}
}
看上面的案例,输出了汽车在路上跑 和 飞机在路上飞, 他们都用到了 “在” 现在我们要改成 汽车zai路上跑, 而飞机不变, 怎么办?
我们使用单一职责原则优化下代码…
package com.test;
public class SingleResponsibility {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
SkyVehicle skyVehicle = new SkyVehicle();
roadVehicle.run("汽车");
skyVehicle.run("飞机");
}
}
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "zai路上跑...");
}
}
class SkyVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在天上飞...");
}
}
这样子拆成了两个类就没有问题了, 可是上面也说了当逻辑足够简单是可以再方法级别遵守单一职责原则的, 我们尝试下…
package com.test;
public class SingleResponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.runRoad("汽车");
vehicle.runSky("飞机");
}
}
class Vehicle {
public void runRoad(String vehicle) {
System.out.println(vehicle + "zai路上跑...");
}
public void runSky(String vehicle) {
System.out.println(vehicle + "在天上飞...");
}
}
1.2.2 接口隔离原则 (Interface Segregation Principle)
介绍:
客户端不应该依赖它不需要的接口, 即一个类对另一个类的依赖应该建立在最小接口上
现在有一段代码如下:
/**
* 接口中有三个方法
*/
interface interface1 {
void method1();
void method2();
void method3();
}
/**
* A类实现接口并重写全部的方法
*/
class A implements interface1 {
@Override
public void method1() {
System.out.println("A重写了方法1...");
}
@Override
public void method2() {
System.out.println("A重写了方法2...");
}
@Override
public void method3() {
System.out.println("A重写了方法3...");
}
}
/**
* B类实现了接口 重写了全部的方法
*/
class B implements interface1 {
@Override
public void method1() {
System.out.println("B重写了方法1...");
}
@Override
public void method2() {
System.out.println("B重写了方法2...");
}
@Override
public void method3() {
System.out.println("B重写了方法3...");
}
}
/**
* C类通过接口依赖A的方法1
*/
class C {
public void m1(interface1 i) {
i.method1();
}
}
/**
* D类通过接口依赖B类的方法2
*/
class D {
public void m2(interface1 i) {
i.method2();
i.method3();
}
}
上面的代码是不符合接口隔离原则的, 那我们怎么优化呢? 将接口拆成多个…
/**
* 接口中有三个方法
*/
interface interface1 {
void method1();
}
interface interface2 {
void method2();
void method3();
}
/**
* A类实现接口并重写全部的方法
*/
class A implements interface1 {
@Override
public void method1() {
System.out.println("A重写了方法1...");
}
}
/**
* B类实现了接口 重写了全部的方法
*/
class B implements interface2 {
@Override
public void method2() {
System.out.println("B重写了方法2...");
}
@Override
public void method3() {
System.out.println("B重写了方法3...");
}
}
/**
* C类通过接口依赖A的方法1
*/
class C {
public void m1(interface1 i) {
i.method1();
}
}
/**
* D类通过接口依赖B类的方法2
*/
class D {
public void m2(interface2 i) {
i.method2();
i.method3();
}
}
1.2.3 依赖倒转原则 (Denpendence Inversion Principle)
- 高层模块不应该依赖低层模块, 二者都应该依赖其抽象
- 抽象不应该依赖细节, 细节应该依赖抽象
- 核心思想: 依赖接口编程
- 设计理念: 相对于细节的多边性, 抽象的东西要稳定的多, 以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多. 在java中抽象指的是接口或抽象类, 细节就是具体的实现类
- 使用接口或者抽象类的目的是制定好规范, 而不涉及具体的操作, 细节的东西交给实现类去做
注意事项:
- 低层模块尽量有接口或者抽象类, 或者两者都有, 程序的稳定性更好
- 变量的声明类型尽量使抽象类或者接口, 这样变量的引用和实际的对象之间就有一个缓存层利于程序的扩展和优化
- 继承时遵循里氏替换原则
看下面的案例:
package com.test;
public class Inversion {
public static void main(String[] args) {
People people = new People();
people.receive(new Email());
}
}
/**
* 电子邮件类
*/
class Email {
public String getInfo() {
return "hello email ...";
}
}
/**
* 人类
* 直接依赖了Email类, 这样不方便扩展, 如果以后要接受微信的消息怎么办...
*/
class People {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
很明显, 上面的代码是不方便扩展的, 我们使用依赖倒转原则优化下…
package com.test;
public class Inversion {
public static void main(String[] args) {
People people = new People();
people.receive(new Email());
}
}
/**
* 接收消息的抽象, 所有的接收消息类全部实现这个抽象
*/
interface Ireceiver {
String getinfo();
}
/**
* 电子邮件类 实现 Ireceiver
*/
class Email implements Ireceiver {
@Override
public String getinfo() {
return "hello email ...";
}
}
/**
* 不直接依赖于Email, 现在直接依赖于它的抽象Ireceiver
*/
class People {
public void receive(Ireceiver receiver) {
System.out.println(receiver.getinfo());
}
}
1.2.3.1 依赖关系传递的三种方式
- 接口传递
- 构造方法传递
- 通过setter方法传递
1.2.4 里氏替换原则 (Liskov Substitution Principle)
oo中(面向对象)继承的说明:
- 继承中包含这样一层含义: 父类中凡是已经实现好的方法实际上是在设定规范和契约, 虽然不强制要求子类去遵守, 但是如果子类随意对这些已经实现好的方法进行修改就会破坏整个集成体系
- 继承在给程序设计带来便利的同时也带来了弊端. 比如会给程序带来侵入性, 程序的可移植性降低, 增加对象之间的耦合性. 如一个类被其他类所继承, 那么我们在修改这个类的时候要考虑到所有子类, 并且这个类修改之后子类都有可能会出现故障
- 正确的使用继承: 里氏替换原则
里氏替换原则介绍:
4. 如果每个类型为T1的对象o1 都有每个类型为T2的对象o2, 使在以T1定义的所有程序P中将对象o1替换成对象o2时,程序P的行为没有发生变化, 那么类型T2是类型T1的子类型. 也就是说所有引用基类的地方必须能透明的使用其子类对象.
5. 使用继承时, 尽量遵循里氏替换原则, 子类尽量不要重写父类的方法
6. 如果你迫不得已非要重写父类的方法, 可以通过聚合 组合 依赖来解决问题
看下面的例子:
/**
* AA类里面有方法计算两个数的差
*/
class AA {
public int method1(int x, int y) {
return x - y;
}
}
/**
* BB 重写了AA, 增加了方法2
*/
class BB extends AA {
@Override
public int method1(int x, int y) {
return x - y;
}
public int method2(int x, int y) {
return method1(x, y) + 9;
}
}
上面AA中就一个方法, BB给人家重写了, 那么还要AA干啥, 按照里氏替换原则优化下…
/**
* 写一个抽象, 让AA和BB去实现
*/
interface Base {
int method1(int x, int y);
}
/**
* AA实现Base
*/
class AA implements Base{
public int method1(int x, int y) {
return x - y;
}
}
/**
* BB实现Base 组合AA
*/
class BB implements Base {
AA aa = new AA(); // 组合AA使用的
@Override
public int method1(int x, int y) {
return x - y;
}
public int method2(int x, int y) {
return aa.method1(x, y) + 9;
}
}
1.2.5 开闭原则 (Open Closed Principle)
- 开闭原则是最基础最重要的设计原则
- 一个软件实体 如类 模块 函数应该对扩展开放(对提供方), 对修改关闭(对使用方). 用抽象构造框架, 用实现扩展细节
- 当软件需要变化的时候, 尽量通过扩展软件实体的行为来实现变化, 而不是通过修改已有的代码
- 编程中遵循其他的原则, 以及使用设计模式的的目的就是遵循开闭原则
看下面的案例:
package com.test;
public class Ocp {
public static void main(String[] args) {
GraphEditor graphEditor = new GraphEditor();
graphEditor.drawSharp(new Circle());
graphEditor.drawSharp(new Rectangle());
}
}
/**
* 用于绘图的类
*/
class GraphEditor {
public void drawSharp(Sharp sharp) {
if (sharp.m_type == 1) {
drawCircle();
} else {
drawRectangle();
}
}
public void drawCircle() {
System.out.println("画圆形...");
}
public void drawRectangle() {
System.out.println("画正方形...");
}
}
/**
* 基类
*/
class Sharp {
int m_type;
}
/**
* 圆形
*/
class Circle extends Sharp {
Circle() {
super.m_type = 1;
}
}
/**
* 方形
*/
class Rectangle extends Sharp {
public Rectangle() {
super.m_type = 2;
}
}
上面的代码实际上是有问题的, 现在的代码支持构建圆形和正方形, 那么我们现在想扩展功能, 我们要绘制三角形我们要怎么办呢?
- 需要新建一个三角形实体
- 更改GraphEditor的代码 就相当于使用方
- 更改main方法
所以这就违反了OCP原则…
优化如下:
package com.test;
public class Ocp {
public static void main(String[] args) {
GraphEditor graphEditor = new GraphEditor();
graphEditor.drawSharp(new Circle());
graphEditor.drawSharp(new Rectangle());
}
}
/**
* 用于绘图的类
*/
class GraphEditor {
public void drawSharp(Sharp sharp) {
sharp.draw();
}
}
/**
* 基类
*/
abstract class Sharp {
abstract void draw();
}
/**
* 圆形
*/
class Circle extends Sharp {
@Override
void draw() {
System.out.println("画圆形...");
}
}
/**
* 方形
*/
class Rectangle extends Sharp {
@Override
void draw() {
System.out.println("画正方形...");
}
}
这样子我们需要扩展的时候需要:
- 增加三角形的实体
- 更改main方法
真正的使用方 也就是GraphEditor类不需要改变…
1.2.6 迪米特法则 (Demeter Principle)
- 一个对象应该和其他对象保持最少的了解
- 类与类的关系越密切, 耦合度越大
- 迪米特法则 又叫最少知道原则, 即一个类对自己依赖的类回到的越少越好. 也就是说对于被依赖的类不管多么的复杂, 都尽量封装在类的内部, 对外出来提供public方法外, 不泄露任何信息
- 迪米特法则还有更直接的定义: 只与直接的朋友通信
- 直接朋友: 每个对象都会与其他对象有耦合关系, 只要两个对象之间有耦合关系就说是朋友关系. 耦合的方式很多依赖 关联 组合 聚合等. 我们称出现在成员变量 方法参数 方法返回值中的类为直接朋友. 局部变量中的类不是直接朋友. 也就是说陌生的类尽量不要以局部变量的方式出现在类的内部…
注意事项:
- 核心是降低类之间的耦合
- 但是注意: 由于每个类减少了没必要的依赖, 因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是完全没有依赖
查看案例: 打印学校总部 和 分布所有的员工ID
package com.liyang;
import java.util.ArrayList;
import java.util.List;
// 客户端
public class Demeter {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
// 学校总部员工类
class Employee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 学院员工类
class CollegeEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 管理学院员工类
class CollegeManager {
public List<CollegeEmployee> getAllEmployee() {
ArrayList<CollegeEmployee> arrayList = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {
CollegeEmployee collegeEmployee = new CollegeEmployee();
collegeEmployee.setId("ID:" + i);
arrayList.add(collegeEmployee);
}
return arrayList;
}
}
// 管理学校总部员工类
class SchoolManager {
public List<Employee> getAllEmployee() {
ArrayList<Employee> arrayList = new ArrayList<Employee>();
for (int i = 0; i < 10; i++) {
Employee employee = new Employee();
employee.setId("ID:" + i);
arrayList.add(employee);
}
return arrayList;
}
void printAllEmployee(CollegeManager collegeManager) {
List<CollegeEmployee> allEmployee = collegeManager.getAllEmployee();
System.out.println("学院员工...");
for (CollegeEmployee collegeEmployee : allEmployee) {
System.out.println(collegeEmployee.getId());
}
List<Employee> allEmployee2 = this.getAllEmployee();
System.out.println("总部员工...");
for (Employee employee : allEmployee2) {
System.out.println(employee.getId());
}
}
}
分析上面的SchoolManager类, Employee, CollegeManager是直接朋友关系, CollegeEmployee就不是直接朋友关系 这样子就违反了迪米特法则…
package com.liyang;
import java.util.ArrayList;
import java.util.List;
// 客户端
public class Demeter {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
// 学校总部员工类
class Employee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 学院员工类
class CollegeEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 管理学院员工类
class CollegeManager {
public List<CollegeEmployee> getAllEmployee() {
ArrayList<CollegeEmployee> arrayList = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {
CollegeEmployee collegeEmployee = new CollegeEmployee();
collegeEmployee.setId("ID:" + i);
arrayList.add(collegeEmployee);
}
return arrayList;
}
void printAllEmployee() {
List<CollegeEmployee> allEmployee = this.getAllEmployee();
System.out.println("学院员工...");
for (CollegeEmployee collegeEmployee : allEmployee) {
System.out.println(collegeEmployee.getId());
}
}
}
// 管理学校总部员工类
class SchoolManager {
public List<Employee> getAllEmployee() {
ArrayList<Employee> arrayList = new ArrayList<Employee>();
for (int i = 0; i < 10; i++) {
Employee employee = new Employee();
employee.setId("ID:" + i);
arrayList.add(employee);
}
return arrayList;
}
void printAllEmployee(CollegeManager collegeManager) {
collegeManager.printAllEmployee();
List<Employee> allEmployee2 = this.getAllEmployee();
System.out.println("总部员工...");
for (Employee employee : allEmployee2) {
System.out.println(employee.getId());
}
}
}
1.2.7 合成复用原则(Composite Reuse Principle)
尽量使用合成/聚合的方式, 而不是继承
1.3 设计原则核心思想
- 找出应用中可能需要变化的地方, 把他们独立出来, 不要和那些不需要变化的代码混在一起
- 针对接口编程而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力
2. 设计模式类型
- 创建型模式: 单例 抽象工厂 原型 建造者 工厂
- 结构型模式: 适配器 桥接 装饰者 组合 外观 享元 代理
- 型卫星模式: 模板方法 命令 访问者 迭代器 观察者 中介者 备忘录 解释器 状态 策略 职责链