前言
“赶时间的朋友可以直接跳过这部分”
q:看完这篇文章能学到什么?
a:掌握设计模式基础,快速理解23种设计模式的设计思想,了解设计模式在实际项目中的使用。
q:这篇文章与其他文章的区别是什么?
a:这篇文章主要通过分享设计模式的基础知识、设计模式的优缺点以及用大白话讲述对设计模式的理解,同时结合UML以及具体代码的使用(而不是毫无相关的栗子)帮助小白快速理解23种设计模式的使用。对于设计模式需要的基础知识,我也会总结在下面,保证零基础的朋友可以看得懂。
【一段胡话,可以跳过】
在写这篇文章之前,我查阅过很多设计模式的博客和书籍,很多对设计模式的介绍比较表层,没有结合实际项目使用进行介绍,列举的栗子也比较乏味,与实际项目没有多大关系,整本书看下来给我感觉就是学到了设计模式,但是不知道怎么用。虽然我是认为【设计模式学的是思想,而不是套用模板】,但是如果说在学设计模式的时候都含含糊糊的,比如说只知道策略模式是用于实现算法替换,取代if-else硬编码,在实际开发过程中又不知道在哪里要使用,如何使用。这就非常尴尬了。后面在看到一些优质的书籍,与大佬的交谈以及与师弟师妹的交流中发现,如果在学习设计模式的时候,先给一个实际项目中的设计模式实际应用,让读者知道哪些地方可以用这种设计模式,知道设计模式的核心代码、核心思路是什么,那上手起来岂不是轻而易举,而不再是只停留在设计模式是什么这一层次,花大量时间在明白什么是XXX设计模式,结果造成不会用的尴尬。
q:看完这篇文章大概需要多久时间?
a:基础扎实半小时复习完(跳过一些不必要的大白话),零基础不超过两小时能上手设计模式,理解设计模式,在实践中使用设计模式。
q:为什么写这篇文章?
a:写这篇文章的初衷是分享自己学习设计模式的过程,以及对设计模式的理解。最初接触设计模式是在大一的课堂上,当时迷迷糊糊似懂非懂,能理解设计模式的实现逻辑,但真正做项目时很难使用到设计模式,在设计软件时没有考虑代码的可靠可扩可读可重用。直到后来去了BAT实习后接触到大型项目,刚好当时项目在进行架构升级,与超级大佬的接触中了解到设计模式在实际项目中的重要性,以一种新的角度去理解设计模式。回顾自己学习设计模式的坎坷,走了太多坑,喜欢刚接触设计模式的朋友能够少走点弯路,带着设计模式的思想研发项目会打开新的天地~
写这篇文章会结合超级大佬的看法,我个人的理解以及在学习设计模式过程中查阅他人博客学到的东西,第一次系统性地梳理知识点,如果有写的不好的地方,欢迎各位大佬指点(轻点喷~)。
希望通过这篇文章对设计模式的梳理,用“大白话”讲述完设计模式,让更多刚接触软件工程的朋友能够快速上手设计模式,带着设计模式的思维写项目,不要像我一样写了一年多的“屎山代码”/(ㄒoㄒ)/~~
基础回顾
如何理解高内聚,低耦合
在学习设计模式之前,需要了解高内聚,低耦合这两个概念
在不少软件工程相关的书籍中能够看到“高内聚,低耦合”的字眼,那么什么是“高内聚,低耦合”呢?
先来理解耦合这个概念
一般我们在开发一个软件实体时,会根据功能、数据模型等进行模块划分。
每个模块之间相互联系的紧密程度,模块之间联系越紧密,则耦合性越高,模块的独立性就越差!反之同理;如果两个类之间耦合性较强,则这两个类之间关系紧密,一个改动后,另一个也可能要做相应变动
so~ 低耦合是指模块、组件或类之间的依赖关系较低,每个模块、组件或类尽可能独立,减少之间的联系。
举个栗子:一个项目中有20个方法调用是正常的,但是要修改了其中一个,另外的19个都要进行修改,那可就麻烦大了。这就是高耦合!模块的独立性太差!
那么如何理解高内聚呢
内聚性是指一个软件模块内部各个元素之间联系的紧密程度,注意!是内部。
一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即高内聚。
总的来说,“高内聚、低耦合”是对软件模块、组件或类的划分原则,要求每个模块应该尽可能独立,内部功能紧密相关,而与其他模块之间的依赖和联系应尽可能少。
如何理解类之间的关系
类之间的关系有:依赖、关联、聚合、组合、泛化、实现。
最初接触这几个概念时,最容易理解的就是泛化、实现,这也就是我们代码中的继承与实现关系。
依赖、关联、聚合、组合这四个就比较绕了,看了一些书籍都是简单带过,没有结合实际代码进行介绍。接下来主要结合代码对这四种关系进行白话介绍。
依赖与关联都是表示类之间的关系,不同的是,依赖表示类之间的关系是一种不稳定的关系,表示一种短暂的使用关系,只有需要的时候才会临时使用,而关联则表示类之间的关系是一种稳定的关系,表示为拥有的关系。关联比依赖的耦合性更高。
在代码中,
依赖关系常常表现为方法的形参,方法中创建的临时变量,类的静态变量
关联关系常常表现为类的成员变量
聚合和组合都是特殊的关联关系,两者区别在于关联对象的生命周期是否同步
聚合是一种强耦合性的关联关系,整体和部分的生命周期不同步,是相互独立的。
从代码角度上理解,成员变量初始化时,不赋初值或者赋值为null,这样整体和部分创建时间不同,生命周期也就不同。在需要设置成员变量值时,通过setting注入。
组合是一种相对"聚合"耦合性更强的关联关系,整体和部分的生命周期同步。整体对象负责代表部分对象的生命周期。
从代码角度上理解,成员变量初始化时,需要赋初值且不为null,这样整体和部分创建时间相同,生命周期也就相同。
什么是设计模式
设计模式的重要性
设计模式的本质是面向对象设计原则的实际运用,是对类的封装、继承和多态性以及类的关联关系和依赖关系的充分理解。
设计模式是一套前人代码设计经验的总结,它描述了在软件设计过程中的一些不断重复发生的问题和该问题的解决方案。
使用设计模式的目的是提高代码的重用性,可读性,可靠性和可扩展性,使程序呈现高内聚,低耦合的特性。
设计模式的作用
设计模式的使用能够解决如下几个场景的尴尬
-
项目初期完成后需要新增功能,如何快速新增新的功能(可扩展性);如何保证对原有功能的使用不影响(可靠性);如何最少地新增代码(重用性);
-
项目组来了新人,如何让新人能够快速结合业务理解项目代码(可读性)
简单地说就是解决软件开发中常见的问题,从而使项目代码更加易于理解和维护(可靠可扩可读可重用),具体解释如下
-
重用性 (相同功能的代码,不用多次编写)
-
可读性 (编程规范性, 便于项目新人的阅读和理解)
-
可扩展性 (当需要增加新的功能时,非常的方便,也称为可维护)
-
可靠性 (当我们增加新的功能后,对原来的功能没有影响)
软件设计七大原则
原则 | 定义 |
---|---|
开闭原则 | 对扩展开放,对修改关闭,提高扩展性,易于维护 |
单⼀职责原则 | ⼀个类只负责⼀个功能领域中的相应职责 |
⾥⽒替换原则 | 所有引⽤基类的地⽅必须能透明地使⽤其⼦类的对象 |
依赖倒置原则 | 依赖于抽象,不能依赖于具体实现 |
接⼝隔离原则 | 类之间的依赖关系应该建⽴在最⼩的接⼝上 |
组合/聚合复⽤原则 | 尽量使⽤组合/聚合,⽽不是通过继承达到复⽤的⽬的 |
迪⽶特法则 | ⼀个实体应当尽可能少的与其他实体发⽣相互作⽤ |
看完是不是一脸懵?怎么理解勒?下面附上我的理解
开闭原则:如果要添加新的功能,对于已上线的代码(能够正常运行的代码)尽量不要去修改,改错了出bug了大家都不好走/(ㄒoㄒ)/~~,这个时候尽量保持原有代码不懂,扩张原有代码的功能,例如原有代码中对于某一模块的服务调用,如果是采用接口引用指向具体实现对象,那这个时候就好办了,我们只需要创建新的实现类,保留原先使用的接口,使用我们新增的实现类就解决问题了。
单⼀职责原则:这个好理解就不多赘述了
⾥⽒替换原则:所有引⽤基类的地⽅必须能透明地使⽤其⼦类的对象。简单地说就是只要父类出现的地方,子类都可以替换他。这里的地方可以是方法的形参,变量的声明等。
举个栗子:我们在写springboot项目时,常常使用List作为形参而不是具体的ArrayList,我们在使用形参时,并不需要关心这个List具体是怎么实现,但是要求这个List必须有get方法。
那可能就有人要说了,是不是只要在方法的形参,变量的声明等地方使用接口或父类声明就可以体现出⾥⽒替换原则了呢?有没有违反了里氏替换原则的场景呢
当然不是!
子类能够完全替代父类是有条件的,主要如下
-
子类需要实现父类的抽象方法,但不能重写父类的非抽象方法
-
子类可以新增方法
-
子类不能抛出父类无法处理的异常
当不符合上述条件时,则违反了里氏替换原则,如抛出父类无法处理的异常。
依赖倒置原则:依赖于抽象,不能依赖于具体实现。在具体的代码中体现为,形参、变量的声明使用父类或接口。
接⼝隔离原则:类之间的依赖关系应该建⽴在最⼩的接⼝上;可以理解为对依赖倒置原则的补充,要求这个接口是最小可用的。
举个栗子:
我们在项目中的工具类需要写一个方法,对PriorityBlockingQueue对象进行添加特定元素的操作,即执行add方法,如果方法的形参声明为BlockingQueue,则不符合接⼝隔离原则,因为我们只需要调用其add方法,这个方法在Queue接口中就已经声明了,因此BlockingQueue并不是最小可用的接口,应该使用Queue作为形参的类型声明。
组合/聚合复⽤原则:这个也比较好理解,继承能实现的,组合/聚合也能实现
举个栗子:
现在有支付抽象类,已有默认支付类,需要创建VIP支付类对默认支付类进行功能扩展。
直接继承默认支付类,对于需要特殊处理的方法进行重写,可以解决问题,但是这样耦合度就大了(因为子类会无条件地继承父类的所有属性和方法)
使用组合/聚合也能够实现这个功能,耦合度还低,具体实现方式是创建VIP支付类,实现支付抽象类并关联默认支付类完成不需要修改的方法。
编辑
迪⽶特法则:⼀个实体应当尽可能少的与其他实体发⽣相互作⽤;简单地说就是能自己处理尽量不要去调用别的实体来实现(减少与其他实体的相互作用)
设计模式的分类
创建型: 在创建对象的同时隐藏创建逻辑,不使⽤ new 直接实例化对象。这使得程序在某个给定实例需要创建哪些对象时更加灵活
结构型: 通过类和接⼝间的继承和引⽤实现创建灵活且可复用的类和对象结构。
⾏为型: 通过类之间不同通信⽅式实现不同⾏为
23种设计模式
创建型
-
工厂模式
-
抽象工厂模式
-
单例模式
-
建造者模式
-
原型模式
结构型
-
适配器模式
-
装饰模式
-
代理模式
-
享元模式
Map
-
外观模式
参考线程池
-
组合模式
树
-
桥接模式
⾏为型
-
模块模式
公共模块
-
策略模式
策略模式https://blog.csdn.net/weixin_56232016/article/details/136823644
-
观察者模式
-
状态模式
-
命令模式
ActionListener、Runnable
-
责任链模式
网关过滤器
-
解释器模式
-
迭代器模式
-
访问者模式
-
中介者模式
MVC控制器
-
备忘录模式
持续更新中…………