https://www.bilibili.com/video/av57936239/?p=74 //尚硅谷图解Java设计模式
第一章
1.3 代码规范纠正
14 面向对象编程
初级程序,一般碰到问题就直觉的用计算机能够理解的逻辑来描述和表达待解决的问题和具体的求解过程,本身没错,但是这样的思维却是的我们程序只为了满足当前的需求,程序不容易维护,不容易扩展,更加不容易复用。
1.5 活字印刷,面向对象
第一: 要改,只需更改要改的字,此为可维护。
第二: 每个字可以在后来的印刷中重复使用,此为可复用。
第三:若要新加字,只需要另刻字加入,此为可扩展。
第四: 把活字的移动就可满足排序需求,此为灵活性好。
1.6 面向对象的好处
通过封装、继承、多态降低程序的耦合度。
1.7 复制 VS 复用
if,需要另外写一个计算器,代码怎么复用?
复制过去吗?NO,编程有一个原则,尽可能地避免重复。
so,计算器功能哪些是和计算有关,哪些是和控制台显示有关的。
1.8 业务的封装
不要复制,要封装
1.9 紧耦合 vs 松耦合
if,现在需要假如一个新的运算功能 开平方,怎么弄?
初级程序,会直接加入到对应的swich中,假入加入一个新的功能,使得原有功能代码发生改变,这是非常危险的,风险太大。
so、需要把每个运算分离出一个类,通过重写虚方法,来分别实现每种运算方式。
1.10 简单工厂模式
一般用来解决对象的创建问题
if,怎么让计算器知道我希望进行哪种运算呢? 即 ,如何去实例化指定对象的、这是实例化谁的问题。
so, 简单工厂模式,来实例化对象。通过多态,返回实现父类的子类的方式
1.11 UML 类图
第二章 商场促销 ------ 策略模式
2.1 使用简单工厂实现
打折和满就送营销是一样的。
因为打折和满几送几 营销是一样的,只需要有个初始化参数。简单工厂模式,重构,每种折扣。
现金收费抽象类超类: CashSuper.cs
正常收费子类
打折收费子类 :CashRebate.cs 【在初始化构造时就乘以折扣率】
返利收费子类: CashReturn.cs 【在初始化时必须要输入返利值】
收费对象生成工厂: CashFactor.cs
最后在现金收费工厂中,来判断和生成出是哪种返利形式。在户用操作类可以 通过多态可以得到收取费用得结果。
if, 目前现金收费工厂,只是固定的 switch : <1. 正常收费 <2.满300返300 <3.打8折 三种模式来选择。而商场是一个随时可能改变促销手段,如满100元 积分10点,用来以后领取礼品。 那么还用简单工厂,则会每次扩展和维护收费方式都要改变这个工厂,很麻烦的。
so, 策略模式
2.4 策略模式
策略模式(strategy):
2.3节中用简单工厂来生成算法对象,这没有错,但是算法本身只是一种策略,最重要的是这些算法可能互相替换(打折换成满就返),这就是变化点,而封装变化点是面向对象的重要思维。
抽象策略结构如下:
2.5 策略模式的实现
在客户端,根据用户选择的打折方式,在客户端去实例化用户指定的策略对象,调用该策略的打折算法。
2.6 策略和简单工厂的结合
if, 2.5节中,这种方式又得在客户端中去switch判断用哪一个策略的算法。所以把switch选择策略要放在CashContext.cs中来判断,策略和简单工厂的结合。
swich的判断方式:缺点是每增加一种算法,都需要在switch中增加一下,非常不爽,用反射。
2.7 策略模式解析
理解:
是一种定义了一系列算法得方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类域使用算法类之间的耦合(DPE)。策略模式封装了变化(每种算法的切换)
优点:
Strategy类层次为Context定义了一系列的可供重用的算法和行为;也简化了每个算法的单元测试。
应用:
在需要不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种规则切换变化的可能性。(商场不同时间应用不同的打折策略)
第3章 拍摄UFO ---- 单一职责原则
3.4 单一职责原则
单一职责:就一个类而言,应该仅有一个一起它变化的原因(ASD). UI时容易变化的,而游戏逻辑是不容易变化的。
案例: 在winfom上做一个俄罗斯方块
二维数组: x下标模拟左右移动 y下标模拟上下移动,值:保存状态,判断是否此处存在砖块。消层数组x从0->9,都等于1,则消层。
第4章 开放 ---- 封闭原则
4.2 多扩展、少修改;
当开始变化发生时,一定要创建抽象来隔离以后发生的同类变化(比如在开始时的加法程序,在增加需求(新来的变化)时,应该及时做出抽象、重构),拒绝不成熟的抽象。
第5章 开放 ---- 会修电脑不会修收音机?----依赖倒转原则
5.3 依赖倒转原则
.抽象不应该依赖细节,细节不应该依赖抽象。说白了就是要对接口编程,不要对实现编程。如:硬盘是针对接口设计的,如果对实现编程,那么换硬盘就得换主板。
A: 如果PC里,cpu、内存等都需要依赖主板,主板一坏,都完蛋。so,cpu等不应该依赖主板。反之也是一样,所以不管高层模块、还是底层模块都应该依赖于抽象,即接口或者抽象类。
5.4 里氏替换原则
子类可以代替父类使用,但是父类不能代替子类使用。才使得开放-封闭成为可能。依赖倒转也是如此。
比如: 企鹅是鸟、但是不能飞。程序中,子类拥有父类所有非private的属性,鸟会飞,企鹅不会飞,所以企鹅不能继承自鸟。
只有当子类可以替换父类,且替换后软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
第6章 穿什么有这么重要吗? ------装饰模式
6.4 装饰模式
概述: 装饰模式(Decorator),就是可以动态的给对象增加新功能(这些新增的功能只满足特殊的情况需要,用时才用到),且把新增的代码隔离出来(装扮者),降低被装饰类的复杂度,装饰类包装它所要装饰的对象,它要求装饰者对象和被装饰者对象有着相同的抽象父类或者接口。把类中的装饰功能搬离出去,简化原有类。
ConcreteComponent.cs 定义一个具体的对象,可以给Component.cs添加一些职责的功能。
Component.cs 装饰者和被装饰者的父类。
理解思路:
例如:给朋友送礼物,需要选择合适的包装材料包装一下,即礼物是被装饰者,包装材料是装饰者);
把被装饰者丢给装饰者来装饰,即把礼物给材料类,让材料来装饰,体现在程序上是ConcreteComponent传给具体的装饰者来装饰,这里是反向思维(不是把材料给礼物来装饰,而是礼物给材料)。当然如果礼物有很多种类,还可以再抽象出一个礼物类ConcreteComponent 2号。
如果礼物很多,则还可以在礼物与Component.cs之间定义一个礼物缓冲层,记录礼物的共有特点,再来继承抽象类礼物类(就是下图的Drink).
注意: 在客户端开始执行程序时。,因为还没有点完咖啡,所以要用父类Drink[超类]来接收。
装饰模式: 利用了SetComonent来对对象进行包装,这样每个装饰对象的实现就和如何使用这个对象分离开了,每个对象只关心自己的功能,不需要关心如何被添加到对象链当中。
6.6 装饰模式总结
把类中的装饰功能搬离出去,简化原有类。
第7章 为别人做嫁衣 ---- 代理模式
7.5 代理模式
代理为其他对象提供一种代理以控制对这个对象的访问。 其实就是真实对象的代表。可以扩展目标对象的功能(这里扩展的是1号女朋友、2号女朋友),把代理对象(各个女孩)的一些共同的方法写在代理中,把各自的写在各自的代理对象中(指各个女孩),或者一些类不让用,只能通过代理来调用。
缺点:如果接口增加方法,目标对象和代理都要实现接口,维护起来比较麻烦。
1. 静态代理模式
例如:通过代理给学校女生送礼物;追求者:实际买了礼物。
目标对象: 被代理者(真实的追求者)
代理:由代理送给被追求者,此处把代理对象女孩聚合到代理对象中。(调用追求者来送礼物)
2. 动态代理模式:
动态的创建代理,来代理目标对象。
第8章 雷锋依然在人间 ----- 工程方法模式
1 . 简单工厂模式UML图:
2. 工厂模式
8.4 简单工厂 vs 工厂方法模式
区别: 简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端提供的选择添加动态的实例化相关的类,即产出相关的产品。对于客户但来说,取除了与具体产品的依赖。(对于客户端来说传入什么就生产什么)。
缺点:简单工厂在需要生成其他产品时(比如计算器需要增加一个开方功能时),就需要在工厂里增加判断(switch case)分支条件,修改原有类,这就等于对扩展和修改都开放了。简单工厂模式违背了开放闭合原则。而工厂方法模式把生成的方法下榻到对应的工厂子类,让各个工厂子类自己实现。
第9章 简历复印 ---- 原型模式
通过浅克隆,来创建当前对象的浅表副本(对于引用类型复制引用,而不复制引用的对象)。并且克隆后,还是可以修改克隆后的数据的。
浅克隆注意点: 如果复制的是引用类型,那么实际上市复制引用,那么修改值后,则修改了这个引用指向的对象。注意循环引用的问题。
一个对象可以产出与它自己相近的对象
第10章 考题抄错会做也白搭 ---- 模板方法模式
10.5 模板方法模式特点
把不变的行为搬移到超类里,去出子类中的重复代码来体现它的优势。复用一系列过程构成的步骤,从高层次看是不同的,但是每个步骤的实现可能会不同。
当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现,通过模板方法模式把行为搬移到单一的地方、这样就帮助子类摆脱重复的、不变的、行为的纠缠。
第11章无熟人难办事? ---- 迪米特法则
理解: 一个类尽量降低成员的访问权限,公开成员使用属性来体现。根本思想是强调类之间的松耦合,类之间的耦合越弱,越有利于复用,一个弱耦合的类被修改,不会对有关系的类造成波及。
第12章 牛市股票还会亏钱? ---- 外观模式
第13章 建造者模式
使用抽象类稳定必须有的功能。指挥者类(拥有需要建造的对象):控制建造过程
应用范围:
所以:
建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时 适用的模式。
第14章 观察者模式
例如: 上班看股票,老板来了,前台通知 ,让观察者好好工作
观察者:是这些看股票员工 主题/抽象通知者(Subject):前台,在状态改变时通知观察者
4.6 观察者模式特点
应用:
目前方式的缺点:都是依赖于抽象,即方法名 要相同,不然没法更新。所以抽象通知者由于不希望依赖抽象观察者的update方法。即所调用所有观察者身上的方法名是一样的。如果是向通知到不同的方法,则就不行。
委托事件:
14.9 事件委托说明:
相当于new一个委托实例,把一些方法委托给了委托实例Update了。关键是可以使得委托对象所搭载的方法并不需要属于同一个类。解决了本来在Boss类中增减的操作。
第15章 就不能不换DB吗? ----抽象工厂模式
先来了解一下工厂方法:“工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类” 比如有不同的数据库时。
对不同的表:用户表、部门表等的抽象。
上边这种实现:目前在修改不同的数据库访问时,还是需要修改为不同的数据库访问。
15.5 抽象工厂模式
应用: 如: 如果数据库中有很多表,而Access 和SqlServer是两大不同的分类,所以解决这种涉及多个产品系列的问题,有个专门的工厂模式叫做抽象通常模式。
抽象工厂类-->具体的工厂 ---->生成出具体的产品
抽象产品类(上述中的部门表、用户表)-->具体的产品
15.6 抽象工厂的优点和缺点
优点:
1. 在初始化时,很容易就能改变具体工厂配置,从而生产出不同的产品
2.具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端中。如下:客户端只认识两个接口(IUser/IDepartment),至于是哪个数据库来实现就不知道了。
缺点:
1.如果现在要增加一个新的表(Project表),那么改的
2. 如果调用数据库的地方有很多,那么在需要改动数据库时,改的的地方也就有很多
15.7 用简单工厂来改进抽象工厂
解决抽象工厂的缺点:
客户端没有出现任何一个SQL Server 、Access的字样,达到了解耦的目的。
客户端代码:
第16章 无尽加班何时休 ----- 状态模式
面向对象思维开发,不要各种if判断,面向过程开发。
16.4 方法过长是坏味道,使得方法责任过大
不同时间老板让做不同得职责。当改变在某个时间所做的事情。
16.5 状态模式
应用范围:
状态模式主要解决的是当控制一个对象状态转换得条件过于复杂时得情况。把状态的转换逻辑 转移到表示不同状态得一系列类当中,从而使得复杂得判断逻辑得到简化。
Handle() :该状态要做的相关的逻辑(执行该状态要做的事,不满足时,设置为下一个状态)。 具体的状态有具体的行为。
Context.CS : 维护一个具体的状态的实例,即ConCretState子类的实例,调用该状态的方法。
16.6 状态模式好处与用处
优点:
把与特定状态相关的行为局部化,并且将不同状态的行为分割开来。
这样把特定状态的行为都放入一个对象中,由于所有与状态相关的代码都存在于某一个ConCreteState中,所以需要扩展新的状态时可以很容易的增加新的状态和转换,消除庞大的if条件判断。
forever remeber : 永远记住,任何改动都是致命的。
适用场景:
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态来改变行为时,就可以考虑使用状态模式。
第17章 在NBA我需要翻译 --- 适配器模式
简单理解:就是不管电压是多少,都可以通过适配器转换为适合的电压为笔电充电。
系统的数据和行为都正确,但是接口不符合,我们应该考虑适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。这种模式主要用在希望复用一些现存的类,但是接口由于复用环境不一致的情况。
类适配器:
通过多重继承(c++支持多重继承)对一个接口和另一个接口进行匹配
对象适配器:
应用:
一般用在项目后期,维护,需要增加接口来适配甲方需求时,可以使用。
或者项目初期,考虑以后使用不同类型的第三方的插件或者被第三方调用,所以提供的接口
总结: 调用的还是原本的接口,只是原本的特殊化
适配器(翻译) 被适配者(姚明)
第18章 如果再回到从前 ----- 备忘录模式
比如:当玩游戏,攻击boos时,允许玩家如果感觉与Boss战斗的效果不理想,则可以让游戏恢复到决斗前。
不用设计模式时的写法:创建新实例来保存。会把整个游戏的细节暴漏给客户端了,这样使得客户端的职责太大了,或者修改角色 "生命力" 为 “经验值”时 ,备份的这部分就一定要修改。
18.3 备忘录模式
对Caretaker.cs: 只能看到备忘录的窄接口,将备忘录传递给其他对象。负责保存备忘录,不能对备忘录进行操作或检查。(get / set 备忘录)
Originnator.cs : 可以看到宽接口。允许访问返回到先前状态所需要的所有数据。
对比到游戏里: “游戏角色其实就是Originator”, 而初级程序员处理时一般会用同样的“游戏角色 ”实例备份”来做备忘录,有时可以考虑,但是使用clone的方式,则会对上层应用开放了Originator的全部(public)接口。
实际使用:
这样就把要保存的细节封装在了Memonto.cs中了,后边有更改也不会影响到客户端了。
应用场景:
<1. Memonto 适用于功能比较复杂的,而且需要维护合记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分,Originator可以根据保存的信息还远到前状态。
<2. 也可应用在命令模式中的撤销功能上,用来存储状态。
18.5 游戏角度备忘
缺点: 角色状态需要完整存储在备忘录对象中,如果状态数据很大很多,那么在资源消耗上,备忘录对象会非常耗费内存。
第19章 分公司 = 一部门 组合模式
简单来说:就是整体和部分可以被一致性对待的问题。树形结构功能任务
19.2 组合模式
结构图:
透明式 与 安全式 :子类不具备父类的功能,但是仍然实现了父类方法,保持了一致性,但是做了无用功,这是透明式。
19.4 合适使用组合模式:
应用:
比如需要自己写一个UI的层级时,把基本控件组合起来,组合成一个自定义控件。这种激素hi组合模式的体现。
unity中可以用程序来组装UI层级。
需要时体现部分与整体层级的结构时。树状任务模块程序。
19.5 公司的管理系统
19.6 组合模式的好处
20.4 .Net迭代器实现
IEnumerator 支持对非泛型集合的简单迭代接口
21. 单例模式
单例类: 可以继承,有保存状态
实用类:Mathf ,提供一些静态方法,是密封类,不能被继承。只是方法属性的集合。
21.5 多线程时的单例
多线程单例,同时静态类时,可能会造成创建多个对象。
Lock:
21.6 双重锁定
每次调用lock锁,只有在实例未被创建的时候加锁
造成的性能问题:
饿汉式单例:静态初始化,即类一开始加载时就初始化,提前占用系统资源。
第22章 手机软件何时统一 ----- 桥接模式
先来了解下合成/聚合模式
22.2 紧耦合的程序演化
传统的抽象继承的缺点:
当复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制复用性。应该优先使用设计模式的合成/聚合,而不是类继承。
22.3 合成聚合复用原则
大雁: 翅膀是合成大雁的一部分。强拥有关系。
雁群: A可以包B,但B不是A对象的一部分。一个雁群可以有多只大雁。
22.4 松耦合的程序:降低耦合度
合成聚合的优点:
<1. 优先使用对象的 合成/聚合 将有助于保持每个类被封装,并被集中在单个任务上,这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。得学会用对象得职责而不是结构来考虑问题。
<2. 合成聚合优于类继承:也更加符合我们的开放闭合原则,而继承是一种强耦合的程序结构。
例如: 手机的牌子 、手机软件。
如果使用继承,则如下结构图:使得类变得不可控制。
如果使用合成聚合后的结构图:此处是把手机软件聚合到手机品牌类中。
22.5 桥接模式
理解桥接模式:
如上边的 实现系统 可能有多角度分类(A\B\C...),每一种分类都有可能变化,那么就把这种角度分离出来让它们独立变化,减少它们之间的耦合。如: 上边例子中可能是品牌的变化,也可能是各种软件的变化。那么就可以把变化分离出来。
在只用继承造成大量的类增加,不能满足开闭原则时,考虑用桥接模式。
第23章 烤羊肉串引来的思考------命令模式
服务员(Invoker.cs) 记录当前命令 (ConcreteCommand.cs),服务员执行命令,即调用命令中的Execute()函数,让receiver去做某事。方法调用被存储在对象中。命令模式是一种回调的面向对象实现。
一个类输入命令---》接收命令---》执行命令(传入某个角色)
23.7 命令模式作用
第24章 加薪非要老总批评 ----- 责任链模式
24.3 责任链模式
发出请求的客户端并不知道这当中的哪一个对象最终处理这个请求,这样系统的更改可以在不影响客户端的情况下动态地重新组织和分配责任。
客户端的调用:
24.4 职责链的好处
<1. 当客户端提交一个请求时,请求是沿链传递直至有一个ConcreteHandle对象负责处理它。
<2. 接收者 和 发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。
<3. 由于是客户端定义了链的结构,我们可以随时增加或修改处理一个请求的结构。
<4. 通过设置的后继者,来转移到更搞层审批。
24.5 用加新代码重构:
第25章 世界需要和平 --- 中介者模式
25.2 中介者模式
中介者模式结构图:
25.4 中介者模式的优缺点
优点:可以集中控制。
缺点: 由于ConcreteMediator可能会因为ConcreteColleague变的越来越多,而难以维护。
应用场合: 如果一组对象已经定义良好(ConcreteColleague比较少),但是以复杂的方式进行通信时的场合。
第26章 项目多也别傻做 ----- 享元模式
26.2 享元模式
享元模式的应用:
<1. 引用的比较,如果值是相等的,则就改变引用的指向。这里就是共享了值。
<2. 还有在休闲游戏中:围棋有361个空位,如果用常规思维,则需要361个对象,但是实际它们的内部状态是颜色,而方位坐标是外部状态。
内部状态:指的是在享元对象的内部状态,并不是随环境变化而改变的共享部分。围棋的颜色,只有黑色、白色。
外部状态:而随着环境变而变得就是外部状态。
享元模式的缺点:
享元模式是的程序更加复杂。只有足够多的对象实例可以共享时,才考虑用享元模式.
第27章 其实你不懂老板的心 ----- 解释器模式
27.2 解释器模式
缺点:
需要为文法中的每一天规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。建议当文法非常复杂时,使得其他的技术如语法分析程序或编译器生成来处理。
27.4 音乐解释器
第28章 男人和女人 ---- 访问者模式
根据不同的状态,来考察各个对象的反应。例如:面对结婚时,男人/女人的状态 ; 面对失恋时, 男人和女人的状态 ;
本例的结构图:
本例中的:各元素:男人、女人。
这里主要区别男人和女人,因为性别的区分是稳定的,所以状态抽象类中增加男人反应、和女人反应,方法的个数是稳定的。
优点:
<1. 把数据结构和作用于结构上的操作之间的耦合脱开,使得操作集合可以相对自由地演化。
<2. 使得算法操作的增加变得容易。即增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集合到一个访问者对象上。
<3. 一般情况下是不需要用到访问者模式的,因为访问者模式要求数据结构是稳定的,这种情况很少。
第29章 模式总结