设计模式
目录
一.模板方法模式 二.策略模式 三.命令模式 四.职责链模式 五.状态模式 六.观察者模式 七.中介者模式 八.迭代器模式 九.访问者模式 十.备忘录模式 十一.解释器模式 注:学习视频:黑马程序员Java设计模式
行为型模式
行为模式用于描述运行中复杂的控制过程,即描述。
行为模式分为和,前者采用在对象和类之间分配行为,后者使用的方式分配行为,后者采用组合与聚合的方式,所以的满足“合成服用原则”,耦合度低。
- 模板方法模式
- 策略模式
- 命令模式
- 职责链模式
- 状态模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式
以上 11 除了行为模式和是,其他的。
一、模板方法模式
1.1 概述
在面向对象的设计过程中,设置了系统的关键步骤,并且已经知道了这些步骤的执行顺序,但有些步骤的具体位置是固定的。
例如,当我们去医院看医生时,我们必须排队、登记、看医生、吃药等。排队和注册是每个人都做同样的行为,但看医生和吃药是根据每个人的不同去不同的地方看医生,吃不同的药。这样的问题可以通过模板方法模式设计
在操作中定义算法股价,并将算法的某些步骤延迟到子类,使子类在不改变算法结构的情况下重写算法的具体步骤。
1.2 结构
-
- 有三种基本方法
抽象方法由抽象类声明,通常需要子类来实现其特定功能。
抽象声明并实现具体方法是模板方法中每个人都需要执行的基本方法。
它已经在抽象类中实现,包括两种空方法:判断逻辑方法和需要子类重写。
-
它们是顶级逻辑组成步骤,可以实现抽象类中的抽象方法和钩子方法。其实就是实现一些特定的功能。
1.3 案例
烹饪步骤固定,分为倒油、热油、蔬菜、调味料等步骤,采用模板法实现。
/** * 抽象类 */ public abstract class AbstractClass {
// 定义烹饪过程算法 public void cookProcess(){
// 倒油 pourOil(); // 热油 heatOil(); // 倒蔬菜 pourVegetable(); // 倒调料 pourSource(); } // 倒油 public void pourOil(){
System.out.println("倒油"); } // 热油
public void heatOil(){
System.out.println("热油");
}
// 倒蔬菜(抽象)
public abstract void pourVegetable();
// 倒调料(抽象)
public abstract void pourSource();
}
/** * 炒卷心菜(子类) */
public class Cook_Cabbage extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("倒卷心菜");
}
@Override
public void pourSource() {
System.out.println("倒五香粉");
}
}
/** * 炒胡萝卜(子类) */
public class Cook_Carrot extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("倒胡萝卜");
}
@Override
public void pourSource() {
System.out.println("倒辣椒");
}
}
// 测试类
public class Client {
public static void main(String[] args) {
Cook_Cabbage cook_cabbage = new Cook_Cabbage();
// 调用父类的炒菜
cook_cabbage.cookProcess();
}
}
// 测试结果
倒油
热油
倒卷心菜
倒五香粉
为了防止恶意破坏,模板方法一般都加final关键字
1.4 优缺点
- 将相同的代码抽取倒父类中,不同的代码在子类编写
- 通过一个父类调用其子类实现的方法,通过子类实现方法的不同,实现了反向控制,符合“开闭原则”
- 对每个不同的实现都需要定义一个子类,会造成类数量增大
- 父类的抽象方法由子类来实现,使代码的阅读难度增大
1.5 使用场景
- 算法步骤固定,其中的某些步骤是一样的,某些步骤是不一样的,就可以使用模板方法模式,将一样的步骤抽取到父类中,不同的步骤由子类来实现。
- 需要子类决定某些父类的步骤,可以实现反向控制。
3.6 JDK源码分析
public abstract class InputStream implements Closeable {
//抽象方法,要求子类必须重写
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}
从上面代码可以看到,无参的
read()
方法是抽象方法,要求子类必须实现。而read(byte b[])
方法调用了read(byte b[], int off, int len)
方法,所以在此处重点看的方法是带三个参数的方法。
在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。
二、策略模式
2.1 概述
先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。
作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。
该模式定义了一系列算法,并且每个算法封装起来,使他们可以互相替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对象算法的封装,把算法职责和算法的具体实现分离(其实就是将算法抽取到接口中,调用直接调用接口就可以,而算法的具体实现就交给具体实现类来处理),并委派给不同对象对这些算法进行管理。
2.2 结构
- 这是一个抽象角色,通常用接口或抽象类来实现,此角色给出了所有具体策略所需的接口。
- 实现了抽象策略定义的接口,提供具体的算法实现。
- 持有一个策略类的引用给客户端调用。
2.3 案例
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。
抽象策略类
/** * 抽象策略类 */
public interface Strategy {
void show();
}
具体策略类(实现抽象策略类)
// 策略A (具体策略类)
public class StrategyA implements Strategy{
@Override
public void show() {
System.out.println("买一送一");
}
}
// 策略B (具体策略类)
public class StrategyB implements Strategy{
@Override
public void show() {
System.out.println("满两百送洗衣液");
}
}
// 策略C (具体策略类)
public class StrategyC implements Strategy{
@Override
public void show() {
System.out.println("满1000打八折");
}
}
环境类(*管理抽象类)
// 售货员 环境类,管理具体策略类,供客户调用
public class SaleMan {
// 聚合所有策略
private Strategy strategy;
public SaleMan(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
// 提供策略调用方法
public void useStrategy(){
this.strategy.show();
}
}
测试类
// 测试类
public class Client {
public static void main(String[] args) {
// 通过环境类调用策略
SaleMan saleMan = new SaleMan(new StrategyA());
// 使用策略
saleMan.useStrategy();
System.out.println("===================");
// 通过环境类调用策略
saleMan.setStrategy(new StrategyB());
// 使用策略
saleMan.useStrategy();
}
}
// 测试结果
买一送一
===================
满两百送洗衣液
策略可能会被创建多次,采用享元模式改进
// 策略工厂类
public class StrategyFactory {
// 创建享元对象集合
private Map<String ,Strategy> strategyMap = new HashMap<>();
// 初始化享元对象集合
private StrategyFactory() {
strategyMap.put("A",new StrategyA());
strategyMap.put("B",new StrategyB());
strategyMap.put("C",new StrategyC());
}
// 提供添加
public void add(String strategyName,Strategy strategy){
strategyMap.put(strategyName,strategy);
}
// 移除
public void remove(String strategyName){
strategyMap.remove(strategyName);
}
// 提供获取策略的方法
public Strategy getStrategy(String strategyName){
return strategyMap.get(strategyName);
}
// 将工厂设置成单例模式
public static StrategyFactory getInstance(){
return StrategyFactoryHolder.strategyFactory;
}
public static class StrategyFactoryHolder{
// 静态内部类可以调用外部的私有方法
private static final StrategyFactory strategyFactory = new StrategyFactory();
}
}
// 测试类
public class Client {
public static void main(String[] args) {
StrategyFactory instance = StrategyFactory.getInstance();
// 通过环境类调用策略
SaleMan saleMan = new SaleMan(instance.getStrategy("A"));
// 使用策略
saleMan.useStrategy();
System.out.println("===================");
// 通过环境类调用策略
saleMan.setStrategy(instance.getStrategy("B"));
// 使用策略
saleMan.useStrategy();
}
}
//测试结果和上面一致
买一送一
===================
满两百送洗衣液
2.4 优缺点
- 策略类实现了同一个接口,所以他们之间可以互相替换。
- 如果需要增加策略,只需要实现抽象策略类接口,原来代码无需改变,符合”开闭原则“。
- ,充分体现面向对象设计思想。
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量(上面已经实现)。
2.5 使用场景
- 一个系统在几个算法中选择一个时,可以将每个算法封装成策略,使用策略模式
- 一个类中定义了多种行为,且每个行为是以条件判断的形式来选择执行的,这个时候就可以将每个条件分支封装到策略中,用策略类来代替条件分支。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
2.6 JDK 源码分析
Comparator
中的策略模式。在Arrays类中有一个sort()
方法,如下:
public class Arrays{
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
}
Arrays就是一个环境角色类,这个sort方法传入一个新策略让Arrrays根据新策略进行排序,这里抽象策略类就是
Comparator
,而我们定义的匿名内部类就是具体策略类。
三、命令模式
3.1 概述
日常生活中,我们出去吃饭都会遇到下面的场景。
讲一个请求封装成一个对象,是发出请求的责任和执行请求的责任分离,这样两者之间就通过命令对象进行沟通,这样方便命令对象进行存储、传递、调用、增加与管理。
3.2 结构
- 定义命令的接口,声明命令的执行方法。
- 实现命令接口,真正执行命令的对象,通常会持有接受者,并调用接受者的方法来完成命令。
- 接收者,真正执行命令的对象,任何类都可能称为接受者,只要能够实现命令要求实现的相应功能。
- 调用命令的对象,通常持有命令角色,并且执行命令完成任务,相当于使用命令对象的入口。
3.3 案例
实现上面图中的命令
服务员:调用者角色,发出命令
资深大厨:接受者,真正执行命令的对象
订单: 命令中包含订单
抽象命令类和具体命令类
/** * 抽象命令角色 */
public interface Command {
// 定义调用命令的方法
void exec();
}
/** * 具体命令类 */
public class ConCreateCommand implements Command{
// 持有接受者
private SeniorChef chef;
// 聚合订单
private Order order;
public ConCreateCommand(SeniorChef chef, Order order) {
this.chef = chef;
this.order = order;
}
// 让接受者来执行命令
@Override
public void exec() {
Set<String> foodNames = order.getMap().keySet();
System.out.println(order.getTableNum() + "号桌订单");
for (String foodName : foodNames) {
Integer foodNum = order.getMap().get(foodName);
chef.cook(foodName,foodNum);
}
}
}
调用者和接收者
/** * 服务员 调用者 */
public class Waitor {
// 持有多个命令对象
private List<Command> commandList =new ArrayList<>();
// 添加命令
public void addCommand(Command command){
commandList.add(command);
}
// 执行多个命令
public void orderUp(){
System.out.println("有人下单了..:");
for (Command command : commandList) {
System.out.println("====================");
command.exec();
}
}
}
/** * 资深厨师类 */
public class SeniorChef {
public void cook(String foodName,Integer foodNum){
System.out.println(foodNum + "份" + foodName);
}
}
订单类和测试类
/** * 订单信息 */
public class Order {
// 桌号
private Integer tableNum;
// 菜单
private Map<String, Integer> map;
public Order(Integer tableNum, Map<String,Integer> map) {
this.tableNum = tableNum;
this.map = map;
}
public Integer getTableNum