对设计模式的理解
一切设计,都围绕着抽象与具体展开!大道至简!
- 抽象:一般指接口。里面没有方法细节,只有方法签名。方法签名告诉你它能干什么,但不提供怎么干
- 具体:所有具体类都应该是单一职责的。具体可以依赖抽象,程序运行过程中,会有该抽象的具体实现替代抽象。且具体类要符合最少知道原则,只开放必要的方法和属性。
封装: 体现了类的单一职责
接口里可以有多个方法签名,每个方法签名是抽象的最小单位
接口隔离原则: 表明客户端不应该被强迫实现一些他们不会使用的接口,应该把胖接口中的方法分组,然后用多个接口替代它,每个接口服务于一个子模块。简单地说,就是使用多个专门的接口比使用单个接口要好很多。
依赖倒转: 针对接口编程,依赖于抽象而不依赖于具体。
里氏替换与多态
- 相同点:对于二者,父类的引用可以指向子类的实例。
- 不同点:对于多态来说,子类可以重写或继承父类的全部方法。而里氏替换原则不建议重写方法。
- 因为要遵守里氏替换原则,所以我们一般用多态的时候,都应该是里氏替换的。
继承: 继承是一个复用父类的同时,扩展父类的概念(因为复用,所以耦合,所以少用继承,少扩展具体类,多实现接口、抽象类)。继承要遵守里氏替换原则,即不要重写父类的方法。
开闭原则: 对扩展开放,对修改关闭。就是说,类写好了就不能改。非要改的话,就再创建一个接口,写一个新的具体类来实现该接口和之前要修改的类。这样,就用接口扩展了之前要修改的类。
1. 单例模式
- 核心在于保证该类的实例在全局范围只有1个;同时要保证线程安全,即多线程的情况下也确保只实例化1个实例。
- 基本实现:构造函数私有,类内有一个私有的字段存储该类的实例。然后在一个公开的方法的内部实例化并返回该类的实例。
- 为啥要保证全局内只有1个实例?为了节省系统资源,避免实例被频繁的创建和销毁,这样就提升了性能。而且1个实例,也是为了共享同一个实例的状态。
- 应用场景:
- 网站的计数器
- 操作系统的文件系统,一个操作系统只能有一个文件系统
- windows 的任务管理器,你只能打开一个
2. 简单工厂模式
首先把产品抽象出一个父类,父类下有几个子类产品继承它。然后有一个具体工厂类,该工厂类有一个公开的方法,可以根据传参创建任意子类产品。
类比:有个披萨店(具体工厂类),经营3种披萨:土豆披萨(具体产品子类)、海鲜披萨(具体产品子类)、牛肉披萨(具体产品子类)。张三说要海鲜披萨,然后披萨店就(生产)做一个海鲜披萨给他。李四说要牛肉披萨,披萨店做了一个牛肉披萨给他。王五说要吃河豚披萨,披萨店表示无能为力。
3. 工厂方法模式(或称为工厂模式)
该模式是基于上面的简单工厂模式所做的改进:
- 这里的工厂和上面的简单工厂一样,依然只生产一种类别的产品。把这种类别抽象出一个抽象产品类,从这个抽象产品类可以派生出多个具体产品子类。
- 把上面工厂类给抽象化成一个抽象工厂类,该抽象工厂类只能有一个用于创建这种产品的方法签名。为啥只能有一个,因为这个模式的关注点是创建同一类别的不同产品(而不是不同类别的不同产品),以后该类别下有了新的产品,再加一个具体产品类即可。
- 有几个具体的产品,就有几个具体工厂子类。由这些具体工厂子类来确定生产的具体产品。
类比:
-
说有个披萨店在开了两家分店,其中有北京的披萨分店(提供土豆披萨、牛肉披萨)、上海的披萨分店(提供海鲜披萨、河豚披萨)。而且各分店都提供外卖,通过美团App可以订外卖。
解说:- 注意这里的4个具体的披萨都同属于一种类别:“披萨”;
- 北京分店、上海分店都是具体的工厂类。你可以想象有一个虚拟的抽象工厂类,派生出了这两个具体工厂类。北京分店可以生产两个具体产品子类是:土豆披萨、牛肉披萨,上海分店可以生产两个具体产品子类是:海鲜披萨、河豚披萨;
- 但是注意,不管北京分店还是上海分店,都只有一个生产产品的方法,只不过这一个方法可以根据传参来决定生产两个披萨中的哪一个。
-
说这个披萨店又扩了一家分店,杭州分店。杭州分店提供胡椒披萨、奶酪披萨。
解说:- 扩了一家分店,就是加了一个具体工厂类。
- 这家新分店提供的胡椒披萨和奶酪披萨,通过这个新加的具体工厂类来生产。
- 这样,就做到了对扩展开放,对修改关闭。
4. 抽象工厂模式
- 此模式下的工厂与上面的工厂模式相比,区别是可以创建多个类别的产品。所以当此模式的工厂只需要创建一个类别的产品时,抽象工厂模式就变成了工厂模式。
- 抽象工厂模式的工厂,用于生产多种产品。而工厂模式只生产一种产品。这就是二者的区别。多个产品可以属于一种产品,所以一种产品指的是对这几个产品的一个抽象。
- 这里可以把一个具体工厂生产的不同种类产品的组合,称为一套。
- 扩展点1:因为一个具体的工厂生产一套具体的产品,所以你可以加新的具体工厂来生产不同套的具体的产品。此时,只需要加一套具体的产品类,和一个具体的工厂类即可。
- 扩展点2:要是有了新的产品种类和新的属于此种类的产品,就需要在抽象工厂里加一个生成这个新的产品种类的产品的方法。然后,还需要在具体工厂类里实现这个方法。
类比:
-
修改一下工厂模式的类比,依然是一开始只有北京和上海两家分店,但是这两家分店现在除了做披萨,现在又开始做汉堡和蛋糕了。以下是两家分店修改后的完整菜单:
- 北京分店提供:土豆披萨、牛肉披萨、吉士汉堡、提拉米苏
- 上海分店提供:海鲜披萨、河豚披萨、田园鸡腿堡、榴莲千层蛋糕
解说:
- 北京和上海分店的菜单里总共提到了4个披萨(具体产品子类),这4个披萨都派生于“披萨”这个父产品类。
- 北京分店提供的吉士汉堡(具体产品子类)和上海分店提供的田园鸡腿堡(具体产品子类),都派生于“汉堡”这个父产品类。
- 北京分店提供的提拉米苏(具体产品子类)和上海分店提供的榴莲千层蛋糕(具体产品子类),都派生于“蛋糕”这个父产品类。
- 北京分店、上海分店都是具体的工厂类。你可以想象有一个虚拟的抽象工厂类,派生出了这两个具体工厂类。北京分店或上海分店都可以生产各自的多种(一套)具体产品。
-
说这个披萨店又扩了一家分店,杭州分店。杭州分店提供胡椒披萨、板烧鸡腿堡、巴斯克芝士蛋糕。
解说:
那么代码怎么改?需要加一个具体工厂类表示杭州分店,然后在三个父类(披萨、汉堡、蛋糕)下各派生出一个子类,即可完成扩展。 -
说披萨店新增了一种产品,叫饮料。
解说:
代码改动:在抽象工厂类里新增一个方法,用于生产饮料。在所有的具体工厂类里实现这个方法,每个具体工厂类生产不同的饮料(比如北京分店生成的饮料是乌龙茶,上海分店生产的饮料是红酒)。
5. 原型模式
原型模式讨论的点是怎么克隆一个对象,怎么克隆一个对象呢?答案是让这个对象自己克隆自己。
克隆是一种能力,不是所有对象都需要这个能力的,所以可以把“克隆”的这种能力抽象为一个接口。如果一个具体类实现了这个接口,就可以对外宣称这个类具备了这种能力,即,我的实例对象能克隆自己。
当然克隆,可以是浅拷贝,也可以是深拷贝,根据需求而定。
而且,克隆的结果,只反应了对象在某个时间点的状态。
这种模式需要改动已经存在的类,所以就违背了开闭原则(对修改关闭)。不过,因为只是实现一个克隆接口,其实也还好。
类比
-
2010年的时候,包不同还在上大二,有一次计算机课,他有两个很铁的同学翘课,由于老师这次上课没有点名,这两个同学得知此消息后拜托包不同代他们交课堂作业。
于是包不同这么干的:他先自己做完课堂作业(是一个word文档),然后把作业拷贝出了两份,在每一份作业里都稍微改改,并且把文档的名字分别改成两个同学的名字。然后拷贝到老师要求的局域网共享目录。
解说:
典型的原型模式应用场景。在这里word文档是具体类的实例,可以把这个具体类想象成已经实现了具有克隆能力的接口。所以通过调用两次具体类实例的克隆方法,就可以克隆出两个一模一样的word文档。然后根据需求(要改文档的人名,以及为了防止老师看出是抄袭而略作修改)对两个文档各自修改。 -
此时离下课还10分钟,包不同忽然想起自己刚才交的作业有点问题,于是他修改了之前的word文档,同时他没管那两个同学的文档,把自己的文档重新上传了上去。
解说:
包不同的文档的状态发生了改变,而他刚才给两个同学上交的文档还停留在刚才的状态。
6. 建造者模式
Build模式,所有的设置在builder的构建方法中完成
建造者模式解决的痛点是:你要创建一类比较复杂的对象,构建的过程是绝对稳定的,但是构建的每一步可以不一样。所以此模式的关注点是:分步构建一个对象、构建过程清晰明确有顺序。 比如我在餐馆里点一盘鱼香肉丝,我不吃辣,而且希望少油,我就可以让餐馆在放辣椒、放油的环节不放辣、少放油。其他人点鱼香肉丝没有忌口,就可以说没有忌口,端上来的菜自然就多油且比较辣。 再比如你平时用到的StringBuilder、IApplicationBuilder(WebAPI或MVC里用到的对象,用来给http请求管道加各种中间件)
建造者模式的4个角色:
- Product(产品):最终要构建出的具体的产品。
- Builder(抽象建造者):这个角色抽象出了用于构建具体产品的各个步骤。
- ConcreteBuilder(具体建造者):把抽象建造者中的各个建造步骤给具体化实现了,每个具体建造者的各个建造步骤肯定是不一样的, 要是一样的话就没必要写好几个具体建造者了。
- Director(指挥者):抽象建造者中交待了我建造一个产品需要哪些步骤,但是没告诉你这些步骤的执行顺序。指挥者会确定各个步骤的执行顺序,并按这个顺序执行一遍。
7. 适配器模式
适用场景:
- 你自己的代码需要一种功能,而且你已经为此功能提前写好了接口。
- 你有一段能实现此功能的现成代码。这段代码可以是一个或若干个类,可以是dll的形式,或者是纯代码的形式。
怎么用你的接口调用这段代码?
- 你的接口不能改,你找到的这段代码也没有必要改。
- 你只需要写一个实现你定义的接口的中间类,在这个中间类里包装这段代码,将这段代码的功能重新整合到你的接口的各个方法里即可。
- 这个中间类即“适配器”,它把“这段外来代码的使用方式”适配了“你的接口的使用方式”。
8. 桥接模式
注:接口与实现分离,并且各自(接口与实现)独立变化
桥接模式太常用了,常用到你都不知道你已经用了不知道多少次它。
常说“组合/聚合优于继承”,而这个模式玩的就是组合/聚合。
所谓桥接,就是扩展的意思,扩展的必然是功能。而这个功能通常都是用一个具体类来表示。
那么给谁扩展功能?所以肯定要有一个主类,然后为这个主类扩展功能。
把一个或多个功能桥接到主类,就是为这个主类扩展了一个或多个功能。
为啥说它特别常用,举例子:你的主类的构造函数接收一个类的对象,或者你的主类有个公开的方法,这个方法可以接收一个类的对象。接收到对象后,可以把这个对象的引用存储到主类的一个内部属性里。然后在主类的内部,就可以使用传进来的对象提供的功能了。
9. 装饰者模式
装饰模式处理的问题是什么?
此模式处理的问题是,对象的一个核心功能与N个衍生功能的组合问题。
某对象有一个核心功能,N个衍生功能,这些衍生功能不是任何时刻都会用到,所以用到的时候就把衍生功能作为装饰,装饰到核心功能上。这就是装饰者模式的设计初衷。
此模式用的不是继承,而是组合。装饰者把被装饰者组合到自身,然后把自身衍生功能进一步强化被装饰者的核心功能。
装饰者装饰被装饰者之后的对象依然能看作被装饰者,举例来说,咖啡加了奶之后,还是咖啡,再加点糖,还是咖啡。咖啡是核心功能,奶、糖都是衍生功能,你可以根据你的需要为咖啡加奶或加糖,或者都加上。
装饰者和被装饰者之间必须是一样的类型,也就是要有共同的父类。在这里父类、子类之间的继承并不是为了实现方法的复制,而是为了实现类型的匹配。因为装饰者和被装饰者同属一个类型,因此装饰者就可以取代被装饰者。这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者为被装饰者增加新的行为。
10. 组合模式
组合模式的定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
关键点是树形结构,树形结构里有叶子节点和非叶子节点,它们都有共同的父类,所以二者的使用方式是一致的。
11. 外观模式
外观模式用的是组合。假设有N个类组成了一个子系统,然后用一个外观类把N个类组合到自身。外观类耦合子系统中的N个类,但是子系统中的N个类全都不耦合外观类。主程序通过使用外观类来使用子系统中的N个类。
类比:
说有一只基金,这只基金内有10只权重股。基民购买20000元的这只基金后,基金经理会把这20000元按10只股票的权重关系分别进行买入。
在这里,基金是外观类,10只股票构成一个子系统,基金耦合了10只股票,10只股票并没有耦合该基金。对基金发起买入操作,基金就会按10只股票的权重比,依次调用10只股票的买入操作。
12. 享元模式
这也是很常用的模式。它一个对象缓存池的存在。
- 比如各种池概念(数据库连接池、多线程的线程池)。数据库连接池里有多个被缓存的数据库连接对象,每当代码里用到相同连接字符串的数据库连接对象时,就直接从池子里取即可。
- 比如创建一个字典,一个键值对的键是名称,值是一个对象。你可以从字典里根据名字取对象,如果字典里以及有了名字对应的键,可以直接取到对象;反之,就创建该对象,并把名字和对象作为键值对存储到字典里供后面可以快速地根据名字取到对象。
享元模式对内外部状态的依赖
- 享元模式有内部状态和外部状态。实现享元模式的关键是把内部状态和外部状态分离开来。有多少种内部状态的组合,系统中便最多存在多少个共享对象。
- 外部状态储存在共享对象的外部,在必要时被传入共享对象来组装成一个完整的对象。
享元模式的使用场景
- 一个程序中使用了大量相似的对象。
- 由于使用了大量对象,造成很大的内存开销。
- 对象的大多数状态都可以变为外部状态。
- 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
13. 代理模式
代理就是中间人,当一个对象不方便直接访问或者不满足需求的时候,需要提供一个替身对象来控制这个对象的访问。替身对象在作出一些处理之后,再把请求转交给本体对象进行处理。
替身对象的主要作用是先作出一些处理,再把处理后的适合本地对象的部分交给本体对象处理。
何时使用: 想在访问一个类的时候对其做一些控制。
实现代理模式的关键点:
代理类和委托类要实现同一个接口(代理类即替身,委托类即本体)
在委托类中实现功能,在代理类的方法中使用委托类的同名方法
代理模式的使用场景:
- 当我们想要隐藏某个类时,可以为其提供代理类
- 当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中进行权限判断来进行不同权限的功能调用)
- 当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行。面向切面编程?)
14. 模板方法模式
模板方法模式的定义: 定义一个操作中的算法的骨架,而将一些步骤延迟到它的子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
要点: 在一个类中定义了算法的骨架,即骨架是确定了的。但是算法的某些特定步骤不能确定,这些不能确定的特定步骤就交给子类来实现。父类中的算法骨架可以有一些确定的算法步骤,而那些不确定的步骤或子步骤在子类中实现。
一句话: 封装算法里不变的部分,扩展可变的部分;行为由父类控制,子类负责单独步骤的实现。
15. 命令模式
假设有这样一个场景: 一个调用者类,一个或多个接收者类,在调用者的代码里可以调用一个或多个接收者的方法。此时调用者类和接受者类都是具体类,它们之间是强耦合关系。
如果有多个接收者,这多个接受者都有相似的命令,比如这么几个接收者:电视、空调、电灯、收音机。它们都有开和关这两个功能。这样的话,你就可以抽象出一个命令接口,这个接口里有两个方法,一个是开,一个是关,然后写8个具体的命令类,都实现这个接口,这8个类分别实现了4种电器的开和关的功能。
然后写一个调用者,比如遥控器,作为调用者,你可以积攒几个命令对象之后,比如说积攒了开灯、开电视、关灯、开收音机这几个命令对象,然后再一次性执行所有命令。在每个命令执行的过程中,因为命令在实例化时都会关联具体的接收者,所以直接调用每个命令的用于执行自身命令的方法即可。
所以命令模式的特征就是: 有N个接收者,假设平均每个接收者有3个可以具体化为命令类的功能,那就有N*3个具体命令类。虽然把接收者从调用者的代码中解耦掉了,但是也多出了N个具体命令类。
可以设计一个命令队列,把新的命令对象一个一个的放入此队列,只要这些命令的执行没有什么顺序性,就可以用多线程来执行这些命令。
16. 访问者模式
定义: 封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
优点: 把对元素的操作抽象出一个抽象访问者类,即,对元素可以有各种操作,把这些操作从元素中分离出来,还能通过更换不同的具体访问者变换对元素的操作。(前提是在元素内预留一个对外接待具体访问者的接口)
适用场景:
- 要有一个有多个元素的数据结构
- 该数据结构内的元素需要被处理,但是处理方式经常会变。这个也是访问者模式的扩展点,即,当元素需要新的处理方式时,可以增加具体访问者。
怎么做:
- 该模式要把访问者抽象出一个抽象访问者,要把元素抽象出一个抽象元素类
- 抽象元素类里有一个公开的方法,可以接待一个访问者(关键是把元素自身的引用交给访问者来处理)
访问者模式的现实生活举例:
- 公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;
- 电影或电视剧中的人物角色,不同的观众对他们的评价也不同;
- 顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。
迭代器模式
迭代器模式关注点在于怎么迭代集合,怎么迭代呢?用迭代器迭代,所以可以抽象出一个抽象迭代器,然后通过具体迭代器实现具体的迭代方式。
抽象迭代器,提供了遍历集合的统一接口,不论你用哪个具体迭代器遍历集合,都是用这些接口。
一般集合都要被一个聚合对象包装起来,所以可以抽象出一个抽象聚合类,然后不同的具体聚合类包装不同类型的集合。客户端会依赖这个抽象聚合对象,而不用耦合具体的集合。
一般迭代器模式都是怎么用的?
假设有3种类型的集合数据,分别是x、y、z,而且这3种类型都是自定义类型的集合。但是有个方法要用到这3种集合数据,这个方法的传参(只有一个参数)应该怎么写?
此时,就可以用迭代器模式来解决这个问题,把这3种类型包装成3个具体聚合类,再写一个统一的具体迭代器类,这就可以了。把抽象迭代器作为这个方法的参数即可。给这个方法传递实参的时候,分别传递每种结合的具体迭代器实例即可。
17. 观察者模式
定义: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使观察者自己更新自己(用主题对象的状态)。
什么时候使用观察者模式: 当一个对象的改变需要同时改变其他(多个)对象的时候。
观察者模式的作用: 做到了具体主题对象和具体观察者对象的解耦,以为二者依赖的都是抽象的观察者或主题。
实现此模式的必要条件
- 观察者(Observer)要公开一个update()这样的方法,在主题对象状态改变后,批量调用众多观察者时使用;
- 主题对象(Subject)的抽象类或接口要有可以添加、移除观察者的公共方法,以及统一通知多个观察者调用update()的公共方法。
18. 中介者模式
参考: https://www.cnblogs.com/hulizhong/p/11655641.html(有个房东与中介的例子)
参考: https://www.kancloud.cn/cyyspring/more/1398240
定义: 用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式的4个类:
- 抽象中介者:定义了各个同事之间交互需要的方法。
- 具体中介者:需要了解维护各个同事对象,并且负责协调各个具体同事之间的交互。
- 抽象同事类:约束具体同事类的类型、并且实现一些具体同事类之间的公共方法。
- 具体同事类:实现自己的业务。同事类之间不会相互引用。
中介者模式适用场景: 多个类之间相互耦合,会形成网状结构。使用中介者模式将网状结构分离为星型结构,进行解耦。但是中介者承担了较多的责任,一旦中介者出现问题,整个系统就会受到影响。
- 主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
- 何时使用:多个类相互耦合,形成了网状结构。
- 如何解决:将上述网状结构分离为星型结构。
- 使用场景
- 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
- 优点
- 降低了类的复杂度,将一对多转化成了一对一。
- 各个类之间的解耦。 3、符合迪米特原则。
- 缺点:中介者会庞大,变得复杂难以维护。
类比:
MVC 模式(Model-View-Controller)就用到了中介者模式,Controller 就是 Model 和 View 的中介。
19. 备忘录模式
参考: http://c.biancheng.net/view/1400.html
定义: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
提供了一种恢复对象状态的机制,使对象可以方便的回到某个历史状态。
备忘录模式的三个角色(是3个具体类)
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。注:管理者可以管理同一个发起人的一个或多个备忘录实例。
现实中的例子:
- word、Photoshop等各种软件的Ctrl+Z,使文档恢复到之前的状态。
- 数据库事务中的回滚操作。
- 玩角色扮演类游戏,可以存档,可以存很多存档。
20. 解释器模式
此模式很少使用。
21. 状态模式
解决的问题: 主要用来解决对象在多种状态转换时,需要对外输出不同行为的问题。状态和行为是一一对应的,状态之间可以相互转换。
定义: 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
不同状态下的行为是不同的,所以每个状态都封装了在自身状态下的一系列行为。
一个对象在不同的状态下的“同一方法签名的方法”的行为不同。
状态模式是把对象的行为二维化看待。横向是对象的不同状态,纵向是对象在不同状态下做同一件事情的结果不同。比如下图:
这个例子要是用状态模式,就是:
- 人作为一个具体类,人在3个年龄时的状态各作为一个具体状态类。
- 人这个具体类依赖3个状态类的抽象类;具体状态类肯定会依赖人具体类。
- 各个状态类里都有3个行为:去应聘、去民政局领证、去跑马拉松。
假设人这个具体类的对象在实例化时把状态初始化为10岁时,但是把人的年龄初始化为50岁。所以当人对象执行去应聘这个操作时,调用“10岁时”这个具体状态类的对象的去应聘这个行为,但在执行此行为时会根据人的年龄进行状态转换,所以会切换到“50岁时”这个具体状态类的对象,用“50岁时”这个状态执行去应聘这个行为操作。
状态模式优点:(参考:https://www.kancloud.cn/cyyspring/more/1420175)
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
22. 策略模式
使用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的'行为',那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
其实在代码中经常用到策略模式,比如,一个方法的参数是要求传递以委托实例,你把委托实例作为参数传递给这个方法,就是把一个遵守这个方法签名的方法的引用传递给了这个方法。这是方法级别的策略模式的实现。
策略模式,一般指的是类级别的策略模式的实现。如下图,一目了然。除此之外,你还可以假设有一种超级鸭,这种鸭子的叫声可以配置为三种中的任意一种,飞行的行为也可以配置为三种中的任意一种。这就体现了把不变的行为放到具体类里,把会发生变化的行为通过组合的方式组合到具体类,供具体类使用。
策略模式的注意事项和细节
- 策略模式的关键是: 分析项目中变化的部分与不变的部分
- 策略模式的核心是: 多用组合/聚合,少用继承;用行为类组合,而不是行为的继承,会更有弹性
- 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不同修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if..else)
- 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的算法类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是: 每添一个策略就要增加一个类,当策略过多时会导致类数目庞大。
23. 职责链模式
定义: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个关系连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
使用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求。
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可。
无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
每个处理者都实现统一的接口。这个接口必须的两个方法:设置接任者、处理请求。
优点
- 降低耦合度。它将请求的发送者和接收者解耦。
- 简化了对象。使得对象不需要知道链的结构。
- 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
- 增加新的请求处理类很方便。
缺点
- 不能保证请求一定被接收。
- 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
- 可能不容易观察运行时的特征,有碍于除错。