知识总结:设计模式总结(C++和Python实现)前言案例实现 创建型模式 结构型模式行为型模式对比总结

前言

GoF的23种设计模式,包括创建型、结构型和行为型,其涵盖了面向对象思想的精髓以及诸多细节。本文结合《设计模式》和《大话设计模式》,并用C++和Python实现了《大话设计模式》中的23种模式案例。原文首发于个人博客Jennica.Space。

创建型

结构型

行为型

案例实现

《大话设计模式》C++版

《大话设计模式》Python版

创建型模式

工厂方法模式(Factory    Method)

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

工厂方法把简单工厂的内部判断逻辑移到了客户端代码,本来需要修改工厂类,现在是修改客户端。

简单工厂模式违背了开放-封闭原则,工厂方法模式借助多态,克服了该缺点,却保持了封装对象创建过程的优点。

抽象工厂模式(Abstract    Factory)

抽象工厂模式:提供一个创建一系列相关或互相依赖对象的接口,只需要知道对象的系列,无需知道具体的对象。

在客户端中,具体工厂类只在初始化时出现一次,更改产品系列即可使用不同产品配置。

利用简单工厂类替换抽象工厂类及其子类,可以使客户端不再受不同系列的影响。

结合反射机制,Assembly.Load(“程序集名称”).CreateInstance(“命名空间”.“类名”),可以直接通过字符串创建对应类的实例。所有在简单工厂中,都可以通过反射去除switch或if,解除分支判断带来的耦合。

反射中使用的字符串可以通过配置文件传入,避免更改代码。

单例模式(Singleton)

单例模式:让类自身保证它只有一个实例,并提供一个全局访问点。

多线程下单例模式可能失效,需要采取双重锁定的的方式,确保被锁定的代码同一时刻只被一个进程访问。

饿汉式单例:即静态初始化方式,在类初始化时产生私有单例对象,会提前占用资源;渴汉式单例:在第一次被引用时将自己初始化,会产生多线程访问安全问题,需要添加双重锁定。

建造者模式(Builder)

建造者模式:将复杂对象的创建与表示分开,使得相同的创建过程可以有不同的表示。用户只需制定需要建造的类型,不需要知道建造的过程和细节。

指挥者是建造者模式中重要的类,用于控制建造过程,也可以隔离用户与建造过程的关联。

建造者隐藏了产品的组装细节,若需要改变一个产品的内部表示,可以再定义一个具体的建造者。

建造者模式是在当前创造复杂对象的算法,独立于该对象的组成部分和装配方式时适用的模式。

原型模式(Prototype)

原型模式:用原型实例指定创建对象的种类,并通过拷贝这些原型创建对象。本质是从一个对象再创建另一个可定制的对象,并且不需要知道创建细节。

原型抽象类的关键是有一个Clone()方法,原型具体类中复写Clone()创建当前对象的浅表副本。

对.Net而言,由于拷贝太常用原型抽象类并不需要,在System命名空间中提供了ICloneable接口,其中唯一的方法就是Clone(),只要实现这个接口就可以完成原型模式。

原型拷贝无需重新初始化对象,动态获取对象的运行状态。既隐藏了对象创建的细节,又提升性能。

在具体原型类中,MemberwiseClone()方法是浅拷贝,对值类型字段诸位拷贝,对引用类型只复制引用但不会把具体的对象值拷贝过来。

比起浅拷贝,深拷贝把引用对象的变量指向新对象,而不是原被引用的对象。对于需要深拷贝的每一层,都需要实现ICloneable原型模式。

数据集对象DataSet,Clone()是浅拷贝,Copy()是深拷贝。

结构型模式

代理模式(Proxy)

代理模式:为其他对象提供一种代理以控制对这个对象的访问。实际上是在访问对象时引入一定程度的间接性。

远程代理:为一个对象在不同地址空间提供局部代表,隐藏一个对象存在于不同空间的事实。如.Net加入Web引用,引入WebService,此时项目会生成WebReference的文件夹,就是代理。

虚拟代理:根据需要创建开销很大的对象,通过它存放实例化需很长时间的真实对象。HTML中的多图,就是通过虚拟代理代替了真实图片,存储路径和尺寸。

安全代理:控制真实对象的访问权限,用于对象应该拥有不同的访问权限时。

智能指引:当调用真实对象时,代理处理一些另外的事情。通过代理在访问对象时增加一些内务处理。

适配器模式(Adapter)

适配器模式:当系统数据和行为都一致,只有接口不符合时,将一个类的接口转化为客户端期望的另一个接口。

适配器模式用于服用一些现存的类,常用在第三方接口或软件开发后期双方都不易修改的时候。

在.Net中DataAdapter是用于DataSet和数据源间的适配器,Fill更改DataSet适配数据源,Update更改数据源适配DataSet。

外观模式(Facade)

外观模式:为子系统中一组接口提供一个一致的界面,即定义一个高层接口,增加子系统的易用性。

外观模式完美体现了依赖倒转原则和迪米特法则。

设计初期阶段,在MVC三层架构中,任意两层间建立外观Facade。

子系统会因不断演化变得复杂,增加外观Facade提供简单简单接口减少依赖。

在维护一个大的遗留系统时,新的开发又必须依赖其部分功能。此时,开发一个外观Facade类,从老系统中抽象出比较清晰的简单接口。让新系统只与Facade交互,而Facade与遗留代码交互所有的工作。

装饰模式(Decorator)

装饰模式:动态的给一个对象添加一些额外的职能,把所需功能按顺序串联起来并进行控制。

每个要装饰的功能放在单独的类中,并让这个类包装它所要修饰的对象。当需要执行特殊行为时,客户端就可以根据需要有选择的、有顺序的使用装饰功能包装对象了。

装饰模式有效的把类的核心职能和装饰功能区分开了,并且可以去除相关类中重复的装饰逻辑。

桥接模式(Bridge)

对象的继承关系编译时已确定,所以无法在运行时修改从父类继承的实现。由于紧耦合,父类中任何的改变必然会导致子类发生变化。当需要复用子类,但继承下来的方法不合适时,必须重写父类或用其他类替代。这种依赖性限制了灵活性和复用性。

合成/聚合复用原则:尽量使用合成和聚合而不是继承。可以保证每个类封装集中在单个任务上,不会出现规模太大的类及继承结构。

桥接模式:抽象类和其派生类分离,各自实现自己的对象。若系统可以从多角度分类,且每种分类都可能变化,则把多角度分离独立出来,降低耦合。

享元模式(Flyweight)

享元模式:运用共享技术有效支持大量细粒度对象。

在享元模式对象内部不随环境改变的共享部分是内部状态,不可共享需要通过调用传递进来的参数是外部状态。

使用享元模式的场景包括,一个应用程序产生了大量的实例对象,占用了大量内存开销;或对象的大多数状态为外部状态,删除内部状态后可以用较少的共享对象来取代组对象。

应用场景有正则表达式、浏览器、机器人指令集等。

组合模式(Composite)

组合模式:将对象的组合以树形的层次结构表示,对单个对象和组合结构的操作具有一致性。

透明方法:叶子和分枝对外接口无差别;安全方法:分枝具有添加删除叶子的接口,低层抽象接口和叶子没有。

基本对象组合成组合,组合又可以被组合,不断递归下去,在任何用到基本对象的地方都可以使用组合对象。

行为型模式

职责链模式(Chain  of Responsibility)

职责链模式:使多个对象都有机会处理请求,解除请求发送者和接收者的耦合。将对象连成一条链,并沿这条链传递请求直到请求被解决。

请求交付给最小接受者,职责链中每一环保存后继的引用,使得请求有序沿链传递。

通过合理设置后继以及分支关系,避免一个请求到了链末端依旧无法被处理,或因配置错误得不到处理的情况。

策略模式(Strategy)

面向对象中并非类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

策略模式:定义算法家族并分别封装,他们完成的工作相同,只是实现不同,可以互相替换。继承有助于析取这些算法的公共功能。此模式让算法的变化不会影响到使用算法的用户。

策略与工厂模式结合,使客户端需要认识的类减少,耦合度更加降低。

策略模式可以简化单元测试,因为每个算法可以通过自己的接口单独测试。

只要在不同时间内应用不同的业务规则,就可以考虑用策略模式来处理这种变化的可能性。

状态模式(State)

拥有过多分支的过长方法违背了单一职责原则,而且当需求变化时修改代码往往会违背开放-封闭原则,应该将分支变成一不同小类,将状态的判断逻辑转移到小类中。

状态模式:一个对象可能拥有多种状态,当内在状态改变时允许改变行为。

状态模式的好处是将与特定状态有关的行为局部化,并将不同状态的行为分隔开。

观察者模式(Observer)

观察者模式:多个观察者对象同时监听某一主题(通知者)对象,当该主题对象状态变化时会通知所有观察者对象,使它们能更新自己。

具体观察者保存一个指向具体主题对象的引用,抽象主题保存一个抽象观察者的引用集合,提供一个可以添加或删除观察者的接口。

抽象模式中有两方面,一方面依赖另一方面,使用观察者模式可将两者独立封装,解除耦合。

观察者模式让主题和观察者双方都依赖于抽象接口,而不依赖于具体。

委托就是一种引用方法类型。委托可看作函数的类,委托的实例代表具体函数。在主题对象内声明委托,不再依赖抽象观察者。

一个委托可以搭载多个相同原形和形式(参数和返回值)的方法,这些方法不需要属于一个类,且被依次唤醒。

迭代器模式(Iterator)

迭代器模式:提供一种方法顺序遍历一个聚集对象,为不同的聚集结构提供遍历所需接口,而不暴露对象内部的表示。

在高级编程语言如c#、c++、java等,都已经把迭代器模式设计进语言的一部分。

迭代器模式分离了对象的遍历行为,既不暴露内部结构又可以让外部代码透明的访问集合内部的数据。

备忘录模式(Memento)

备忘录模式:不破坏封装,获取对象内部状态并在其之外保存该对象,以便其未来恢复到当前状态。

Orginator负责创建Memento,Memento封装Originator状态细节,Caretaker负责保管和交付Memento。

备忘录模式适用于需要维护历史状态的对象,或只需要保存原类属性中的小部分。

命令模式(Command)

命令模式:将请求分装为对象,将请求和执行分开,可以用不同的请求对客户参数化。可以对请求排队、通过或否决、记录日志、撤销或重做。

基于敏捷开发原则,不要给代码添加基于猜测而实际不需要的功能,在需要的时候通过重构实现。

模板方法模式(Template    Method)

模板方法模式:定义一个操作中的算法框架,将一些步骤延迟到子类中。子类在不改变框架的前提下就可以重新定义某些特定步骤。

当不变和可变的行为在子类中混到一起时,可以通过把重复的行为移到同一地方,帮助子类摆脱重复不变行为的纠缠。

中介者模式(Mediator)

中介者模式:用一个中介对象来封装一系列对象间的交互。

中介者模式在系统中易用也容易被误用,当系统中出现了多对多的交互复杂的对象群时,更应考虑设计的问题。

由于控制集中化,中介者模式将交互复杂性变成了中介者的复杂性,中介者类会比任何一个同事类都复杂。

中介者模式应用的场合有,一组对象以定义良好但复杂的方式进行通信,以及想定制一个分布在多个类中的行为却不想产生太多子类。

解释器模式(Interpreter)

解释器模式:给定一种语言,定义它文法的一种表示,再定义一个解释器,使用该表示来解释语言中的句子。

如果一种特定类型发生的频率足够高,就可以将其实例表达为一个句子,构建解释器来解析。

解释器模式就是用“迷你语言”来表现程序要解决的问题,将句子抽象为语法树。由于各个节电的类大体相同,便于修改、扩展和实现。

解释器为文法中的每条规则定义了一个类,当文法过多时将难以维护,建议使用其他技术如语法分析程序或编译器生成器处理。

访问者模式(Visitor)

访问者模式:在不改变各元素的前提下定义作用于这些类的新的操作。

访问者模式使用双分派,将数据结构和作用于结构上的操作解耦,意味着执行的操作决定于请求的种类和接收者的状态。

如果系统具有较为稳定的数据结构,又有易于变化的算法操作,则适合使用访问者模式。

对比总结

工厂方法模式:为不同子类创建不同工厂;

抽象工厂模式:为不同系列建造不同工厂;

单例模式:保证实例唯一;

建造者模式:为不同类组装出一套相同的方法;

原型模式:实现深拷贝。

代理模式:控制访问;

适配器模式:将接口转换为客户端期望的形式;

外观模式:整理出一套可用接口;

装饰模式:动态修改类的职能;

桥接模式:将多角度分类分离独立;

享元模式:共享实例;

组合模式:递归生成树形结构的组合对象。

职责链模式:按顺序让负责的对象们依次处理;

策略模式:将算法族抽象封装;

状态模式:将复杂的状态转移方式下发到每个状态内部;

观察者模式:发布和订阅;

迭代器模式:遍历容器;

备忘录模式:在对象之外备份及恢复;

命令模式:封装请求与执行分开;

模板方法模式:提炼共有方法。

中介者模式:用中介对象封装交互。

解释器模式:迷你语言;

访问者模式:解耦数据结构及操作。

本文来源于牛客网

作者:尤汐_Jennica

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

VLC播放器加载恶意字幕文件导致执行任意代码漏洞分析与POC实现

今年5月23号的时候,听说checkpoint搞了个大新闻:vlc等播放器加载特定字幕可以完全控制用户电脑。当时我就震惊了:还有何种操作。想想看,当你吃着辣条,...

43240
来自专栏java一日一条

java提高篇之异常(上)

在这个世界不可能存在完美的东西,不管完美的思维有多么缜密,细心,我们都不可能考虑所有的因素,这就是所谓的智者千虑必有一失。同样的道理,计算机的世界也是不完美的,...

11620
来自专栏技术小黑屋

你的Java代码对JIT编译友好么?

本文为 InfoQ 中文站特供稿件,首发地址为:你的Java代码对JIT编译友好么?。如需转载,请与 InfoQ 中文站联系。

30620
来自专栏Vamei实验室

Java基础07 包

我们已经写了一些Java程序。之前的每个Java程序都被保存为一个文件,比如Test.java。随后,该程序被编译为Test.class。我们最终使用$java...

215100
来自专栏海说

深入理解计算机系统(3.2)---数据格式、访问信息以及操作数指示符

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer14.html

14950
来自专栏Kevin-ZhangCG

Java开发岗面试知识点解析

387110
来自专栏海说

深入理解计算机系统(3.2)---数据格式、访问信息以及操作数指示符

  本文的内容其实可以成为汇编语言的基础,因为汇编语言大部分时候是在操作一些我们平时开发看不到的东西,因此本文的目的就是搞清楚,汇编语言都是在操作些什么东西。或...

13640
来自专栏C/C++基础

*** glibc detected *** malloc(): memory corruption

在Linux Server上不好模拟出来:不过若是先malloc,再越界memset,再free此内存块,然后malloc新内存块就会出现类似错误。

39120
来自专栏北京马哥教育

Python入门教程:超详细1小时学会Python

1.Hello world 安装完Python之后,打开IDLE(Python GUI) ,该程序是Python语言解释器,你写的语句能够立即运行。 我们写下...

46850
来自专栏JetpropelledSnake

Linux学习笔记之Redis中5种数据结构的使用场景介绍

原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码。目前目标是吃透 redis 的数据结构。我们都知...

13010

扫码关注云+社区

领取腾讯云代金券