设计模式基础
- 设计模式是指在软件开发中,经过验证的,用于解决在特定环境下、重复出现的、特定问题的解决方案。
- 设计模式组成:模式名称、环境和问题、解决方案;
- 模式的分类:
- 创建型模式: 抽象化了对象实例化过程,用来帮助创建对象的实例,包括:
- 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点;
- 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类;
- 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类;
- 生成器模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示;
- 原型模式:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象;
- 结构型模式:描述如何组合类和对象以获得更大的结构,包括:
- 外观模式:为子系统的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用;
- 适配器模式:将一个类的接口转化为客户希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作;
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问;
- 组合模式:将对象组合成树形结构一表示“部分--整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性;
- 享元模式:运用共享技术有效地支持大量细粒度的对象;
- 装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活;
- 桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化;
- 行为型模式:描述算法和对象间职责的分配,包括:
- 中介者模式:用一个中介对象来封装一系列的对象交互,中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互;
- 观察者模式:定义对象间一对一多的依赖关系,当一个对象的状态发生该表示,所有依赖于它的对象都得到通知并被自动更新;
- 命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作;
- 迭代器模式:提供一种方法顺序访问一个聚合对象中的各种元素,而又不需暴露该对象的内部表示;
- 模板方法模式:定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤;
- 策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化;
- 状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看上去似乎修改了它的类;
- 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后就可以将该对象恢复到原先保存的状态;
- 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子;
- 职责链模式:很多对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链处理该请求,直到有一个对象处理它位置;
- 访问者模式:表示一个作用域某对象结构中的各元素地操作。它可使你在不改变各元素地类的前提下定义作用于这些元素的新操作;
- 创建型模式: 抽象化了对象实例化过程,用来帮助创建对象的实例,包括:
- 六大设计原则:
- 开闭原则:对扩展开放,对修改关闭;
- 单一职责原则:一个类只负责一个职责:
- 里氏替换原则:子类可以替换父类,程序执行效果不变(子类不能重写父类的非抽象方法,但可以重载);
- 依赖倒置原则:依赖抽象而不是依赖实现(针对接口编程,而不是针对实现编程);
- 接口分离原则:一个接口的功能不能过于臃肿,可以使用多个专一功能的接口;
- 最少知识原则:类知道其他类、其他类的方法或属性尽量少;
外观(Facade)模式:结构型模式
-
模式:
- 客户端只需要调用 Facade 模块上的高层接口,外观模块再委托子模块实现具体功能;
- 外观模块与子模块共同组成了一个系统;
- 封装交互,简化调用,体现了“最少知识原则”;
-
特点
- 使外部更简单地使用内部子模块;
- 屏蔽了外部客户端和内部模块的交互、增加可维护性;
- 复用功能;
- 不限制内部模块直接使用;
- 有选择性地暴露接口,隐藏系统内部的方法;
- Facade 模块只实现内部模块功能的组合调用,不进行功能的处理;
-
优点
- 松散耦合:松散外部与子模块的耦合关系;
- 简单易用:外部不需要了解子模块内部实现;
- 更好地划分访问的层次;
-
缺点
- 过多或不合理的 Facade 让人迷惑,是调用 Facade 还是直接调用子类;
-
使用场景
- 复杂子系统;
- 外部与实现部分松散耦合;
- 多层结构的系统,用外观对象作为每层的入口;
适配器(adapter)模式:结构型模式
-
模式:
- 已有一个功能模块,但是接口与客户端需求不一致,需要新增一个模块“转换接口”;
- 对象适配器:依靠对象组合的方式,把相应功能委托给被适配的对象;
- 类适配器:采用多重继承对一个接口与另一个接口进行匹配(支持多重继承的语言);
- 转换匹配,复用功能;
-
优点:
- 实现原有功能模块的复用;
- 更好的拓展性;
-
缺点:
- 过多使用适配器,会让系统非常零乱;
-
使用场景:
- 使用已存在类,但接口不符合需求;
- 创建可复用类,新类可能和一些不兼容的类一起工作;
- 使用已存在子类,但不可能对每一个子类都配饰,可以选用对象适配器适配父类;
单例(singleton)模式:创建型模式
-
模式:
- 在一个系统运行期间,某个类只需要一个实例;
- 类自身负责自己类实例的创建工作,提供外部访问类实例的方法;
- 不能让外部访问类的构造方法,否则无法控制外部创建类的实例个数;
- 懒汉式:类中用变量存储一个实例,并提供接口获取实例,使用对象实例(调用获取实例方法)时创建单例;
- 饿汉式:类中用变量存储一个实例,并提供接口获取实例,装载对象(定义实例变量)时直接创建实例;
- 控制实例数目;
-
特点
- 保证运行期间只会被创建一个实例;
- 提供全局唯一一个类实例的访问点;
- 范围:对于 java 来说,一个 classloader 对应一个单例类实例;
- 构建方法要是私有的:不能被外部调用;
- 获取实例的方法要是静态的:直接通过类调用方法,不需要先得到类实例;
- 存储实例的变量是静态的:与实例无关(java 中 static 变量在类装载时初始化一次,被多个实例共享);
- 懒汉式体现了延迟加载的思想,尽可能节约资源;
- 缓存的思想,内存换时间;
-
优点
- 懒汉式时间换空间,饿汉式空间换时间;
- 懒汉式可能线程不安全(并发问题),饿汉式线程安全;
-
懒汉式的并发问题:
- 可以在获取实例方法上加上 synchronized 关键字(加互斥锁)变为同步方法,但每次判断会降低访问速度
- 双重检查加锁:实例不存在时才进入同步代码,同步代码中再判断一次实例不存在,再新建实例,要求实例变量上有 volatile 关键字;
- volatile 关键字:不被本地线程缓存,所有对该变量的读写都是直接操作共享内容,从而确保多个线程能正确地处理该变量;
-
lazy initialization holder class
-
使用场景:
- 需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时;
工厂(factory)模式:创建模式
-
框架:
- 框架是能完成一定功能的半成品软件,能加快应用开发进度、精良的程序架构;
- 框架比设计模式更具体、更特例化,并且能包含多个设计模式;
- 可以用框架,但是需要搞清楚框架是如何实现功能的,不然项目中由框架实现的部分会是个黑箱,无法掌控项目;
-
模式:
- 业务不知道该创建哪一个具体实例对象,也不知道如何进行创建;
- 工厂只定义接口对象,具体创建交给子类实现;
- 父类不知道具体实现的情况下,完成自身功能的调用,而具体的实现延迟到子类来实现(我的理解:操作方法定义在父类上,子类覆盖父类中创建实例的方法);
- 第一种方案:客户端使用 Create 类(我感觉是用子类创建器 ConcreteCreator 实例化?);
- 第二种方案:客户端使用由 Create 创建出来的对象(Create 暴露方法,直接使用 ConcreteProduct 实例化一个子类对象,然后返回给客户端?);
- 参数化工厂:给工厂传递参数,让工厂根据参数不同来创造不同的产品对象,不同产品必须是同一个父类型;
- 延迟到子类来选择实现,体现了“依赖倒置原则”;
-
IoC(控制反转)/DI(依赖注入)
- A 类内部使用外部资源 C,如果 A 类中主动创建 C 实例,则为正向依赖;
- A 等待 IoC/DI 容器获取一个 C 的实例,再反向注入 A 中,则为依赖注入;
- 对 A 来说,依赖注入:应用程序依赖容器创建并注入它所需要的外部资源;
- 对容器来说,控制反转:容器控制应用程序 A,由容器反向地向应用程序注入所需的外部资源;
- 优点:松散耦合,使体系变得灵活;
- 工厂模式与依赖注入一样,有主从换位的思想;
-
平行的类层次结构
- 工厂模式可以用来连接平行的类层次(我的理解:工厂模块可以作为某一类功能相似的模块的父类);
-
优点
- 不需要关心具体的实现,具体实现任务延迟到子类去完成;
- 容易扩展对象的新版本;
- 连接平行的类层次;
-
缺点
- 工厂要创建具体的产品对象,具体产品对象和工厂方法耦合性强;
-
使用场景:
- 类需要创建某个接口的对象,但是不知道具体的实现;
- 类本身希望由子类来创建所需对象;
抽象工厂(abstract factory)模式:创建型模式
-
模式:
- 与工厂模式不同:用于一系列对象、且对象间有相关、依赖关系的场景;
- 抽象工厂起到约束作用,并且提供子类的统一外观,由子类提供一系列对象创建方法;
- 抽象工厂选择具体工厂 -> 具体工厂选择子类 -> 调用子类创建;
- 选择产品簇的实现;
-
特点:
- 抽象工厂实现产品簇,工厂实现一个产品;
-
DAO(数据访问对象)
- 实现 DAO 模式时,常使用抽象工厂;
- 实现 DAO 模式时,常使用抽象工厂;
-
优点
- 分离接口和实现;
- 切换产品簇变得容易;
-
缺点
- 不太容易扩展新的产品(新子类);
- 容易造成类层级复杂;
-
使用场景:
- 系统独立于产品的创建、组合和表示;
- 系统需要由多个产品系列中的一个来配置的时候(动态切换产品簇);
- 强调一系列相关产品的接口,以便联合使用;
生成器(builder)模式:创建型模式
-
模式:
- 处理步骤相同,但没不得具体实现不同;
- 构建和表示分离;
- 构建过程为指导者,负责指导装配过程、但不负责每部具体的实现;
- 表示(负责具体的实现)称为生成器;
- 指导者就是可以重用的构建过程,生成器是可以被切换的具体实现;
- 用具体的生成器子类实例,作为创建指导者实例的参数;
- 分离整体构建算法和部件构造;
-
特点
- 生成者模式主要功能是分步骤地构建产品、并且构建过程是统一的;
- 重心在于分离构建算法和具体的构造实现,从而使构建算法重游、具体的构造实现可以方便地扩展和切换;
- 指导者与生产器交互:
- 简化,build 成为生成器上的一个方法:
-
优点:
- 松散耦合:构建和表现的分离;
- 容易改变产品内部表示,只需要切换生成器就可以;
- 更好复用构建算法、具体实现;
-
使用场景:
- 如果创建对象的算法,应该独立于该对象的组成部分以及装配方式时;
- 如果同一个构建过程有不同的表示时;
原型(prototype)模式:创建型模式
-
模式:
- 已有某个对象实例后,快速地拷贝出更多的对象;
- 原型模式要求实例对象实现一个可以“克隆”自身的接口,调用接口可以获得新的对象;
- 克隆生成对象;
-
特点:
- 原型模式的功能包括两个方面:一是通过克隆来创建新的实例对象,而是为克隆出来的实例复制原型实例属性的值;
- 有对象属性时,需要深拷贝;
- 解决“只知道接口而不知道实现的问题”,采用“接口造接口”的方法;
-
原型管理器
- 系统内维护可用原型的注册表;
- 向原型管理器里面添加原型对象的时候,通过 new 来创建对象,其余时候都是通过原型管理器来请求原型实例、通过克隆方法来获得新的对象实例;
-
优点
- 对客户端隐藏具体的实现类型;
- 运行时动态改变具体的实现类型;
-
缺点
- 每个原型子类必须实现 clone 操作,深拷贝较麻烦
-
使用场景
- 系统想要独立于使用的对象;
- 实例化的类实在运行时刻动态指定的;