设计模式
设计模式和软件工程作用是差不多的,都是为了更高效的软件开发
设计模式是软件开发中一个些典型问题的解决方法。设计模式主要关心当产品的 业务需求 发生变更时,要怎么做,让软件开发更加安全高效
软件的开发需要依据一些设计原则,违反了这些设计原则后,开发就会因为业务需求的变化而变的十分困难。这时候,就需要用到设计模式了
设计模式有三大类
类别 | 特点 |
---|---|
创造型 | 关注类的创建,提供创造类的对象的机制 |
行为型 | 关注类的通讯和职责委派 |
结构型 | 介绍如何将对象和类组装成较大的结构 |
UML类图
classDiagram
Delivered --|> Base : 继承
Realization ..|> Interface : 实现
Penguin --> Climate : 关联
LiHua --o Chinese : 聚合
Wing --* Bird : 组合
Animal ..> Water : 依赖
- 关联:一个类需要知道另外一个类的一些消息
- 聚合:一种弱的拥有关系,体现的是A对象可以包含B对象,但是B对象不是A对象的一部分
- 组合:一种强的拥有关系,体现部分和整体的关系,部分和整体的声明周期是相同的
设计原则 | Design Principles
依赖倒置原则
- 高层模块不应该依赖于底层模块,两者都应该依赖与抽象
- 抽象不应该依赖于细节。细节应该依赖抽象
针对接口(抽象)编程,不要针对实现(细节)编程
单一职责原则
- 一个类只应该有一个影响它变化的因素
类的职责过多的话,当每一处的需求发生变化的话就会牵一发而动全身,把指着分开则可以实现代码的高效复用
开放封闭原则
- 类应该是可拓展的,但是不可修改的
可拓展是为了应对变化,不可修改是为了防止错误
里氏替换原则
- 子类应该可以替换父类(IS-A)
接口隔离原则
- 接口应该小而完整
优先使用对象组合,而不是类继承
- 类的继承是白箱复用,对象组合是黑箱组合
- 类继承破坏了封装性,子类父类耦合性高
- 而对象组合则只要求被组合的对象具有良好定义的接口
封装变化点
- 应该将变化的部分和稳定的部分分开,设计者可以在分界的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合
针对接口编程,而不是针对实现编程
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口
- 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案
创造型模式 | Creational Patterns
提供创建对象的机制, 增加已有代码的灵活性和可复用性
主要的措施是绕开 new
然后避免紧耦合(编译时依赖)
简单工厂
针对的问题
针对由于需求的需要,创建的对象经常改变的情况。
例如在界面中之前使用
Lable
显示进度,现在要改为进度条。如果Lable
在原先的代码中
实现的方法
classDiagram
Product <.. Client
Creator <.. Client
ProductA <.. Creator
class Product {
<<Interface>>
+doStuff()*
}
class Creator{
+creatorProduct(flag)
}
Product <|.. ProductA
Creator::creatorProduct(flag)
中对传入的flag
进行判断,通过条件分支创建不同的对象Client
通过Product *pd = Creator::creatorProduct(flag)
创建对象
带来的好处
简单工厂的出现能够让代码更加方便的管理。当使用 new
创建对象的时候就形成了稳定的模块依赖于变化的模块了,违反了依赖倒置原则。
如果不采用简单工厂方法,为了适应对象经常改变的这种情况,就需要在代码中不断的出现if-else
这种条件语句,用来更改对应的对象,如果现在新添加一种类型的对象,就需要在每个条件语句进行更改,同时,所有更改的文件都需要重新编译,效率低下
如果采用简答工厂方法,添加新类型的时候就只需要在对应的工厂类中的Creator::creatorProduct(flag)
进行更改
存在的问题
简单工厂每次修改都需要更改Creator::creatorProduct(flag)
,违反了开放封闭原则。当这些产品种类变的很多的时候,工厂的条件判断就会变的十分庞杂。同时很多人进行协作时,会对这个工厂进行频繁的修改,管理起来就会比较麻烦。
参考资料
https://blog.csdn.net/qq_22238021/article/details/79832092
工厂方法
针对的问题
解决和简单工厂一样的问题,但是针对简单工厂违反开放封闭原则的问题,将条件分支转化为了类接口的实现
工厂方法 = 简单工厂 + 策略模式
实现的方法
解决方法是为每个产品定义一个工厂,工厂负责创造产品的实例。所有的产品都继承自一个抽象产品类,所有的工厂都继承自一个抽象工厂类。使用的时候通过不同的工厂获得不同的产品
classDiagram
Creator <.. Client
Product <.. Client
class Creator{
<<Interface>>
+creatProduct()*
}
class Product{
<<Interface>>
+doStuff()*
}
class CreatorA{
+createProduct()
}
class CreatorB{
+createProduct()
}
Creator <|.. CreatorA
Creator <|.. CreatorB
Product <|.. ProductA
Product <|.. ProductB
ProductA <.. CreatorA
ProductB <.. CreatorB
createProduct()
返回具体Createor
对应的Product
对象(CreatorA::createProduct() { return new ProductA; }
)。- 构造
Product
对象时,通过调用product *p = creator->creatProduct()
实现对象的创建,当业务需求时更改创建的对象时,只需更改creator
指向的对象即可
特点
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
抽象工厂
主要是针对一系列相互依赖的对象的创建工作
具体的解决方法时提供一个接口,让这个接口负责创建一系列相互依赖的对象
classDiagram
AbstractCreator <.. Client
AbstractProductA <.. Client
AbstractProductB <.. Client
class AbstractCreator {
<<Interface>>
+createProductA()*
+createProductB()*
}
class AbstractProductA {
<<Interface>>
}
class AbstractProductB {
<<Interface>>
}
class Creator1 {
+createProductA()
+createProductB()
}
class Creator2 {
+createProductA()
+createProductB()
}
AbstractCreator <|.. Creator1
AbstractCreator <|.. Creator2
AbstractProductA <|.. ProductA1
AbstractProductA <|.. ProductA2
AbstractProductB <|.. ProductB1
AbstractProductB <|.. ProductB2
ProductA1 <.. Creator1
ProductB1 <.. Creator1
ProductA2 <.. Creator2
ProductB2 <.. Creator2
特点
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
单例
解决只生成一个实例的问题
classDiagram
class Singleton{
-Singleton()
+getInstance()$
}
1 |
|
行为模式 | Behavioral Patterns
负责对象间的高效沟通和职责委派
观察者
实现订阅-通知这种关系,一个对象的状态发生改变,就立即通知其他的对象,是一种一对多或者多对多的通知关系
classDiagram
Publisher <.. Client
Subscriber <.. Client
class Publisher {
<<Interface>>
+attach(Subscriber)*
+detach(Subscriber)*
-notify()*
}
class ConcretePublisher {
+getDate()
-attach(Subscriber)
-detach(Subscriber)
-notify()
-mainProgess()
}
class Subscriber {
<<Interface>>
update()*
}
class ConcreteSubscriber {
update()
}
Publisher <|.. ConcretePublisher
Subscriber <|.. ConcreteSubscriber
Publisher <.. Subscriber
ConcretePublisher <.. ConcreteSubscriber
策略
针对问题
某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担
虽然可以将算法类抽象成一个对象,然后使用简单工厂创建。但是遇到算法更改十分频繁的时候,工厂类会同时部署起来也十分的麻烦,因此不是最好的解决方法。
实现的方法
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程 序(稳定)而变化(扩展,子类化)
classDiagram
Context <.. Client
StrategyA <.. Client
StrategyB <.. Client
StrategyC <.. Client
Strategy --o Context
class Strategy {
<<Interface>>
+Algorithm()*
}
class StrategyA {
+Algorithm()
}
class StrategyB {
+Algorithm()
}
class StrategyC {
+Algorithm()
}
class Context {
+Context(Strategy)
+contextInterface()
}
Strategy <|.. StrategyA
Strategy <|.. StrategyB
Strategy <|.. StrategyC
Context::contextInterface() { strategy->Algorithm(); }
这个函数执行算法- 使用算法时,调用
Context(new StrategyA())
创建上下文后调用Context::Contextinterface()
即可
特点
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需 要Strategy模式。
模板方法
针对的问题
对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因 (比如框架与应用之间的关系)而无法和任务的整体结构同时实现
实现的方法
定义一个操作中的算法的骨架 (稳定),而将一些步骤延迟 (变化)到子类中。Template Method使得子类可以不改变 (复用)一个算法的结构即可重定义(override 重写)该算法的 某些特定步骤。
classDiagram
class AbstractClass {
+templateMethod()
-step1()*
-step2()*
-step3()*
}
class Class1 {
-step1()
}
class Class2 {
-step2()
-step3()
}
AbstractClass <|.. Class1
AbstractClass <|.. Class2
特点
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
结构型模式 | Structural Patterns
介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效
装饰模式
针对的问题
在通过类的继承实现拓展的时候,有时候一些组合会使得拓展出来的子类数量急剧的膨胀
例如咖啡类。如果要在咖啡上添加牛奶、巧克力和糖,就会产生8个子类
装饰模式能够实现类的动态拓展,避免子类数量的膨胀
实现
classDiagram
ConcreteComponent <.. Client
DecoratorComponentA <.. Client
DecoratorComponentB <.. Client
class Component{
<<Interface>>
+operation()*
}
Component <|.. ConcreteComponent
class ConcreteComponent{
+operation()
}
Component <|-- Decorator
class Decorator{
<<Interface>>
+Decorator(Component)
+operation()
}
class DecoratorComponentA{
+Decorator(Component)
+operation()
-addItem()
}
class DecoratorComponentB{
+Decorator(Component)
+operation()
-addString
}
Decorator <|.. DecoratorComponentA
Decorator <|.. DecoratorComponentB
装饰类
DecoratorComponentA/B
的operation()
中包括传入的component
的opration()
和自己添加的操作1
2
3
4void DecoratorComponentA::operation() {
component.operation(); // component是这个类构造函数中传入的对象
... // DecoratorComponentA自己的操作
}Client
使用时,进行如下操作即可1
2
3
4
5
6void doStuff() {
Component *cc = new ConcreteComponent();
DecorationComponentA *dca = new DecorationComponentA(cc);
DecorationComponentB *dcb = new DecorationComponentB(dca);
dcb->operation();
}
特点
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
参考资料
- https://refactoringguru.cn/design-patterns/
- 程杰.《大话数据结构》