继设计模式学习笔记(上)

15 抽象工厂模式

场景

不同数据库在 SQL 的具体实现上有差距,若存在系统中使用 SQL 语句上过多的使用专门的某种数据库的 SQL 语句,在迁移数据库时,会导致很多 SQL 在新的数据库中不能运行。

解决

使用原生 SQL 语句

实例

class User
{
    private int _id;
    public int ID
    {
        get {	return _id;	}
        set {	_id = value; }
    }

    private string _name;;
    public string Name
    {
        get {	return _name; }
        set { _name = value; }
    }
}

模式解释

工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类

效果

由于多态的存在,声明 User 接口的对象事先不知道在访问哪个数据库,却可以在运行时正常,达到业务逻辑与数据访问解耦

抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口,而无需指定具体的类

实际使用步骤

先通过抽象工厂创建一个具体工厂,再通过具体工厂生产特定实现的产品对象

即为创建不同的产品对象,应使用不同的具体工厂

抽象工厂优缺点分析

优点:

  • 易于交换产品系列:具体工厂类在一个应用中只需要在初始化时出现一次,使得改变一个应用的具体工厂变得容易,只需要改变具体工厂即可使用不同的产品配置

  • 让具体的创建实例过程与客户端分离:客户端通过抽象接口操纵实例,产品的具体类名被具体工厂的实现分离,不会出现在客户端代码中

缺点:

增加一个表,需要增加三个类,并更改三个工厂类才能完全实现

抽象工厂改进

使用简单工厂

去除三个抽象工厂,使用一个简单工厂,根据条件判断工厂类型去实例不同的具体工厂

反射+抽象工厂的数据访问程序

去某个地方找应该要实例化的类是哪一个——依赖注入(本需要专门的 IOC 容器提供,例如 Spring)

常规写法:

IUser result = new SqlserverUser();

反射写法:

IUser result = (IUser)Assembly.Load("抽象工厂模式").CreateInstance("抽象工厂模式.SqlserverUser");

差别:原来的实例化写死在程序里,而通过反射可以利用字符串实例化对象,变量可以更换(实例化由编译时转为运行时)

反射+配置文件实现数据访问程序

使用读取配置文件的方式来决定要实例化的数据库访问类

利用反射解决了数据库访问时的可维护、可拓展问题

在使用简单工厂的地方,都可以考虑使用反射来去除条件判断,解除分支判断带来的耦合

16 状态模式

背景

方法过长(Long Method)极有可能有坏味道

面向对象设计希望做到代码的责任分解

概念解释

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类

应用场景

主要解决当控制一个对象状态转换的条件表达式过于复杂时,把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化

优缺点分析

优点:

  • 将与特定状态相关的行为局部化,并将不同状态的行为分割开

将特定的状态相关的行为放入一个对象中,由于所有与状态相关的代码都存在于某个类中,所以通过定义新的子类可以容易地增加新的状态和转换

目的

消除庞大的条件分支语句(大的分支判断会难以修改和拓展)

状态模式通过把各种状态转移逻辑分部到 State 的子类之间,来减少相互间的依赖

使用场景

当一个对象的行为取决于其状态,并且必须在运行时根据状态改变其行为

17 适配器模式

概念

将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

实例

电源适配器:可以将任意伏的电转换成需要的电压

软件应用场景

系统的数据和行为都正确,但接口不符时,考虑使用适配器。

目的是使控制范围外的一个原有对象与某个接口匹配。

主要应用于希望复用现存的类,但接口与复用环境要求不一致的情况

类型(GoF 中的分类)

  • 类适配器模式

  • 对象适配器模式

类适配器通过多重继承对一个接口与另一个接口进行匹配,但 C#、VB.NET、Java 等不支持多重继承(C++支持),此处讲对象适配器

使用时机

使用一个已存在的类,如果其接口(方法)和要求不符,则考虑使用适配器

两个类所做事情相同或相似,但具有不同的接口要使用它时,客户端可以统一调用同一接口,这样更简单、直接、紧凑

18 备忘录模式

概念解析

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态

适用场景

用于功能比较复杂,但需维护或记录属性历史的类,或需要保存的属性只是众多属性中的一小部分时,Originator(发起者)可根据保存的 Memento(备忘录)信息还原到前一状态

备忘录类

使用备忘录可以将复杂的对象内部信息对其他对象屏蔽起来,从而恰当地保持封装的边界

使用注意点

角色状态需要完整存储到备忘录对象中,如果状态数据很大很多,在资源消耗上,备忘录对象会很耗内存

19 组合模式

概念解析

将对象组合成树形结构以表示“部分——整体”的层次结构。使得用户对单个对象和组合对象的使用具有一致性

方式

  • 透明方式

    在 Component 中声明所有用来管理子对象的方法,其中包括 add、remove 等,这样实现 Component 接口的所有子类都具备了 add、remove。好处是叶节点和枝节点对于外界没有区别,完全具备一致的行为接口。缺点是 Leaf 类本身不具备 add、remove 方法的功能,实现其没有意义

  • 安全方式

    在 Component 接口中不声明 add、remove 方法,子类的 Leaf 不需要实现其,而在 Component 声明所有用来管理子类的对象,不过由于不够透明,所以叶和枝类将不具有相同接口,客户端调用需要做相应判断,带来不便

适用场景

需求中体现部分与整体层次的结构时,及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象

优势

基本对象可以被组合成更复杂的组合对象,而组合对象又可以被组合,不断递归,客户端代码任何用到基本对象的地方都可以使用组合对象

用户不用关心到底是处理一个叶节点还是处理一个组合组件,不用为定义组合而写选择判断语句(可以一致地使用组合结构和单个对象)

20 迭代器模式

概念解析

提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示

内部原理

迭代器(Iterator)模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以不暴露集合的内部结构,又可以让外部代码透明地访问集合内部的数据

21 单例模式

实现关键点

  1. 构造方法私有化,使得外部不能通过 new 来获得实例对象
  2. 向外部提供静态(因为外部无法获得该类对象去调用其方法)公有方法获得实例,内部实现单例控制
  3. 声明静态类变量,用来存放实例化出的对象

概念解析

保证一个类只有一个实例,并提供一个访问它的全局访问点

通常我们可以让一个全局变量使得一个对象被访问,但不能防止实例化多个对象。最好的方法是让类自身负责保存它的唯一实例,这个类可保证没有其他实例可以被创建,并且它提供一个访问该实例的方法

优势分析

单例模式因为 Singleton 类封装它的唯一实例,这样它可以严格控制客户怎样访问它及何时访问它。对唯一实例的受控访问

多线程时的单例

给进程一把锁,lock 是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(被阻止),直至该对象被释放