设计模式概述

1. 设计模式简介

设计模式(Design Pattern)是软件开发过程中解决的一般问题的方法经验总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使编码真正工程化。每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案。
在这里插入图片描述

1.1 设计模式类型

设计模式大约有二十多种,主要可分为三大类:创建型设计模式、结构型设计模式、行为型设计模式。

下面将简要介绍各设计模式。

1.2 面向对象设计六大原则

  • 单一职责原则(The Single Responsibility Principle:SRP)
  • 开放封闭原则(The Open Closed Princeple:OCP)
  • 里氏替换原则(The Liskov Substitution Principle:LSP)
  • 迪米特法则(The Law of Demeter:LD)
  • 接口隔离原则(The Interface Segregation Principle:ISP)
  • 依赖倒置原则(The Dependency Inversion Principle:DIP)

也称为SOLID原则。

开闭原则要求我们写好的类不要去修改,如果需要增加功能,请扩展它。单一职责原则要求我们的一个类只做一件事情。里氏替换原则要求子类必须兼容父类。迪米特法则要求我们尽可能少的依赖其他的类。接口隔离原则要求我们定义接口的时候尽可能简单一些。依赖倒置原则要求我们不能依赖实现类,而应该依赖接口编程。

1.2.1 单一职责原则

一个类只做一件事,不要做与这个类主要职责无关的事。

1.2.2 开放封闭原则

对扩展开放,对修改封闭。类或接口定义好后,若无bug,若不是为了改进内部实现,则不应修改这个类。开闭原则是为了保持类或接口后续版本的兼容性。

1.2.3 里氏替换原则

所有父类出现的地方,都可以用子类替换。

1.2.4 迪米特法则

迪米特法则又叫最少知识原则,一个类应该对他所依赖的类知道的越少越好。高内聚,低耦合。

1.2.5 接口隔离原则

使用多个小的接口比使用一个臃肿的接口要更好。细的接口也有利于遵守最少知识原则。

1.2.6 依赖倒置原则

不要依赖具体实现,而应该依赖抽象。应该面向接口编程,而不是面向实现类编程。

2. 创建型

创建型设计模式提供了一种灵活的创建对象方式,同时隐藏了创建的逻辑。创建型设计模式包括5种:

  • 简单工厂模式:创建某一类对象
  • 工厂方法模式:创建工厂,工厂创建产品
  • 抽象工厂模式:创建多个种类的对象,创建系列产品
  • 单例模式:维护全局唯一对象
  • 建造者模式:拼接各个元素组装成一个对象
  • 原型模式:从现有原型对象拷贝产生对象

2.1 简单工厂模式

工厂模式中工厂类提供一个统一的创建的对象方法。根据输入参数创建不同子类型对象。

在开发中经常需要将日志输出,日志可以输出到控制台、文本文件、数据库、消息队列或者通过接口输出到其他系统。采用工厂模式设计一个日志输出工厂。

	// 日志抽象类
    public abstract class AbstractLogger
    {
        public abstract void write(string log);
    }

	// 控制台日志记录
    class ConsoleLogger : AbstractLogger
    {
        public override void write(string log)
        {
            Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {log}");
        }
    }

	// 文件日志记录
    class TXTLogger : AbstractLogger
    {
        public override void write(string log)
        {
            File.WriteAllText("log.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {log}");
        }
    }

	// 日志工厂
    public class LoggerFactory
    {
        public static AbstractLogger getLogger(string name)
        {
            if (name.Equals("console"))
            {
                return new ConsoleLogger();
            }
            else
            if (name.Equals("txt"))
            {
                return new TXTLogger();
            }
            return null;
        }
    }

		// 测试
        static void Main(string[] args)
        {
            var logger= LoggerFactory.getLogger("console");
            logger.write("this is Factory Pattern test");

            logger =  LoggerFactory.getLogger("txt");
            logger.write("this is Factory Pattern test");
        }

2.2 工厂方法模式

在简单工厂的基础上,抽象工厂,每个工厂可生成一种产品。通过创建不同的工厂对象生产产品。

2.3 抽象工厂模式

抽象工厂通过一个超级工厂创建普通工厂,由普通工厂创建系列产品。

在工厂模式中实现了一个日志输出工厂,但是这个日志输出工厂功能太单一,我们需要更丰富的功能。现在来通过抽象工厂模式改造日志输出工厂,我们需要能够输出调试信息、提示信息、错误信息,而且需要能够输出到控制台、文本文件、数据库、消息队列或者其他系统。

2.4 单例模式

单例模式可能是最最常用的模式,在项目中经常用到。
单例模式用于创建程序中只允许存在一个实例的类的对象。单例模式确保可以在程序的任意地方都访问同一个对象。

在多线程中需考虑单例类对象创建时的竞态条件

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。
导致竞态条件发生的代码区称作临界区。
在临界区中使用适当的同步就可以避免竞态条件。

2.5 建造者模式*

提供创建一组对象的统一实现。
例如,一个手机套餐包括:流量、通话时间、短信。

2.6 原型模式*

原型模式通过复制的方式来创建对象,在创建大量重复对象时非常快速。要注意浅拷贝和深拷贝。

3. 结构型

结构型关注类和对象的组合。

  • 适配器模式
  • 桥接模式
  • 过滤器模式
  • 组合模式
  • 装饰器模式
  • 外观模式
  • 享元模式
  • 代理模式

3.1 适配器模式

适配器是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能。将一个接口转换成客户希望的另外一个接口。

在工作中,我们公司的产品有标准的sdk,通常我们都是给客户提供标准sdk;有一次碰到一个客户,他们之前已经对接了其他厂家的sdk,他们比我们牛x,所以只能修改我们的sdk去适配他们的接口。通过在我们的sdk上在增加一层适配转换成客户需要的接口。

3.2 桥接模式*

在设计类的继承体系时,如果遇到了类型变化的方向沿两个或两个以上的维度变化的情况,如果采用继承来解决问题,就会导致一个大问题——类型数量会飞速膨胀。这个时候应采用组合的方法来解决问题,也就是组合优于继承。桥接模式就是贯彻这一理念诞生的又一个设计模式。

3.3 过滤器模式

最常见的过滤器模式,就是mybatis自动生成的条件查询对象,通过过滤器模式生成sql语句,返回满足条件的数据。

3.4 组合模式*

很多时候,对象与对象之间会产生一个树形结构。组合模式通过给这棵树的每个节点都抽象出一个相似的接口,屏蔽整体与部分的差别,极大的方便了对这棵树的操作。不管是操作一个节点,还是操作一棵树或一棵子树,操作都是统一的。

3.5 装饰器模式

允许向一个现有的对象添加新的功能,同时又不改变其结构。包装现有类,在保持类方法签名完整性的前提下,提供了额外的功能。

创建一个新类,包装现有类,且不改变现有类的方法签名。

c++ 中的shared_ptr有点这种意思。

3.6 外观模式

隐藏系统复杂性,向外部提供一个可以访问的接口。sdk写了很多接口,但是有些并不是客户关心和需要的,可以通过外观模式,把需要的接口暴露出来。

摄像头信息采集sdk中,涉及调焦、灯光控制、曝光控制等,一般的客户都不了解,虽然我们提供了接口,但是客户不需要,也不想客户误操作,只好通过外观模式把这些接口都隐藏起来。

3.7 享元模式

用于减少创建对象的数量,减少内存占用和提高性能。例如一幅图像,每个像素点都是一个对象,可能有百分之八十的像素点都是白色,则只需要创建一个白色像素对象,其他都是引用。这样就减少了内存占用。

享元:共享元素

原型模式是通过内存拷贝创建对象,可提高创建相同对象的速度。

3.8 代理模式

一个类代表另一个类的功能。
提供一种代理以控制这个对象的访问。

例如在C中,申请空间都是使用malloc,释放内存空间都是使用free,现在我需要一个统计程序内存使用量的功能。我们要如何做?创建两个新的new_malloc、new_free函数代理malloc、free函数,并在函数内添加内存使用量的计数功能。为了使new_malloc创建的内存不被free释放,并且记录每个内存申请的大小,需适当修改申请的内存的存储布局,即new_malloc返回的不是申请的内存空间的头地址,而是在申请时多申请4byte,前4个byte存放内存区大小,返回第5个byte的地址。(参考redis的内存分配)

代理模式和装饰器模式有相似之处,从外观上很难区分,之间的边界也比较模糊,但是我们可以从概念来区分:

  1. 装饰器模式用于增强自身功能,能力更强了
  2. 代理模式是让代理类做一些与被代理类本身业务无关的职责,代理模式是为了实现对对象的控制,不对本身功能进行增强

4. 行为型

行为型关注对象之间的通信(调用)。

  • 责任链模式
  • 命令模式
  • 解释器模式
  • 迭代器模式
  • 中介者模式
  • 备忘录模式
  • 观察者模式
  • 状态模式
  • 空对象模式
  • 策略模式
  • 模板模式
  • 访问者模式

4.1 责任链模式

为请求创建了一个接收者对象的链。例如tcp/ip中数据封包的生成和解包的过程:

  • 封包生成:原始数据 => (传输层)添加发送端口信息等信息头 => (网络层)添加发送ip等信息头 => (数据链路层)添加mac地址等信息头 => 封包
  • 解析封包:封包生成的逆过程

对某个对象进行类似“审批流程”的处理操作。职责链模式通过将一系列的“处理器对象”连接成一个链来处理消息,保持消息处理过程对消息发送者透明,实现发送者与处理者解耦。

4.2 命令模式

命令模式是一种数据驱动的设计模式。请求以命令的形式包裹在对象中,并传递给调用对象。调用对象寻找可以处理该命令的合适对象,并把该命令传递给相应的对象执行该命令。

// 老板写了一封信给财务,财务收到后执行老板的命令。
#include <vector>
#include <iostream>

using namespace std;

class command
{
public:
    virtual void execute() = 0;
};

class fagongzi : public command
{
public:
    void execute()
    {
        cout << "老板发工资啦" << endl;
    }
};

class daobi : public command
{
public:
    void execute()
    {
        cout << "公司倒闭,老板跑路了" << endl;
    }
};

class caiwu
{
private:
    vector<command *> _cmds;

public:
    void recvCmd(command *cmd)
    {
        _cmds.push_back(cmd);
    }

    void execCmd()
    {
        for (auto cmd : _cmds)
        {
            cmd->execute();
        }
        _cmds.clear();
    }
};

int main()
{
    command *cmd1 = new daobi;
    caiwu _caiwu;

    _caiwu.recvCmd(cmd1);
    _caiwu.execCmd();

    cout << "老板抓回来了" << endl;

    command *cmd2 = new fagongzi;
    _caiwu.recvCmd(cmd2);
    _caiwu.execCmd();
}

4.3 解释器模式

对给定的语言的文法创建一种表示(建立模型)并定义对应的解释器,用这个解释器去解释语句。解释器模式常用于实现表达式语言或文本处理。

例如:计算器输入计算表达式,计算结果;sql语句。

4.4 迭代器模式

迭代器模式用于顺序访问集合对象的元素,不需要指定集合对象的底层表示。

4.5 中介者模式

中介者模式是迎来降低多个对象与类之间的通信复杂性。这种模式提供了一个中介者类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。

例如聊天室,A和B之间互相发送信息,聊天室就是中介者,负责转发A与B之间的通信。
路由器也是一个中介者,通过MAC地址查找消息发送的目标。

4.6 备忘录模式

备忘录模式保存了一个对象的某个状态,以便在适当的时候恢复对象。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,并且能在适当的时候恢复对象。

例如:windows的ctrl+z回退;数据库的事务管理

4.7 观察者模式

当对象被修改时,自动通知对此对象感兴趣的目标对象。

例如:各种事件机制

4.8 状态模式

类的行为是基于它的状态的改变。状态模式通过按类的状态这个维度进行拆分,分离各个不同状态的逻辑。当一个对象具有很多不同的状态,且不同状态下对操作的响应不同,或者说执行某操作的判断情况的条件分支相关代码过于复杂时,状态模式会是解决这一问题的非常好的方案。

例如:项目申报中,在不同项目状态下,提交项目将执行不同的操作。

通过创建不同的状态类,来执行各个状态时的操作。

4.9 空对象模式

用空对象取代NULL对象实例的检查。空对象可以在数据不可用时提供默认的行为。

在有些地方直接使用NULL,额外的判断,若使用空对象,则不需要判断,且可以执行NULL时的默认行为。

4.10 策略模式

一个类的行为或其算法可以在运行时更改。通过改变策略对象,来改变对象的行为。

例如:超市收银,对不同的商品和不同的客户可以采取不同的计价策略。例如普通客户和VIP客户,商品满减、两件五折等,都可以通过策略模式调整计价。

4.11 模板模式/模板方法模式

一个抽象公开定义了执行它的方法的方法模板,它的子类可以按需要重写方法实现,但是调用将以抽象类中定义的方式进行。

抽象类定义好骨架,具体每个步骤的实现由子类自己定义。

4.12 访问者模式

对数据结构的操作封装,实现对数据结构的操作与数据结构本身分离。可以看作一个增强型的策略模式。

例如:一个文件目录数据结构,可以有文件查看访问者,文件搜索访问者;也可以增加其他的访问者,都与文件目录数据结构无关。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值