设计模式学习笔记
文章目录
0.设计模式简介
1.设计模式要活学活用,不要生搬硬套。
想要游刃有余地使用设计模式,需要打下牢固的程序设计语言基础、夯实自己的编程思想、积累大量的实践经验、提高开发能力。目的都是让程序低耦合,高复用,高内聚,易扩展,易维护。
2.分析成功的模式应用项目
对现有的应用实例进行分析是一个很好的学习途径,应当注意学习已有的项目,而不仅是学习设计模式如何实现,更重要的是注意在什么场合使用设计模式。
3.在编程中领悟模式
软件开发是一项实践工作,最直接的方法就是编程。没有从来不下棋却熟悉定式的围棋高手,也没有不会编程就能成为架构设计师的先例。掌握设计模式是水到渠成的事情,除了理论只是和实践积累,可能会“渐悟”或者“顿悟”。
4.避免设计过度
设计模式解决的是设计不足的问题,但同时也要避免设计过度。一定要牢记简洁原则,要知道设计模式是为了使设计简单,而不是更复杂。如果引入设计模式使得设计变得复杂,只能说我们把简单问题复杂化了,问题本身不需要设计模式。
设计模式中7 种设计原则,它们分别为开闭原则、里氏替换原则、依赖倒置原则、单一职责原则、接口隔离原则、迪米特法则和合成复用原则。
实际上,这些原则的目的只有一个:降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性。
记忆口诀:访问加限制,函数要节俭,依赖不允许,动态加接口,父类要抽象,扩展不更改。
在程序设计时,我们应该将程序功能最小化,每个类只干一件事。若有类似功能基础之上添加新功能,则要合理使用继承。对于多方法的调用,要会运用接口,同时合理设置接口功能与数量。最后类与类之间做到低耦合高内聚。
1.单例模式
2.工厂方法
3.抽象工厂
4.责任链模式
5.模板方法 ok
一般情况下,模板方法+策略模式 一起组合使用。
一.模板方法简介
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。
以下介绍的模板方法模式将解决以上类似的问题。
二.模式的定义和特点
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
1.它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
2.它在父类中提取了公共的部分代码,便于代码复用。
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下:
1.对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
2.父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
3.由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
三.模式的结构和实现
模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。
1.模式的结构
模板方法模式包含以下主要角色:
1.1 抽象类/抽象模板(Abstract Class)
抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
这些方法的定义如下:
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
基本方法:是整个算法中的一个步骤,包含以下几种类型:
。抽象方法:在抽象类中声明,由具体子类实现。
。具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
。钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
1.2 具体子类/具体实现(Concrete Class)
具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
模板方法模式的结构图如图 1 所示:
四.什么是模板方法模式
所谓模板方法模式,其实很简单,可以从模板的角度考虑,就是一个对模板的应用,就好比老师出试卷,每个人的试卷都是一样的,即都是从老师的原版试卷复印来的,这个原版试卷就是一个模板,可每个人写在试卷上的答案都是不一样的,这就是模板方法模式,是不是很好理解。它的主要用途在于将不变的行为从子类搬到超类,去除了子类中的重复代码。
模板方法模式(TemplateMethod),定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。UML结构图如下:
其中,AbstractClass实现类一个模板方法,定义了算法的骨架,具体子类将重定义PrimitiveOperation以实现一个算法的步骤;而ConcreteClass实现了PrimitiveOperation以完成算法中与特定子类相关的步骤。
1.抽象模板类
定义一个模板方法来组合PrimitiveOperation1()和PrimitiveOperation2()两个方法形成一个算法,然后让子类重定义这两个方法。
public abstract class AbstractClass {
public abstract void PrimitiveOperation1();
public abstract void PrimitiveOperation2();
//模板方法
public void TemplateMethod() {
PrimitiveOperation1();
PrimitiveOperation2();
}
}
2.具体模板类
这里定义两个具体模板类,ConcreteClassA及ConcreteClassB来进行测试,继承抽象模板类,实现具体方法。
public class ConcreteClassA extends Abstract Class {
@Override
public void PrimitiveOperation1() {
System.out.println("具体方法A方法1实现");
}
@Override
public void PrimitiveOperation2() {
System.out.println("具体方法A方法2实现");
}
}
3.Client客户端
通过调用模板方法来分别得到不同的结果。
public class Client {
public static void main(String[] args) {
Abstract Class abstractClass;
abstractClass = new ConcreteClassA();
abstractClass.TemplateMethod();
abstractClass = newConcreteClassB();
abstractClass.TemplateMethod();
}
}
运行结果如下:
五.模板方法的应用
1.何时使用
。有一些通用的方法时
2.方法
。将通用算法抽象出来
3.优点
。封装不变部分,扩展可变部分
。提取公共部分代码,便于维护
。行为由父类控制,子类实现
4.缺点
。每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
5.使用场景
。有多个子类共有的方法,且逻辑相同
。重要的、复杂的方法,可以考虑作为模板方法
。重构时,模板方法模式是一个经常使用到的模式,把相同的代码抽取到父类中,通过钩子函数约束其行为
6.应用实例
。做试卷,大家题目都是一样的,只是答案不同
。对于汽车,车从发动到停车的顺序是相同的,不同的是引擎声、鸣笛声等
。造房时,地基、走线、水管都一样,只有在建筑后期才有差异
7.注意事项
。为防恶意操作,一般模板方法都加上final关键字
六.demo
1.Game抽象类
package com.tangguanlin.templatemethod;
import lombok.extern.slf4j.Slf4j;
/**
* Game 抽象类
*/
@Slf4j
public abstract class Game {
//游戏名称
abstract String getName();
//模板方法 初始化
public final void init() {
log.info("游戏 {} 初始化完成",getName());
}
//游戏启动
abstract void start();
//游戏结束
abstract void over();
//模板
public final void play() {
init();
start();
over();
}
}
2.超级马里奥 具体实现类
package com.tangguanlin.templatemethod;
import lombok.extern.slf4j.Slf4j;
/**
* 超级马里奥 具体实现类
*/
@Slf4j
public class Mario extends Game{
private static final String NAME = "超级马里奥";
@Override
String getName() {
return NAME;
}
@Override
void start() {
log.info("1player 已就位");
log.info("2player 已就位");
log.info("游戏 {} 开始---------------------------", NAME);
}
@Override
void over() {
log.info("1player 被食人花吃了");
log.info("2player 掉坑");
log.info("游戏 {} 结束---------------------------", NAME);
}
}
3.英雄联盟 具体实现类
package com.tangguanlin.templatemethod;
import lombok.extern.slf4j.Slf4j;
/**
* 英雄联盟 具体实现类
*/
@Slf4j
public class LOL extends Game {
private static final String NAME = "英雄联盟";
@Override
String getName() {
return NAME;
}
@Override
void start() {
log.info("红方禁用英雄完毕");
log.info("蓝方禁用英雄完毕");
log.info("盖伦 已就位");
log.info("艾希 已就位");
log.info("瑞兹 已就位");
log.info("凯尔 已就位");
log.info("易 已就位");
log.info("游戏 {} 开始---------------------------", NAME);
}
@Override
void over() {
log.info("门牙被摧毁");
log.info("水晶被摧毁");
log.info("游戏 {} 结束---------------------------", NAME);
}
}
4.客户端Client
package com.tangguanlin.templatemethod;
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
Game game = new LOL();
game.play();
Game mario = new Mario();
mario.play();
}
}
七.项目实战
封装了审批流的抽象方法,由于新增审批流需求,之前各个模块又是调用不同的接口,操作不同的数据库,所以一般情况下很难统一处理加入的审批功能,用模板方法是个不错的选择
1.抽象模板类
先定义一个审批流的抽象类,
抽象方法包括:
-
更新发布状态(审批成功更新对应模块的数据为发布状态)
-
更新未发布状态(审批失败、审批流删除等情况下,更新对应模块的数据为发布状态)
-
批量更新未发布状态(审批流批量删除等情况下,批量更新对应模块的数据为发布状态)
-
获取菜单id(根据菜单id查询该模块是否具有审批流)
2.具体模板类
6.策略模式 ok
6.1 策略模式简介
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。
这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。
策略对象改变 context 对象的执行算法。
6.2 模式的定义和特点
**意图:**定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
**主要解决:**在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
**何时使用:**一个系统有许多许多类,而区分它们的只是他们直接的行为。
**如何解决:**将这些算法封装成一个一个的类,任意地替换。
**关键代码:**实现同一个接口。
应用实例:
1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
3、JAVA AWT 中的 LayoutManager。
优点:
1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。
缺点: 1、策略类会增多。
2、所有策略类都需要对外暴露。
使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,
那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
**注意事项:**如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
使用场景:
我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。
Context 是一个使用了某种策略的类。StrategyPatternDemo,
我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。
6.3 demo1
1.Payment接口
package com.tangguanlin.strategy1;
/**
* 支付 抽象类
**/
public interface Payment {
/**
* 支付
* @param money
*/
void pay(int money);
}
2.银行支付 实现类
package com.tangguanlin.strategy1.service;
import com.tangguanlin.strategy1.Payment;
/**
* 银行支付 具体类
**/
public class BankPayService implements Payment {
@Override
public void pay(int money) {
System.out.println("正在使用银行支付,金额:"+money);
}
}
3.支付宝支付 实现类
package com.tangguanlin.strategy1.service;
import com.tangguanlin.strategy1.Payment;
/**
* 支付宝支付 具体类
**/
public class AlipayService implements Payment {
@Override
public void pay(int money) {
System.out.println("正在使用支付宝支付,金额:"+money);
}
}
4.微信支付 实现类
package com.tangguanlin.strategy1.service;
import com.tangguanlin.strategy1.Payment;
/**
* 微信支付 具体类
**/
public class WechatService implements Payment {
@Override
public void pay(int money) {
System.out.println("正在使用微信支付,金额:"+money);
}
}
5.客户端调用
package com.tangguanlin.strategy1;
import com.tangguanlin.strategy1.service.AlipayService;
import com.tangguanlin.strategy1.service.BankPayService;
import com.tangguanlin.strategy1.service.WechatService;
import java.util.HashMap;
import java.util.Map;
/**
* 客户端调用
**/
public class Test {
private static Map<String, Payment> paymentMap = new HashMap<>();
static {
paymentMap.put("alipay", new AlipayService());
paymentMap.put("wechatPay", new WechatService());
paymentMap.put("bank", new BankPayService());
}
public static void main(String[] args) {
Payment payment = getPayWay("alipay");
payment.pay(10);
}
//获取支付方式
public static Payment getPayWay(String payType){
return paymentMap.get(payType);
}
}
6.4 demo2
1.运算接口类 Operation
package com.tangguanlin.strategy2;
/**
* 说明:运算接口类
* 作者:汤观林
* 日期:2021年10月31日 17时
*/
public interface Operation {
public int doOperation(int num1, int num2);
}
2.加法运算 实现类
package com.tangguanlin.strategy2;
/**
* 说明:加法运算
* 作者:汤观林
* 日期:2021年10月31日 17时
*/
public class OperationAdd implements Operation{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
3.减法运算实现类
package com.tangguanlin.strategy2;
/**
* 说明:减法运算
* 作者:汤观林
* 日期:2021年10月31日 17时
*/
public class OperationSubtract implements Operation{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
4.乘法运算实现类
package com.tangguanlin.strategy2;
/**
* 说明:乘法运算
* 作者:汤观林
* 日期:2021年10月31日 17时
*/
public class OperationMultiply implements Operation{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
5.OperationContext类
package com.tangguanlin.strategy2;
/**
* 说明: Context类
* 作者:汤观林
* 日期:2021年10月31日 18时
*/
public class OperationContext {
private Operation operation;
public OperationContext(Operation operation){
this.operation = operation;
}
public int executeOperation(int num1, int num2){
return operation.doOperation(num1, num2);
}
}
6.客户端调用
package com.tangguanlin.strategy2;
/**
* 说明: 客户端调用
* 作者:汤观林
* 日期:2021年10月31日 18时
*/
public class OperationClient{
public static void main(String[] args) {
//加法运算
OperationContext operationContext = new OperationContext(new OperationAdd());
System.out.println("10 + 5 = " + operationContext.executeOperation(10, 5));
//减法运算
operationContext = new OperationContext(new OperationSubtract());
System.out.println("10 - 5 = " + operationContext.executeOperation(10, 5));
//乘法运算
operationContext = new OperationContext(new OperationMultiply());
System.out.println("10 * 5 = " + operationContext.executeOperation(10, 5));
}
}
7.建造者模式 ok
Builder
7.1 盖房项目需求
1.需要建房子:这一过程为打桩,切墙,封顶
2.房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的。
3.请编写程序,完成需求。
7.2 传统方式思路分析
传统方式用的其实就是模板方法设计模式:
7.3 传统方式代码实现
传统方式用的其实就是模板方法设计模式
项目结构:
AbstructHouseBuilder.java 抽象建造者
package com.tangguanlin.design_pattern.builder.history;
/**
* 说明:抽象建造类 ---模板方法
* 作者:汤观林
* 日期:2022年01月21日 14时
*/
public abstract class AbstructHouseBuilder {
//打地基
public abstract void buildBasic();
//切墙
public abstract void buildWall();
//封顶
public abstract void roofed();
//建房子 模板方法
public void build(){
buildBasic();
buildWall();
roofed();
}
}
CommonHouseBuilder.java 普通房子建造
package com.tangguanlin.design_pattern.builder.history;
/**
* 说明:普通房子具体建造类
* 作者:汤观林
* 日期:2022年01月21日 14时
*/
public class CommonHouseBuilder extends AbstructHouseBuilder {
@Override
public void buildBasic() {
System.out.println("普通房子打地基");
}
@Override
public void buildWall() {
System.out.println("普通房子切墙");
}
@Override
public void roofed() {
System.out.println("普通房子封顶");
}
}
HouseBuilder.java 高楼建造
package com.tangguanlin.design_pattern.builder;
/**
* 说明:高楼建造者
* 作者:汤观林
* 日期:2022年01月21日 11时
*/
public abstract class HouseBuilder {
public House house = new House();
//将建造的流程写好,抽象的方法
public abstract void buildBasic(); //打地基
public abstract void buildWall(); //切墙
public abstract void buildRoofed(); //做房顶
//建造好房子,将房子返回
public House returnHouse(){
return house;
}
}
Client.java 客户端
package com.tangguanlin.design_pattern.builder.history;
/**
* 说明:客户端
* 作者:汤观林
* 日期:2022年01月21日 14时
*/
public class Client {
public static void main(String[] args) {
//普通房子
CommonHouseBuilder commonHouse = new CommonHouseBuilder();
//建普通房子
commonHouse.build();
System.out.println("-----------------");
//高楼
HighHouseBuilder highHouse = new HighHouseBuilder();
//建高楼
highHouse.build();
}
}
运行结果:
普通房子打地基
普通房子切墙
普通房子封顶
-----------------
高楼打地基
高楼打地基
高楼打地基
7.4 传统方式优缺点
1.优点是比较好理解,简单易操作
2.设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好,
也就是说,这种设计方案,把产品(即:房子)和创建的过程(即:建房子流程)封装在一起,耦合性增强了。
3.解决方案:将产品和产品建造过程解耦==>建造者模式
7.5 建造者模式基本介绍
Builder
1.建造者模式 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),
使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
2.建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建他们,
用户不需要知道它内部的具体构建细节。
7.6 建造者模式的四个角色
1.产品角色(Product): 一个具体的产品对象
2.抽象建造者(Builder): 创建一个Product对象的各个部件指定的接口/抽象类
3.具体建造者:实现接口,构建和装配各个部件
4.指挥者:构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。
它主要有两个作用:
一是隔离了客户与对象的生产过程
二是负责控制产品对象的生产过程
7.7 建造者模式原理类图
Director指挥者:这里是缓冲层
7.8 建造者模式解决盖房需求
思路分析(类图):
7.9 建造者模式代码实现
代码结构:
House.java 产品 house -->Product
package com.tangguanlin.design_pattern.builder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 说明:产品 house -->Product
* 作者:汤观林
* 日期:2022年01月21日 11时
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class House {
private String basic; //地基
private String wall; //墙
private String roofed; //屋顶
}
AbstructHouseBuilder.java 抽象建造者
package com.tangguanlin.design_pattern.builder;
/**
* 说明:抽象建造者
* 作者:汤观林
* 日期:2022年01月21日 11时
*/
public abstract class AbstructHouseBuilder {
public House house = new House();
//将建造的流程写好,抽象的方法
public abstract void buildBasic(); //打地基
public abstract void buildWall(); //切墙
public abstract void buildRoofed(); //做房顶
//建造好房子,将房子返回
public House returnHouse(){
return house;
}
}
CommonHouseBuilder.java 普通房子 具体 建造者
package com.tangguanlin.design_pattern.builder;
/**
* 说明: 普通房子 具体 建造者
* 作者:汤观林
* 日期:2022年01月21日 11时
*/
public class CommonHouseBuilder extends AbstructHouseBuilder {
@Override
public void buildBasic() {
System.out.println("普通房子打地基5米");
}
@Override
public void buildWall() {
System.out.println("普通房子切墙10cm");
}
@Override
public void buildRoofed() {
System.out.println("普通房子切墙屋顶");
}
}
HighHouseBuilder.java 高楼 具体 建造者
package com.tangguanlin.design_pattern.builder;
/**
* 说明:高楼 具体 建造者
* 作者:汤观林
* 日期:2022年01月21日 11时
*/
public class HighHouseBuilder extends AbstructHouseBuilder {
@Override
public void buildBasic() {
System.out.println("高楼打地基100米");
}
@Override
public void buildWall() {
System.out.println("高楼切墙20cm");
}
@Override
public void buildRoofed() {
System.out.println("高楼的透明屋顶");
}
}
HouseDirector.java 指挥者 这里去指定制作流程,返回产品 核心类
package com.tangguanlin.design_pattern.builder;
/**
* 说明:指挥者 这里去指定制作流程,返回产品 核心类
* 作者:汤观林
* 日期:2022年01月21日 11时
*/
public class HouseDirector {
AbstructHouseBuilder houseBuilder = null;
//构造器传入 houseBuilder
public HouseDirector(AbstructHouseBuilder houseBuilder){
this.houseBuilder = houseBuilder;
}
//通过set方法传入 houseBuilder
public void setHouseBuilder(AbstructHouseBuilder houseBuilder){
this.houseBuilder = houseBuilder;
}
//如何处理建造房子的流程,交给指挥者,这里指定产品的制作流程
public House BuilderHouse(){
houseBuilder.buildBasic(); //先打地基
houseBuilder.buildWall(); //再切墙
houseBuilder.buildRoofed(); //最后封顶
return houseBuilder.returnHouse();
}
}
Client.java 建造者模式 客户端
package com.tangguanlin.design_pattern.builder;
/**
* 说明:建造者模式 客户端
* 作者:汤观林
* 日期:2022年01月21日 11时
*/
public class Client {
public static void main(String[] args) {
//盖普通房子
CommonHouseBuilder commonHouseBuilder = new CommonHouseBuilder();
//生成 创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonHouseBuilder);
//完成盖房子,返回产品
House commonHouse = houseDirector.BuilderHouse();
System.out.println("-----------------------------------------------");
//盖高楼
HighHouseBuilder highHouseBuilder = new HighHouseBuilder();
houseDirector.setHouseBuilder(highHouseBuilder);
House highHouse = houseDirector.BuilderHouse();
}
}
运行结果:
普通房子打地基5米
普通房子切墙10cm
普通房子切墙屋顶
-----------------------------------------------
高楼打地基100米
高楼切墙20cm
高楼的透明屋顶
建造者模式的注意事项和细节:
1.客户端(使用程序)不必知道产品内部组成的细节,将产品本身和产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
2.每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
3.可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4.增加新的具体建造者无须修改原有类库的代码,指挥者针对抽象建造者编程,系统扩展方便,符合“开闭原则”。
5.建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
6.如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化。
导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式。
7.抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:
具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。
而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新的产品。
8.装饰器模式
什么是装饰者模式
装饰者模式是一种为函数或类增添特性的技术,它可以让我们在不修改原来对象的基础上,为其增添新的能力和行为。它本质上也是一个函数(在javascipt中,类也只是函数的语法糖)。
我们什么时候可以弄到它呢
我们来假设一个场景,一个自行车商店有几种型号的自行车,现在商店允许用户为每一种自行车提供一些额外的配件,比如前灯、尾灯、铃铛等。每选择一种或几种配件都会影响自行车的售价。
如果按照比较传统的创建子类的方式,就等于我们目前有一个自行车基类,而我们要为每一种可能的选择创建一个新的类。可是由于用户可以选择一种或者几种任意的配件,这就导致最终可能会生产几十上百个子类,这明显是不科学的。然而,对这种情况,我们可以使用装饰者模式来解决这个问题。
自行车的基类如下:
class Bicycle {
// 其它方法
wash () {}
ride () {}
getPrice() {
return 200;
}
}
那么我们可以先创建一个装饰者模式基类
class BicycleDecotator {
constructor(bicycle) {
this.bicycle = bicycle;
}
wash () {
return this.bicycle.wash();
}
ride () {
return this.bicycle.ride();
}
getPrice() {
return this.bicycle.getPrice();
}
}
这个基类其实没有做什么事情,它只是接受一个Bicycle实例,实现其对应的方法,并且将调用其方法返回而已。
有了这个基类之后,我们就可以根据我们的需求对原来的Bicycle类为所欲为了。
比如我可以创建一个添加了前灯的装饰器以及添加了尾灯的装饰器:
class HeadLightDecorator extends BicycleDecorator {
constructor(bicycle) {
super(bicycle);
}
getPrice() {
return this.bicycle.getPrice() + 20;
}
}
class TailLightDecorator extends BicycleDecorator {
constructor(bicycle) {
super(bicycle);
}
getPrice() {
return this.bicycle.getPrice() + 20;
}
}
那么,接下来我们就可以来对其自由组合了:
let bicycle = new Bicycle();
console.log(bicycle.getPrice()); // 200
bicycle = new HeadLightDecorator(bicycle); // 添加了前灯的自行车
console.log(bicycle.getPrice()); // 220
bicycle = new TailLightDecorator(bicycle); // 添加了前灯和尾灯的自行车
console.log(bicycle.getPrice()); // 240
这样写的好处是什么呢?假设说我们有10个配件,那么我们只需要写10个配件装饰器,
然后就可以任意搭配成不同配件的自行车并计算价格。而如果是按照子类的实现方式的话,
10个配件可能就需要有几百个甚至上千个子类了。
从例子中我们可以看出装饰者模式的适用场合:
-
如果你需要为类增添特性或职责,可是从类派生子类的解决方法并不太现实的情况下,就应该使用装饰者模式。
-
在例子中,我们并没有对原来的Bicycle基类进行修改,因此也不会对原有的代码产生副作用。
我们只是在原有的基础上增添了一些功能。
因此,如果想为对象增添特性又不想改变使用该对象的代码的话,则可以采用装饰者模式。
总结
使用装饰者模式可以让我们为原有的类和函数增添新的功能,并且不会修改原有的代码或者改变其调用方式,因此不会对原有的系统带来副作用。我们也不用担心原来系统会因为它而失灵或者不兼容。就我个人而言,我觉得这是一种特别好用的设计模式。
9.观察者模式 ok
9.1 观察者模式 ok
类结构:
9.1.1 被观察者接口
package com.tangguanlin.observer;
/**
* 说明:被观察者
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public interface IObserverable {
//添加观察者
void addObserver(IObserver observer);
//删除观察者
void removeObserver(IObserver iObserver);
//向观察者发送信息
void notityObservers(String message);
}
9.1.2 被观察者具体类
package com.tangguanlin.observer;
import com.tangguanlin.observer.IObserver;
import com.tangguanlin.observer.IObserverable;
import java.util.ArrayList;
import java.util.List;
/**
* 说明:some被观察者
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public class SomeObserverable implements IObserverable {
private List<IObserver> observers; //观察者集合
public SomeObserverable() {
//在被观察者对象 创建的同时,就将观察者集合创建
observers =new ArrayList<IObserver>();
}
//添加观察者
@Override
public void addObserver(IObserver observer) {
observers.add(observer);
}
//删除观察者
@Override
public void removeObserver(IObserver observer) {
observers.remove(observer);
}
//向观察者发送信息
@Override
public void notityObservers(String message) {
//通知每一个观察者
for(IObserver observer:observers){
observer.handleNotify(message);
}
}
}
9.1.3 观察者接口
package com.tangguanlin.observer;
/**
* 说明:观察者接口
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public interface IObserver {
//处理被观察者发送来的信息
void handleNotify(String message);
}
9.1.4 1号观察者具体类
package com.tangguanlin.observer;
import com.tangguanlin.observer.IObserver;
/**
* 说明: 1号观察者 具体类
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public class FirstObserver implements IObserver {
//处理被观察者发送来的信息
@Override
public void handleNotify(String message) {
System.out.println("1号观察者接收到["+message+"],正在处理消息");
}
}
9.1.5 2号观察者具体类
package com.tangguanlin.observer;
/**
* 说明: 2号观察者 具体类
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public class SecondObserver implements IObserver {
//处理被观察者发送来的信息
@Override
public void handleNotify(String message) {
System.out.println("2号观察者接收到["+message+"],正在处理消息");
}
}
9.1.6 测试类
package com.tangguanlin.observer;
/**
* 说明:测试类
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public class MyTest {
public static void main(String[] args) {
//创建多个观察者
IObserver first = new FirstObserver();
IObserver second = new SecondObserver();
//创建被观察者
IObserverable someObserverable = new SomeObserverable();
//被观察者 添加观察者
someObserverable.addObserver(first);
someObserverable.addObserver(second);
//被观察者 向所有观察者发送消息
someObserverable.notityObservers("全体注意,出发!");
someObserverable.removeObserver(first);
System.out.println("-------------------------------");
//被观察者 向所有观察者发送消息
someObserverable.notityObservers("全体注意,出发!");
}
}
运行结果:
1号观察者接收到[全体注意,出发!],正在处理消息
2号观察者接收到[全体注意,出发!],正在处理消息
-------------------------------
2号观察者接收到[全体注意,出发!],正在处理消息
9.2 监听器模式 ok
监听器设计模式,是观察者设计模式的一种实现,它并不是23中设计模式之一。
这里的监听器实际对应的就是观察者,而被监听对象,则是指被观察者。
当被监听对象的状态发生改变时,也需要通知监听器,监听器在收到通知后会做出相应改变。
与观察者设计模式不同的是,
被监听者的状态改变,被定义为了一个对象,称为事件。
被监听对象有了新的名字,称为事件源,
对监听器的通知,称为触发监听器。
其实质与观察者设计模式是相同的。
下面以被监听者所执行的增删改查curd操作进行监听为例,来演示监听器设计模式的用法。
类结构:
9.2.1 事件源接口–被观察者
package com.tangguanlin.listener;
/**
* 说明:事件源接口 --- 被观察者
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public interface IListenerable {
//为事件源注册监听器 ---添加观察者
void setListener(IListener listener);
//触发监听器 ----发送通知
void triggerListener(ICurdEvent event);
public void saveStudent();
public void removeStudent();
public void modifyStudent();
public void findStudent();
}
9.2.2 事件源具体类–被观察者
package com.tangguanlin.listener;
/**
* 说明: 事件源 --- 被观察者
* 作者:汤观林
* 日期:2022年02月13日 15时
*/
public class Listenerable implements IListenerable {
private IListener listener;
public Listenerable() {
}
//为事件源注册监听器 ---添加观察者
@Override
public void setListener(IListener listener) {
this.listener = listener;
}
//触发监听器 ----发送通知
@Override
public void triggerListener(ICurdEvent event) {
listener.handle(event);
}
//下面的方法是实际源类真正的业务逻辑,而监听器监听的就是这些业务方法的执行
public void saveStudent(){
System.out.println("向DB中插入了一条数据");
ICurdEvent curdEvent = new CurdEvent(this,"saveStudent");
this.triggerListener(curdEvent);
}
public void removeStudent(){
System.out.println("从DB中删除了一条数据");
ICurdEvent curdEvent = new CurdEvent(this,"removeStudent");
this.triggerListener(curdEvent);
}
public void modifyStudent(){
System.out.println("修改了db中的一条数据");
ICurdEvent curdEvent = new CurdEvent(this,"modifyStudent");
this.triggerListener(curdEvent);
}
public void findStudent(){
System.out.println("从db中的执行了查询");
ICurdEvent curdEvent = new CurdEvent(this,"findStudent");
this.triggerListener(curdEvent);
}
}
9.2.3 事件接口
package com.tangguanlin.listener;
/**
* 说明:定义增删改查事件
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
//通常,对于事件对象,我们一般是需要从事件对象中获取到事件源对象的
public interface ICurdEvent {
//声明事件类型
String CREATE_EVENT = "create event"; //增
String UPDATE_EVENT = "update event"; //改
String RETRIEVE_EVENT = "retrieve event"; //查
String DELETE_EVENT = "delete event"; //删
//获取事件源
IListenerable getEventSource();
//获取事件类型
String getEventType();
}
9.2.4 事件具体类
package com.tangguanlin.listener;
/**
* 说明:定义事件类
* 作者:汤观林
* 日期:2022年02月13日 15时
*/
public class CurdEvent implements ICurdEvent {
private IListenerable eventSource; //事件源 --- 被监听者
private String methodName; //事件源所执行的方法名称
public CurdEvent(IListenerable eventSource,String methodName) {
this.eventSource = eventSource;
this.methodName = methodName;
}
@Override
public IListenerable getEventSource() {
return eventSource;
}
//根据事件源所执行的不同的方法,返回不同的事件类型
@Override
public String getEventType() {
String eventType = null;
if(methodName.startsWith("save")){
eventType = CREATE_EVENT;
}else if(methodName.startsWith("remove")){
eventType = DELETE_EVENT;
}else if(methodName.startsWith("modify")){
eventType = UPDATE_EVENT;
}else if(methodName.startsWith("find")){
eventType = RETRIEVE_EVENT;
}else{
eventType = "have not this event type";
}
return eventType;
}
}
9.2.5 监听器接口–观察者接口
package com.tangguanlin.listener;
/**
* 说明:监听器接口 --- 观察者接口
* 作者:汤观林
* 日期:2022年02月13日 14时
*/
public interface IListener {
//处理事件 --- 处理被观察者发送的消息
void handle(ICurdEvent event);
}
9.2.6 监听器具体类–观察者
package com.tangguanlin.listener;
/**
* 说明:监听器具体类
* 作者:汤观林
* 日期:2022年02月13日 15时
*/
public class CurdListener implements IListener {
@Override
public void handle(ICurdEvent event) {
String eventType = event.getEventType();
if(ICurdEvent.CREATE_EVENT.equals(eventType)){ //若事件类型是“添加”
System.out.println("事件源执行了添加操作");
}else if(ICurdEvent.DELETE_EVENT.equals(eventType)){ //若事件类型是“删除”
System.out.println("事件源执行了删除操作");
}else if(ICurdEvent.UPDATE_EVENT.equals(eventType)){ //若事件类型“修改”
System.out.println("事件源执行了修改操作");
}else if(ICurdEvent.RETRIEVE_EVENT.equals(eventType)){ //若事件类型是“查询”
System.out.println("事件源执行了查询操作");
}
}
}
9.2.7 测试类
package com.tangguanlin.listener;
/**
* 说明: 测试类
* 作者:汤观林
* 日期:2022年02月13日 15时
*/
public class MyListenerTest {
public static void main(String[] args) {
//1.定义监听器 ---观察者
IListener curdListener = new CurdListener();
//2.定义事件源 --- 被观察者
IListenerable eventSource = new Listenerable();
//3.事件源注册监听器 --- 添加被观察者
eventSource.setListener(curdListener);
//4.事件源执行自己的方法
eventSource.saveStudent();
eventSource.removeStudent();
eventSource.modifyStudent();
eventSource.findStudent();
}
}
运行结果:
向DB中插入了一条数据
事件源执行了添加操作
从DB中删除了一条数据
事件源执行了删除操作
修改了db中的一条数据
事件源执行了修改操作
从db中的执行了查询
事件源执行了查询操作