一、前言
最近两周工作比较忙,一直疏忽了写博客这件事。但是再忙也得坚持下去,虽然很难,但是自己定下的小目标含着泪也要把它做下去啊~~好了,废话不多说直接进入正题吧。
设计模式相信大家应该都有接触过,并且在不经意间也使用过一些设计模式,使用设计模式最重要的一点是让我们的代码看起来更清晰可读,并且更具可扩展性,还可以达到代码最大程度复用的目标。所以今天我就来介绍一下最近在项目中用到的两个设计模式,这两个设计模同样也是使用的比较多的两个模式:模板方法模式以及策略模式。
二、模板方法模式
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。模板方法模式需要开发抽象类和具体子类。抽象类给出一个算法的轮廓和骨架,具体类则负责给出这个算法的各个各个特定逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。
模板方法所代表的行为称为顶级行为,其逻辑称为顶级逻辑。模板方法模式的静态结构图如下所示:
这里涉及到两个角色:
抽象模板(Abstract Template)角色有如下责任:
■ 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
■ 定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体模板(Concrete Template)角色又如下责任:
■ 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
■ 每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
有句名言说的好:Talk is cheap. Show me the code…拿出代码才有说服力嘛!那么我们就以炒菜这件事为例来实现一下模板方法模式吧。首先我们来梳理一下,如果要做一个菜需要做些什么事项,以及这些事项的顺序。俗话说,巧妇难为无米之炊。。。那最先肯定需要原材料,所以就需要去买菜;买到菜之后就是考验动手能力的时候了,需要洗菜,切菜以及做菜了。通过我个人总结,这个世界上的所有菜都有个基本操作顺序:买菜-->洗菜-->准备菜-->做菜-->出锅,所有菜的做法都是大概就是这个抽象的流程。因此,我们可以构建一个抽象类来定义这些流程,代码如下:
1 package com.guigui.common.patterns.template; 2 3 /** 4 * 炒菜抽象类(定义炒菜的具体步骤) 5 */ 6 public abstract class AbstractCook { 7 8 protected String dishName = ""; 9 10 public void fryProcess() { 11 // 设置菜名 12 this.setDishName(); 13 // 步骤一: 买菜 14 this.buyDishes(); 15 // 步骤二: 洗菜 16 this.washDishes(); 17 // 步骤三: 准备菜 18 this.prepareDisher(); 19 // 步骤四: 做菜 20 this.fryDishes(); 21 // 步骤五: 出锅 22 this.finishDishes(); 23 } 24 25 // 设置菜名 26 protected abstract void setDishName(); 27 28 // 买菜通用步骤 29 protected void buyDishes() { 30 System.out.println("去菜市场买:" + dishName); 31 } 32 33 // 洗菜通用步骤 34 protected void washDishes() { 35 System.out.println("将" + dishName + "用水洗干净"); 36 } 37 38 // 准备菜(各类菜准备方式不一样,此处使用抽象方法) 39 protected abstract void prepareDisher(); 40 41 // 做菜(各类菜做法不一样,此处抽象) 42 protected abstract void fryDishes(); 43 44 // 出锅 45 protected void finishDishes() { 46 System.out.println("将" + dishName + "盛出并装盘"); 47 } 48 49 }
在这里我们规定了做菜的通用流程,像买菜、洗菜以及出锅这些步骤,所有菜的做法基本都是一样的,所以可以将其方法的实现定义在抽象类中,如有特别需要也可对相应步骤进行重写;而准备菜和做菜这两个步骤,不同类型的才的做法差异较大,所以需要定义为抽象方法,让具体的菜做法实现类去实现这两个步骤。接下来就是定义具体的菜的做法类,我这边给出了三个具有代表性的菜类的做法,分别是:炒青菜、蒸鸡蛋以及烧排骨,下面具体来看一下他们的实现:
1、 炒青菜
1 package com.guigui.common.patterns.template; 2 3 /** 4 * 炒青菜类 5 */ 6 public class CookGreens extends AbstractCook { 7 @Override 8 protected void setDishName() { 9 this.dishName = "青菜"; 10 } 11 12 @Override 13 protected void prepareDisher() { 14 System.out.println("开始摘" + dishName + "..."); 15 System.out.println("开始切" + dishName + "..."); 16 System.out.println("开始对" + dishName + "进行过水..."); 17 System.out.println("开始准备蒜瓣, 并将其切碎..."); 18 } 19 20 @Override 21 protected void fryDishes() { 22 System.out.println("倒油并烧开..."); 23 System.out.println("倒入蒜瓣爆香..."); 24 System.out.println("倒入" + dishName + "进行翻炒3分钟..."); 25 } 26 }
实现了准备菜和做菜这两个抽象方法,内容对应青菜的做法。
2、 蒸鸡蛋
1 package com.guigui.common.patterns.template; 2 3 /** 4 * 蒸鸡蛋类 5 */ 6 public class CookEggs extends AbstractCook { 7 @Override 8 protected void setDishName() { 9 this.dishName = "鸡蛋"; 10 } 11 12 @Override 13 protected void washDishes() { 14 // 鸡蛋不需要清洗,这里用一个空方法重写覆盖 15 } 16 17 @Override 18 protected void prepareDisher() { 19 System.out.println("打破" + dishName + "壳,并放入碗中..."); 20 System.out.println("捣碎" + dishName + "蛋黄和蛋清, 搅拌至均匀..."); 21 System.out.println("倒入一定量的清水以及油和盐..."); 22 } 23 24 @Override 25 protected void fryDishes() { 26 System.out.println("在蒸锅中放入一定量水..."); 27 System.out.println("放入隔层..."); 28 System.out.println("放入准备好的" + dishName + ", 盖上锅盖蒸10到15分钟..."); 29 } 30 }
实现蒸鸡蛋的准备方法与做法,另外鸡蛋不需要洗,所以把洗菜这个方法实现成了一个空的方法,表示这个菜是不需要清洗的。
3、 烧排骨
1 package com.guigui.common.patterns.template; 2 3 /** 4 * 烧排骨类 5 */ 6 public class CookRibs extends AbstractCook{ 7 @Override 8 protected void setDishName() { 9 this.dishName = "排骨"; 10 } 11 12 @Override 13 protected void prepareDisher() { 14 System.out.println("剁碎洗干净后的" + dishName + "..."); 15 System.out.println("准备一定量的葱姜蒜,并切碎..."); 16 System.out.println("将准备好的" + dishName + "在锅中进行焯水处理后盛出..."); 17 } 18 19 @Override 20 protected void fryDishes() { 21 System.out.println("在锅中倒入油并烧开..."); 22 System.out.println("倒入准备好的葱姜蒜爆香..."); 23 System.out.println("放入焯水后的排骨进行翻炒5到10分钟..."); 24 System.out.println("倒入一定量料酒,及其他调料,继续翻炒几分钟..."); 25 } 26 }
实现了排骨的具体做法。
以上便是模板方法模式的具体实现;下面继续介绍另外一个常用的设计模式:策略模式,策略模式经常和模板方法模式结合起来使用,模板方法规定主流程,具体的实现分别对应不同的策略。
三、策略模式
策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。下图为策略模式具体结构:
这个模式涉及到三个角色:
● 环境(Context)角色:持有一个Strategy的引用。
● 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
● 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
还是以上述做菜的例子为例说明,做菜的抽象流程类便是抽象策略角色,每一种菜的实现都可以当做是一个具体策略角色,然后我们还需要加上一个环境角色,来完善这个策略模式,下面代码示例即为环境角色类:
1 package com.guigui.common.patterns.strategy; 2 3 import com.guigui.common.patterns.template.AbstractCook; 4 5 public class Context { 6 // 持有具体策略对象 7 private AbstractCook cookDish; 8 // 构造方法,传入具体策略对象 9 public Context(AbstractCook cookDish) { 10 this.cookDish = cookDish; 11 } 12 // 执行策略方法 13 public void strategyProcess(){ 14 cookDish.fryProcess(); 15 } 16 }
这样便完成了策略模式的基本结构。下面写一个示例来演示一下模板方法模式和策略模式结合起来使用过程,以下为测试类代码:
1 package com.guigui.common.patterns; 2 3 import com.guigui.common.patterns.template.AbstractCook; 4 import com.guigui.common.patterns.template.CookEggs; 5 import com.guigui.common.patterns.template.CookGreens; 6 import com.guigui.common.patterns.template.CookRibs; 7 import com.guigui.common.patterns.strategy.Context; 8 9 import java.util.HashMap; 10 import java.util.Map; 11 12 public class StrategyMain { 13 private static Map<String, AbstractCook> strategyMap = new HashMap<>(); 14 static { 15 strategyMap.put("greens", new CookGreens()); 16 strategyMap.put("eggs", new CookEggs()); 17 strategyMap.put("ribs", new CookRibs()); 18 } 19 public static void main(String[] args) { 20 // 炒青菜 21 Context green_context = new Context(strategyMap.get("greens")); 22 green_context.strategyProcess(); 23 System.out.println("=========================================="); 24 25 // 蒸鸡蛋 26 Context egg_context = new Context(strategyMap.get("eggs")); 27 egg_context.strategyProcess(); 28 System.out.println("=========================================="); 29 30 // 烧排骨 31 Context rib_context = new Context(strategyMap.get("ribs")); 32 rib_context.strategyProcess(); 33 System.out.println("=========================================="); 34 } 35 }
这个示例通过使用策略模式来做了三个菜,并且这三个菜的做法是通过一个模板方法来限定基本流程,而不同的做菜类则是不同的做菜策略,只需要将具体的策略实例传入环境角色,便可以实现对应的做菜流程。
测试结果:
好了,今天要介绍的内容大概就是这么多,有什么不对的地方欢迎大家指正,非常感谢~~~