设计模式
文章目录
1.设计模式简介
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));
}
}