23种设计模式概述(持续更新)

首先介绍设计模式中一些概念:

# 一些角色名词

## 客户(使用者)
一切设计模式的目的都是为了让客户(使用者)在使用的时候最方便,以后维护起来改动最小,或者说无感
## 高层组件:目标(类、接口)
跟客户直接接触的类,利用抽象、多态、封装、继承、组合,充分封装内部实现,跟客户解耦
根据设计模式不同,叫法也不同:目标类(名词)、管理/策略/调用类(管理一些动作方法)、工厂类(管理一些生成目标产品的方法)等
## 底层组件
产品、目标对象、具体实现者、子系统等角色

下面的每个设计模式都有一个对应的简易类图,横向是依赖 关联 聚合 组合(会指出),竖向是继承、实现
面向对象的特性:封装、继承、多态(有的还加个抽象,四个OO基本概念)


# 九个OO的设计原则:

依赖倒置原则
    A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。换种说法:客户不应该依赖于具体目标类,应该依赖于目标接口
    B.抽象不应该依赖于具体,具体应该依赖于抽象。

设计模式:

# 策略模式

客户(使用者)             调用类(也叫上下文)                   策略类(实现了一个算法,通常由一个接口或者抽象类实现)         
                (持有一个策略类的引用,由客户确定具体的
                策略角色,并在对外暴露的方法中调用策略        
                    类的抽象方法) 
                                                           具体策略角色1
                                                           具体策略角色2(包装了相关的算法和行为。)

关键代码:实现同一个接口。
使用场景
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
应用实例
1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
3、JAVA AWT 中的 LayoutManager。


# 观察者模式

太过常用,不做赘述


# 装饰者模式(对象装饰、类装饰)

客户(使用者)        目标接口
            
                   装饰者对象1
                   装饰者对象2(两者都实现目标接口,装饰者对象拥有目标对象,组合,在目标对象的基础上,添加功能)

                   目标对象

关键代码
1、目标接口充当抽象角色,不应该具体实现。 2、装饰类继承目标接口
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
注意事项:可代替继承。
应用实例
1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。


# 工厂模式

## 简单工厂:
客户(使用者)   工厂类    (类中封装产品的各种类型的生成方法)             产品
## 工厂模式
客户(使用者)   工厂接口(生成产品A)

                  实现者1                                             产品1
                  实现者2(实现目标接口生成不同类型的产品)                产品2

关键代码:创建过程在其子类执行。
优点
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
应用实例
1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
2、Hibernate 换数据库只需换方言和驱动就可以。
3、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
4、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
5、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

## 抽象工厂模式
客户(使用者)   工厂接口(可以生成产品蔟,产品A,产品B)

              实现者1(生成产品A1、产品B1)                              产品A1、产品B1
              实现者2(生成产品A2、产品B2)                              产品A2、产品B2

关键代码:在一个工厂里聚合多个同类产品。
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
注意事项:产品族难扩展,产品等级易扩展。
应用实例: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。

## 几种工厂模式的区别:

具体使用场景:
当产品的生成只有一种固定情况或者少数几种情况(以后不会经常扩展)的时候,可以使用简单工厂。 
                                        -------  一个工厂类,一个工厂方法
当产品的生成有一个判断纬度(n个区域或者n个等级),可以使用工厂方法。 
                                        -------  n个工厂类,每个类一个工厂方法
当产品的生成有两个判断纬度(两相结合,比如:n个区域或者n个等级,每个区域、等级有一个相同内容的产品族,有m种),可以使用抽象工厂  
                                        -------  n个工厂类,每个类中m个工厂方法


工厂方法比简单工厂相比多加一层工厂封装类的意义在于:
1、用户并不想关心产品接口是怎么实现的,如果这个实现过程和逻辑比较复杂呢?将这个过程封装到工厂类中,别的地方也可以重用;
2、接口的具体怎么实现,全部交给另一个人去做(他写的产品类)。如果添加一个新产品,那么他再添加一个工厂类和产品类,用户使用这个工厂类即可。符合开发-关闭原则
3. 工厂类可以继承于某个接口,或是抽象类,工厂类已经对产品类的实现就行了封装,用户用它结合配置参数和反射实现动态创建,是很合理的。相比,简单工厂是不太合适的。

产品簇(Product family)指具有相同或相似的功能结构或性能,共享主要的产品特征、组件或子结构,并通过变型配置来满足特定市场的一组产品的聚类

工厂方法与抽象工厂:
抽象工厂和工厂方法没有本质区别,是对工厂方法的扩展。
当产品类,涉及到'多个产品簇(2个区分纬度)'时,'挑选一个合适的纬度',对同类的产品抽象为一个接口。工厂类中,可以定义多个返回具体产品的方法,自由组合。
也有'把工厂方法模式划分到抽象工厂模式'的,认为工厂方法是抽象工厂模式的特例的一种,就是只有一个要实现的产品接口。

# 单例模式

太过常用,不做赘述


# 命令模式

客户(使用者)    调用invoker类                                                   命令Command接口
        (持有命令,客户根据不同的操作,赋值给调用者不同的命令实现者(们)        (不定义具体的行为,只提供执行、取消两个方法)                                                                                    
          调用setCommand())                                                                                          (实现者各种各样,一个行为就是一个命令,完成不同的操作) 
                                                                               received实现者1 
                                                                               received实现者2

关键代码:定义三个角色:
1、received 真正的命令执行对象
2、Command
3、invoker 使用命令对象的入口
使用场景:认为是命令的地方都可以使用命令模式,比如:
1、GUI 中每一个按钮都是一条命令。
2、模拟 CMD。
优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
注意事项:系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。
应用实例:struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。


# 适配器模式与外观模式

## 适配器模式
客户(使用者)      目标接口

                 适配器对象1
                 适配器对象2(适配器对象实现目标接口,对象拥有目标对象,组合        —>           被适配对象
                   实现目标接口的方法,并在方法中调用被适配对象的方法
                   将一个接口转化成另一个接口)

关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
优点
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、增加了类的透明度。
4、灵活性好。
缺点
1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
应用实例
1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。
2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。
3、在 LINUX 上运行 WINDOWS 程序。
4、JAVA 中的 jdbc。

## 外观模式
客户(使用者)       外观(管理)类(多个外观方法)                             复杂的子系统
              (类中封装了一个或多个子系统中复杂的组合,
              只暴露给客户一套简洁的api调用)

关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。
注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。
应用实例
1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
2、JAVA 的三层开发模式。


# 模板方法模式

客户(使用者)       模板(调用)'抽象类'(实现了一个算法,其中几个步骤需要子类实现、钩子需要子类选择性实现)         

                   子类1
                   子类2  实现目标抽象类中的几个步骤

关键代码:在抽象类实现,其他步骤在子类实现。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
应用实例
1、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

参考书籍:《Head First 设计模式》
参考链接: 菜鸟教程

猜你喜欢

转载自blog.csdn.net/weixin_33755557/article/details/87582394