# 设计模式的七大原则
23种设计模式遵循的原则
1. 单一职责原则。
2. 接口隔离原则。
3. 依赖倒转(倒置)原则。
4. 里氏替换原则。
5. 开闭原则ocp。
6. 迪米特法原则。
7. 合成复用原则。
达到目的:
1. 代码重用性:相同功能的代码,不用多次编写。
2. 可读性:编程规范,便于其他程序员阅读。
3. 可扩展性:添加新功能时方便。
4. 可靠性:添加新功能后对原功能没有影响。
5. 高内聚,低耦合。
## 单一职责原则
单一职责原则指的是对类来说,一个类只负责一项职责,例如类A负责两个不同的职责1和职责2,修改职责1的代码会导致职责2执行出错,这个时候就要将类A分解成类职责1和类职责2(粒度分解为A1和A2)。也可以从方法的级别分解不同的职责。
举例如下:
`SingleReposibility1.java`
~~~
zpackage cn.net.smrobot.design_principle;
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("公交");
vehicle.run("汽车");
vehicle.run("飞机");
}
}
/**
* 方式1:这种方法违反了单一职责原则
* 例如传入“飞机”等不应该用run来执行
* 解决方法:根据不同的交通工具,分解成不同的类
*/
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上跑");
}
}
~~~
这种方式导致不同种类的对象调用了同一个方法,例如飞机并不能适用于run方法。
> 改进方式:根据不同的交通工具,分解成不同的工具类
`SingleResponsibility2.java`
~~~
package cn.net.smrobot.design_principle;
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("摩托车");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
WaterVehicle waterVehicle = new WaterVehicle();
waterVehicle.run("潜艇");
}
}
/**
* 方案二:遵守了单一职责原则
* 但是改动大,将类分解的同时修改了客户端
* 改进:直接修改Vehicle类,而不修改其他类
*/
class RoadVehicle{
public void run(String vehicle) {
System.out.println(vehicle + "在公路上跑...");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在天空上飞...");
}
}
class WaterVehicle{
public void run(String vehicle) {
System.out.println(vehicle + "在水里游...");
}
}
~~~
这种方式就遵循了`类级别`的单一职责原则,但是由于修改添加了太多的类,改动太大。
> 改进方法:直接修改Vehicle类,而不用添加其他类
`SingleResponsibility3.java`
~~~
package cn.net.smrobot.design_principle;
public class SingleResponsibility3 {
public static void main(String[] args) {
}
}
/**
* 方式3:没有对原来的类进行大修改,只是增加了方法
* 类级别上遵守单一职责原则,但是在方法上遵守了单一职责原则
*/
class Vehicle1 {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上跑");
}
public void runAir(String vehicle) {
System.out.println(vehicle + "在天空飞...");
}
public void runWater(String vehicle) {
System.out.println(vehicle + "在水里游...");
}
}
~~~
这种方法通过增加方法的方式,在`方法级别`上遵守了单一职责原则,但是类级别上遵守单一职责原则.
**总结**
1. 单一职责原则可以降低类的复杂度。
2. 提高代码的可读性,降低变更代码引起的风险。
3. 如果类的逻辑简单,类中方法数量足够上,可以在方法级别上保证单一职责原则(`静态工具类`)。
## 接口隔离原则
Interface Segregation Principle
接口隔离原则指的是客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
大致意思就是当一个类实现一个接口时,如果接口中的一些方法并不会通过该类的依赖被使用到,那么就不要去实现该接口的所有方法了,而是将该接口分解成多个更小的接口,使得实现这些接口的中的方法都会被使用到。
举例:
`InterfaceSegregation1.java`
~~~
package cn.net.smrobot.design_principle.interfaceSegregation;
/**
* 接口隔离原则
*/
public class InterfaceSegregation1 {
public static void main(String[] args) {
}
}
interface Interface1 {
void method1();
void method2();
void method3();
void method4();
void method5();
}
/**
* B实现接口
*/
class B implements Interface1 {
@Override
public void method1() {
System.out.println("B:method1");
}
@Override
public void method2() {
System.out.println("B:method2");
}
@Override
public void method3() {
System.out.println("B:method3");
}
@Override
public void method4() {
System.out.println("B:method4");
}
@Override
public void method5() {
System.out.println("B:method5");
}
}
/**
* D也实现接口
*/
class D implements Interface1{
@Override
public void method1() {
System.out.println("D:method1");
}
@Override
public void method2() {
System.out.println("D:method2");
}
@Override
public void method3() {
System.out.println("D:method3");
}
@Override
public void method4() {
System.out.println("D:method4");
}
@Override
public void method5() {
System.out.println("D:method5");
}
}
/**
* A类通过接口依赖(使用)B类,但只会使用其中的1,2,3方法
*/
class A {
public void depend1(Interface1 interface1) {
interface1.method1();
}
public void depend2(Interface1 interface1) {
interface1.method2();
}
public void depend3(Interface1 interface1) {
interface1.method3();
}
}
/**
* C 类通过接口依赖类D,但是只是会使用其中的1,4,5个方法
*/
class C {
public void depend1(Interface1 interface1) {
interface1.method1();
}
public void depend2(Interface1 interface1) {
interface1.method4();
}
public void depend3(Interface1 interface1) {
interface1.method5();
}
}
~~~
> 因此需要将接口拆分成多个子接口
## 依赖倒转原则
`Dependence Inversion Principle`
> 1. 高层模块不要依赖底层模块,二者都应该依赖其抽象
>
> 2. 抽象不应该依赖细节,细节应该依赖抽象
>
> 3. 中心思想就是面向接口编程
>
>
> 在java中,抽象指的是接口或者是抽象类,细节是对接口或抽象的实现
~~~
public class DependenceInversion1 {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email {
public String getInfo() {
return "电子邮件信息:hello,world!";
}
}
/**
* 依赖的方法1:
* 缺点:如果是其他信息来源(WeiXin),receive方法将不能使用
* 改进方法:引入一个抽象的接口IReceive,表示接受者,让Person与接口IReceive发生依赖
*/
class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
~~~
> Email参数设计为传递接口的方式
~~~
public class DependenceInversion2 {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
// 定义一个接受者接口
interface IReceive{
String getInfo();
}
// 实现接口
class Email1 implements IReceive{
@Override
public String getInfo() {
return "电子邮件信息:hello,world!";
}
}
class WeiXin implements IReceive {
@Override
public String getInfo() {
return "微信信息:hello,world!";
}
}
/**
* 依赖的方法2:将接口作为参数传递
* 依赖接口更加抽象,稳定性更好
*/
class Person1 {
public void receive(IReceive iReceive) {
System.out.println(iReceive.getInfo());
}
}
~~~
依赖关系的实现:
1. 通过接口传递实现依赖 - 多态
`上面的例子`
2. 通过构造方法实现依赖
~~~
interface IReceive {
String getInfo();
}
class Person {
private IReceive iReceive;
public Person(IReceive iReceive) { // 通过构造器传递一个已经实现好的接口实现类
this.iReceive = iReceive;
}
}
~~~
3. 通过set方法实现依赖
~~~
interface IReceive {
String getInfo();
}
class Person {
private IReceive iReceive;
// 通过set方法传递一个已经实现了接口的实现类
public setIReceive(IReceive iReceive) {
this.iReceive = iReceive;
}
}
~~~
总结:
1. 底层模块尽量都要有抽象类或接口,会使程序的稳定性更好
2. 变量声明类型尽量使用抽象类或接口,这样我们在变量引用和实际对象间,就存在一个缓冲区,有利于程序扩展和优化
3. 继承时遵循里氏替换原则
## 里氏替换原则
面向对象中继承体系的问题:
1. 增加程序之间的耦合性;
2. 如果一个父类被修改了,就要考虑其所有子类的功能,并且可能会导致所有子类的功能都故障;
`如何正确使用继承` = `尽量满足里氏替换原则`
> 如果对每个类型为T1的对象o1,都有类型T2的对象o2,使得在程序中用o2代替o1而不会对程序造成任何印象,即引用基类的地方必须能透明的使用其子类对象
* 在继承时,子类尽量不要重写父类的方法
* 在继承中实际上是程序的耦合性增高,在适当情况下,可以通过聚合,组合,依赖来解决问题。
~~~
package cn.net.smrobot.design_principle.liskov;
/**
* 里氏替换原则
*/
public class Liskov1 {
public static void main(String[] args) {
A a = new A();
System.out.println(a.fun1(10, 2));
System.out.println("------------------");
B b = new B();
System.out.println(b.fun1(10, 2));
System.out.println(b.fun2(10, 2));
}
}
class A {
public int fun1(int a, int b) {
return a - b;
}
}
class B extends A {
@Override
public int fun1(int a, int b) {
return a + b;
}
public int fun2(int a, int b) {
return fun1(a, b) + 9;
}
}
~~~
这里的fun1的方法被重写了,可能导致想要的功能不准确。
> 在实际编程过程中,我们常常会通过重写父类的方法完成新的功能,这样写会导致整个继承体系的复用性差,特别是在运行多态比较频繁的情况下。
>
> 解决方法:取出原有的继承关系,抽象一个更加基础的基类。
修改方案:
~~~
package cn.net.smrobot.design_principle.liskov;
public class Liskov2 {
public static void main(String[] args) {
}
}
// 创建一个更加基础的类
class Base {
}
class A1 extends Base{
public int fun1(int a, int b) {
return a - b;
}
}
// B类不在继承A类,如果B类要用到A类,可以用依赖,聚合等关系来替换
class B1 extends Base{
//B类使用A类中的方法
private A1 a1 = new A1();
public int fun1(int a, int b) {
return a + b;
}
public int fun2(int a, int b) {
return fun1(a, b) + 9;
}
// 用到A类中的方法
public int fun3(int a, int b) {
return a1.fun1(a, b);
}
}
~~~
总结:
1. 继承会带来程序的入侵性,降低程序的可移植性。
2. 里氏替换原则用来指导正确的使用继承关系。
## 开闭原则
Open Closed Principle
开闭原则指的是,模块和函数应该对扩展开放(提供方),对修改关闭(使用方),用抽象构建框架,用实现扩展细节。
当软件需要变化时,尽量`通过扩展`软件的实体行为来实现变化,而不是通过修改已有的代码来实现变化。
**其他原则是为了实现开闭原则**
~~~
package cn.net.smrobot.design_principle.opendClose;
/**
* 绘图类
*/
public class Ocp1 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
/**
* 绘制类
* 使用方
* 当添加绘制一个新的图形时需要在使用方这里修改代码
*/
class GraphicEditor {
public void drawShape(Shape shape) {
if (shape.type == 1) {
drawRectangle();
}
if (shape.type == 2) {
drawCircle();
}
}
public void drawRectangle() {
System.out.println("绘制矩形");
}
public void drawCircle() {
System.out.println("绘制圆形");
}
}
class Shape {
public int type;
}
class Rectangle extends Shape {
public Rectangle() {
this.type = 1;
}
}
class Circle extends Shape {
public Circle() {
this.type = 2;
}
}
~~~
> 改进思路:可以将Shape类改成抽象类,然后让子类重写各自的实现。
~~~
package cn.net.smrobot.design_principle.opendClose;
/**
* 绘图类
*/
public class Ocp1 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
/**
* 绘制类
* 直接调用draw方法,多态的思想
*/
class GraphicEditor {
public void drawShape(Shape shape) {
shape.draw();
}
}
abstract class Shape {
public int type;
protected void draw();
}
class Rectangle extends Shape {
public Rectangle() {
this.type = 1;
}
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
class Circle extends Shape {
public Circle() {
this.type = 2;
}
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Triangle extends Shape {
public Circle() {
this.type = 2;
}
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
~~~
总结:
1. 对于使用方,尽量不要修改提供方的内容,而是扩展提供方的内容。
2. 尽量使用的抽象的方式来修改程序的功能。
## 迪米特法则
Demeter Principle
> 1. 一个对象应该对其他对象保持最少的了解。
>
> 2. 又叫最少知道原则,即一个类对自己依赖的类知道得越少越好,即一个类尽量进行封装。
>
> 3. 更简单的定义:只和直接朋友通信。
>
直接朋友:
将出现在`成员变量,方法参数,方法返回值`中的类称为直接朋友,而以局部变量的方式出现的类不是直接朋友,因此别的类尽量不要以局部变量的方式出现在类的内部。
总计:
1. 迪米特法则用来降低类之间的耦合关系。
2. 只是尽量减少类之间的依赖关系,而不是完全不依赖。
## 合成复用原则
> 尽量使用合成或者聚合的方式,而不是使用继承的方式
## 总结
1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混合在一起。
2. 面向接口编程,而不是面向实现编程。
3. 主要目的是让代码达到松耦合。
## 类之间的关系
UML Unified Modeling Language 统一建模语言,是一套帮助软件系统分析和设计的语言工具。
由一系列符号(类似于数学符号)和图案组成用来表示**类之间的关系**。
由如下几种关系:
1. Dependency 依赖
2. Association 关联
3. Generalization 泛化/继承
4. Realization 实现
5. Aggregation 聚合关系,关联的一种,通过set方法引入
6. Composite 组合关系,关联关系的一种,通过成员属性A a = new A();的方法引入
**类图分类**
1. 用例图 use case
2. 静态结构图:`类图`,对象图,包图,组件图,部署图
3. 动态行为图:交互图,状态图,活动图
## 依赖关系
只要在类中用到了对方,他们之间就存在依赖关系。
:-: ![](https://img.kancloud.cn/d7/bb/d7bba041108c554faced1921cb105e6d_534x306.png)
使用的位置:
1. 类的成员属性
2. 方法的返回类型
3. 方法接收的参数类型
4. 方法中的局部变量。
## 泛化和实现关系
泛化关系:继承关系,也是依赖关系的一种特例。
:-: ![](https://img.kancloud.cn/87/a3/87a3fac7acd4ff3d865aafcb031acaff_251x262.png)
实现关系:即A类实现B接口,也是依赖关系的一种特例。
:-: ![](https://img.kancloud.cn/ea/10/ea106f5d46d2906c6aff34c756ec81a9_289x250.png)
## 关联关系
类与类之间的关系,是依赖关系的一种特例。
关联关系具有导航性:即双向关系或单向关系。
~~~
// 单向一对一关系
public class Person() {
private IDCard card;
}
public class IDCard() {}
// 双向一对一关系
public class Person() {
private IDCard card;
}
public class IDCard() {
private Person person;
}
~~~
## 聚合关系
表示整体和部分的关系,`整体和部分可以分开`,聚合关系是关联关系的特例。具有导航性和多重性。`对象的引入通过set方法或者构造方法`实现。
~~~
// 鼠标类和显示器类都可以和Computer类分开,因此是聚合关系
public class Computer{
private Mouse mouse;
private Monitor monitor;
// set方法
}
~~~
:-: ![](https://img.kancloud.cn/68/48/68481131c3d904a590f16d170ff92def_515x203.png)
## 组合关系
整体和部分是不可以分开的称为组合关系,对象的引入通过直接new实现。
~~~
// 鼠标类和显示器类在Computer一创建就会被创建,因此是不可分割的,属于组合关系
public class Computer{
private Mouse mouse = new Mouse();
private Monitor monitor = new Mouse();
}
~~~
:-: ![](https://img.kancloud.cn/33/fe/33fe059712904edc38053815ef7c016d_595x215.png)
聚合关系和组合关系是可以混合使用,主要是看整体和部分是否可以分开。
> 如果Person类中定义了对IDCard的级联删除,那么Person类和IDCard类就是组合关系。不一定直接使用new才是组合关系。
- 第一章 Java基础
- ThreadLocal
- Java异常体系
- Java集合框架
- List接口及其实现类
- Queue接口及其实现类
- Set接口及其实现类
- Map接口及其实现类
- JDK1.8新特性
- Lambda表达式
- 常用函数式接口
- stream流
- 面试
- 第二章 Java虚拟机
- 第一节、运行时数据区
- 第二节、垃圾回收
- 第三节、类加载机制
- 第四节、类文件与字节码指令
- 第五节、语法糖
- 第六节、运行期优化
- 面试常见问题
- 第三章 并发编程
- 第一节、Java中的线程
- 第二节、Java中的锁
- 第三节、线程池
- 第四节、并发工具类
- AQS
- 第四章 网络编程
- WebSocket协议
- Netty
- Netty入门
- Netty-自定义协议
- 面试题
- IO
- 网络IO模型
- 第五章 操作系统
- IO
- 文件系统的相关概念
- Java几种文件读写方式性能对比
- Socket
- 内存管理
- 进程、线程、协程
- IO模型的演化过程
- 第六章 计算机网络
- 第七章 消息队列
- RabbitMQ
- 第八章 开发框架
- Spring
- Spring事务
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 数据库
- Mysql
- Mysql中的索引
- Mysql中的锁
- 面试常见问题
- Mysql中的日志
- InnoDB存储引擎
- 事务
- Redis
- redis的数据类型
- redis数据结构
- Redis主从复制
- 哨兵模式
- 面试题
- Spring Boot整合Lettuce+Redisson实现布隆过滤器
- 集群
- Redis网络IO模型
- 第十章 设计模式
- 设计模式-七大原则
- 设计模式-单例模式
- 设计模式-备忘录模式
- 设计模式-原型模式
- 设计模式-责任链模式
- 设计模式-过滤模式
- 设计模式-观察者模式
- 设计模式-工厂方法模式
- 设计模式-抽象工厂模式
- 设计模式-代理模式
- 第十一章 后端开发常用工具、库
- Docker
- Docker安装Mysql
- 第十二章 中间件
- ZooKeeper