学习笔记-Java设计模式-行为型模式1
Java设计原则&&模式学习笔记
说明
近期扫地生决定整合一下年初学习的设计模式,一来用于复习巩固,二来也希望可以把自己的整合与有需要的同学共勉。
扫地生在学习的过程中主要参考的是公众号“一角钱技术”的相关推文,同时也参考了下列几篇文章。对这些作者扫地生表示衷心的感谢。
5、行为型模式
5.1 行为型模式1——模板方法模式(Template Method)
速记关键词:框架
简介
定义:定义一个操作的算法骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构就可以重定义该算法的某些特定步骤。
模板方法模式就是基于继承的代码复用技术。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。所以模板方法的模板其实就是一个普通的方法,只不过这个方法是将算法实现的步骤封装起来的。
模式实现
package top.saodisheng.designpattern.templatemethod.v1;
/**
* description:
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class TemplateMethodPattern {
public static void main(String[] args) {
AbstractClass tm = new ConcreteClass();
tm.templateMethod();
}
}
/**
* 1. 定义抽象类
*/
abstract class AbstractClass {
/**
* 模板方法
*/
public void templateMethod() {
specificMethod();
abstractMethod1();
abstractMethod2();
}
/**
* 具体方法,各个子类中都相同的代码逻辑
*/
public void specificMethod() {
System.out.println("抽象类中的具体方法被调用...");
}
/**
* 抽象方法1
*/
public abstract void abstractMethod1();
/**
* 抽象方法2
*/
public abstract void abstractMethod2();
}
/**
* 2. 定义具体子类
*/
class ConcreteClass extends AbstractClass {
@Override
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
@Override
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
}
解决的问题
- 提高代码复用性:将相同部分的代码逻辑放在抽象父类中就,而将不同的代码逻辑放入不同的子类中。
- 实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,符合开闭原则。
在面向对象程序设计过程中,常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
模式组成
组成(角色) | 作用 |
---|---|
抽象类(Abstract Class) | 负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法;基本方法:是整个算法中的一个步骤,包含了——抽象方法(在抽象类中声明,由具体子类实现)、具体方法(在抽象类中已经实现,在具体子类中可以使用继承或重写它)、钩子方法(在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种) |
具体子类(Concrete Class) | 实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。 |
实例说明
用模板方法模式实现出国留学手续设计程序。
分析:出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。
在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了,如下图:
实例实现
package top.saodisheng.designpattern.templatemethod.v2;
/**
* description:
*
* 模板方法模式案例
* 用模板方法模式实现出国留学手续设计程序
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class TemplateMethodPattern {
public static void main(String[] args) {
StudyAbroad tm=new StudyInAmerica();
tm.templateMethod();
}
}
/**
* 1. 定义抽象类,定义一个出国留学的抽象类StudyAbroad
*/
abstract class StudyAbroad {
/**
* 模板方法
*/
public void templateMethod() {
// 获取学校资料
lookingForSchool();
// 入学申请
applyForEnrol();
// 办理因私出国护照、出境卡和公证
applyForPassport();
// 申请签证
applyForVisa();
// 体检、订机票、准备行装
readyGoAbroad();
// 抵达
arriving();
}
public void applyForPassport() {
System.out.println("三.办理因私出国护照、出境卡和公证:");
System.out.println(" 1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。");
System.out.println(" 2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。");
}
public void applyForVisa() {
System.out.println("四.申请签证:");
System.out.println(" 1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;");
System.out.println(" 2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。");
}
public void readyGoAbroad() {
System.out.println("五.体检、订机票、准备行装:");
System.out.println(" 1)进行身体检查、免疫检查和接种传染病疫苗;");
System.out.println(" 2)确定机票时间、航班和转机地点。");
}
/**
* 获取学校资料
*/
public abstract void lookingForSchool();
/**
* 入学申请
*/
public abstract void applyForEnrol();
/**
* 抵达
*/
public abstract void arriving();
}
/**
* 2. 定义具体子类:美国留学
*/
class StudyInAmerica extends StudyAbroad {
@Override
public void lookingForSchool() {
System.out.println("一.索取学校以下资料:");
System.out.println(" 1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;");
System.out.println(" 2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;");
System.out.println(" 3)了解该学校的住宿、交通、医疗保险情况如何;");
System.out.println(" 4)该学校在中国是否有授权代理招生的留学中介公司?");
System.out.println(" 5)掌握留学签证情况;");
System.out.println(" 6)该国政府是否允许留学生合法打工?");
System.out.println(" 8)毕业之后可否移民?");
System.out.println(" 9)文凭是否受到我国认可?");
}
@Override
public void applyForEnrol() {
System.out.println("二.入学申请:");
System.out.println(" 1)填写报名表;");
System.out.println(" 2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;");
System.out.println(" 3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。");
}
@Override
public void arriving() {
System.out.println("六.抵达目标学校:");
System.out.println(" 1)安排住宿;");
System.out.println(" 2)了解校园及周边环境。");
}
}
优缺点
优点:
- 它封装了不变的部分,扩展可变部分。它把认为是不变部分的代码逻辑封装到父类中实现,而把可变部分代码逻辑让子类自己具体去实现,便于子类继续扩展。
- 它在父类中提取了公共部分的代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
- 对每个不同的实现都要定义一个子类,这会导致类的个数增加,系统更加庞大,设计更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
应用场景
- 当你想让客户端只扩展算法的特定步骤,而不是整个算法或其结构时,请使用Template Method模式;
- 当你有几个类包含几乎相同的算法,但有一些细微的差异时,请使用此模式。
模式的扩展
在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如下图所示:
package top.saodisheng.designpattern.templatemethod.v3;
/**
* description:
* 含钩子方法的模板方法模式
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class HookTemplateMethod {
public static void main(String[] args) {
HookAbstractClass hook = new HookConcreteClass();
hook.TempplateMethod();
}
}
/**
* 1 创建含钩子方法的抽象类
*/
abstract class HookAbstractClass {
/**
* 模板方法
*/
public void TempplateMethod() {
abstractMethod1();
HookMethod1();
if (HookMethod2()) {
SpecificMethod();
}
abstractMethod2();
}
/**
* 具体方法
*/
public void SpecificMethod() {
System.out.println("抽象类中的具体方法被调用...");
}
/**
* 钩子方法1
*/
public void HookMethod1() {
}
/**
* 钩子方法2
*
* @return
*/
public boolean HookMethod2() {
return true;
}
/**
* 抽象方法1
*/
public abstract void abstractMethod1();
/**
* 抽象方法2
*/
public abstract void abstractMethod2();
}
/**
* 含钩子方法的具体子类
*/
class HookConcreteClass extends HookAbstractClass {
@Override
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
@Override
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
@Override
public void HookMethod1() {
System.out.println("钩子方法1被重写...");
}
@Override
public boolean HookMethod2() {
return false;
}
}
源码中的应用
#Servlet Api
javax.servlet.http.HttpServlet
#Spring
org.springframework.web.servlet.mvc.AbstractController
......
5.2 行为型模式2——解释器模式(Interpreter, 编译原理对应知识点)
速记关键词:虚拟机的机制
简介
定义:给定一个语言,定义它的文法的一种表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。
解释器模式就是定义语言的文法,并且建立一个解释器来解释该语言中的句子。解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中。它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
模板的结构和实现
解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。如果有和扫地生一样上过编译原理这门课的同学应该对这几个词汇不陌生。
文法
文法是用于描述语言的语法结构的形式规则。例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下:
〈句子〉::=〈主语〉〈谓语〉〈宾语〉
〈主语〉::=〈代词〉|〈名词〉
〈谓语〉::=〈动词〉
〈宾语〉::=〈代词〉|〈名词〉
〈代词〉你|我|他
〈名词〉7大学生I筱霞I英语
〈动词〉::=是|学习
注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。
句子
句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。
语法树
解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。
模式的实现
解释器模式实现的关键是定义文法规则、设计终结符类与非终结符类、画出结构图,必要时构建语法树,其代码结构如下:
package top.saodisheng.designpattern.interpreter.v1;
/**
* description:
* 解释器模式
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class InterpreterPattern {
}
/**
* 抽象表达式类
*/
interface AbstractExpression {
/**
* 解释方法
* @param info
* @return
*/
public Object interpret(String info);
}
/**
* 终结符表达式类
*/
class TerminalExpression implements AbstractExpression {
@Override
public Object interpret(String info) {
// 对终结符表达式的处理
return null;
}
}
/**
* 非终结符表达式类
*/
class NonterminalExpression implements AbstractExpression {
private AbstractExpression exp1;
private AbstractExpression exp2;
@Override
public Object interpret(String info) {
//非对终结符表达式的处理
return null;
}
}
/**
* 环境类
*/
class Context {
private AbstractExpression exp;
public Context() {
//数据初始化
}
public void operation(String info) {
//调用相关表达式类的解释方法
}
}
解决的问题
对于一些固定文法构建一个解释句子的解释器。
模式组成
组成(角色) | 作用 |
---|---|
抽象表达式 (Abstract Expression)角色 | 定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。 |
终结符表达式 (Terminal Expression)角色 | 是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。 |
非终结符表达式 (Nonterminal Expression)角色 | 也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。 |
环境(Context)角色 | 通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。 |
客户端(Client) | 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。 |
实例概况
用解释器设计一个北京公交卡的读卡器程序。
说明:假如北京公交车读卡器可以判断乘客的身份,如果是“海淀区”或者“朝阳区”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。
分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。
<expression> ::= <city>的<person>
<city> ::= 海淀区|朝阳区
<person> ::= 老人|妇女|儿童
然后根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。
实例实现
package top.saodisheng.designpattern.interpreter.v2;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* description:
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class InterpreterPattern {
public static void main(String[] args) {
Context bus = new Context();
bus.freeRide("海淀区的老人");
bus.freeRide("海淀区的年轻人");
bus.freeRide("朝阳区的妇女");
bus.freeRide("朝阳区的儿童");
bus.freeRide("南京的年轻人");
}
}
/**
* 1 定义一个抽象表达式(Expression)接口,它包含了解释方法interpret(String info)
*/
interface Expression {
public boolean interpret(String info);
}
/**
* 2 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的城市或人,
* 并实现抽象表达式接口中的解释方法interpret(String info),用来判断被分析的字符串是否是集合中的终结符
*/
class TerminalExpression implements Expression {
private Set<String> set = new HashSet<String>();
public TerminalExpression(String[] data) {
Collections.addAll(set, data);
}
@Override
public boolean interpret(String info) {
return set.contains(info);
}
}
/**
* 3 定义一个非终结符表达式(AndExpression)类,它也是抽象表达式的子类,它包含满足条件的城市终结符表达式对象和满足条件的人员终结符表达式对象,
* 并实现interpret(String info)方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员
*/
class AndExpression implements Expression {
private Expression city = null;
private Expression person = null;
public AndExpression(Expression city, Expression person) {
this.city = city;
this.person = person;
}
@Override
public boolean interpret(String info) {
String s[] = info.split("的");
return city.interpret(s[0]) && person.interpret(s[1]);
}
}
/**
* 4 定义一个环境(Context)类,它包含解释器需要的数据,完成对终结符表达式的初始化,
* 并定义一个方法freeRide(String info)调用表达式对象的解释方法来对被分析的字符串进行解释
*/
class Context {
private String[] citys = {"海淀区", "朝阳区"};
private String[] persons = {"老人", "孕妇", "儿童"};
private Expression cityPerson;
public Context() {
TerminalExpression city = new TerminalExpression(citys);
TerminalExpression person = new TerminalExpression(persons);
cityPerson = new AndExpression(city, person);
}
public void freeRide(String info) {
boolean ok = cityPerson.interpret(info);
if (ok) {
System.out.println("您是" + info + ", 您本次乘车免费!");
} else {
System.out.println(info + ",您不是免费人员,本次乘车扣费2元");
}
}
}
优缺点
优点:
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
缺点:
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
应用场景
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
模式扩展
在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。
现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。
使用前先配置依赖包:
<!-- https://mvnrepository.com/artifact/jep/jep -->
<dependency>
<groupId>jep</groupId>
<artifactId>jep</artifactId>
<version>2.24</version>
</dependency>
package top.saodisheng.designpattern.interpreter.v3;
import org.nfunk.jep.JEP;
/**
* description:
*
* @author 扫地生_saodisheng
* @date 2021-02-06
*/
public class JepDemo {
public static void main(String[] args) {
//一个数学表达式
JEP jep = new JEP();
//给变量赋值
String exp = "((a+b)*(c+b))/(c+a)/b";
jep.addVariable("a", 10);
jep.addVariable("b", 10);
jep.addVariable("c", 10);
try {
//执行
jep.parseExpression(exp);
Object result = jep.getValueAsObject();
System.out.println("计算结果: " + result);
} catch (Throwable e) {
System.out.println("An error occured: " + e.getMessage());
}
}
}
源码中的应用
SpelExpressionParser
5.3 行为型模式3——策略模式(Strategy)
速记关键词:多方案切换
简介
定义:定义了算法簇,分别封装起来,让他们之间可以相互替换,此模式的变化独立与算法的使用者。
在策略模式中它将这些解决问题的方法定义成一个算法群,每一个方法都对应着一个具体的算法,这里的一个算法称之为一个策略。虽然策略模式定义了算法,但是它并不提供算法的选择,即什么算法对于什么问题最合适这是策略模式所不关心的,所以对于策略的选择还是要客户端来做。客户必须要清楚的知道每个算法之间的区别和在什么时候什么地方使用什么策略是最合适的,这样就增加客户端的负担。
设计原则:
- 把变化的代码从不变的代码中分离出来。
- 针对接口编程而不是具体类。
- 多用组合/聚合,少用继承,策略模式中Context通过聚合使用策略。
解决的问题
在有多种算法相似的情况下,使用 if ... else锁带来的复杂和难易维护,使用策略模式将算法的责任和本身进行解耦。
模式组成
组成(角色) | 作用 |
---|---|
抽象策略角色(Strategy) | 定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。 |
具体策略角色(ConcreteStrategy) | 实现了抽象策略定义的接口,提供具体的算法实现。 |
环境类(Context) | 持有一个策略类的应用,最终提供客户端调用 |
实例说明
《植物大战僵尸》这个游戏很多人都玩过,里面有各种不同的植物和僵尸。不同的植物、僵尸各自有不同的特点。假如你要开发这样一款游戏,游戏最开始的版本比较简单,只有两种僵尸:普通僵尸、旗手僵尸。
第一版
类型 | 外观 | 移动 | 攻击 |
---|---|---|---|
普通僵尸 | 普通 | 朝着一个方向移动 | 咬 |
旗手僵尸 | 普通+手持旗子 | 朝着一个方向移动 | 咬 |
package top.saodisheng.designpattern.strategy.v1;
/**
* description:
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class StrategyPattern {
public static void main(String[] args) {
AbstractZombie normalZombie = new NormalZombie();
AbstractZombie flagZombie = new FlagZombie();
normalZombie.display();
normalZombie.move();
normalZombie.attack();
System.out.println();
flagZombie.display();
flagZombie.move();
flagZombie.attack();
}
}
/**
* 1. 定义抽象策略角色,抽象僵尸类
*/
abstract class AbstractZombie {
public abstract void display();
public void attack() {
System.out.println("咬");
}
public void move() {
System.out.println("一步一步移动");
}
}
/**
* 2. 定义具体的策略角色,普通僵尸类
*/
class NormalZombie extends AbstractZombie {
@Override
public void display() {
System.out.println("我是普通僵尸");
}
}
/**
* 3. 定义具体策略角色,旗手僵尸
*/
class FlagZombie extends AbstractZombie {
@Override
public void display() {
System.out.println("我是旗手僵尸");
}
}
随着游戏的上线,需要根据玩家的反馈对对游戏进行各方面的优化
第二版
类型 | 外观 | 移动 | 攻击 |
---|---|---|---|
普通僵尸 | 普通 | 朝着一个方向移动 | 咬 |
旗手僵尸 | 普通+手持旗子 | 朝着一个方向移动 | 咬 |
大头僵尸 | 大头 | 朝着一个方向移动 | 头撞 |
石膏僵尸 | 石膏装 | 一瘸一拐 | 武器 |
XXX僵尸 | ... | ... | ... |
这简单,我们再写俩僵尸类,然后重写跟抽象僵尸类(AbstractZombie)不一样的方法实现就行。
package top.saodisheng.designpattern.strategy.v2;
/**
* description:
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class StrategyPattern {
public static void main(String[] args) {
AbstractZombie bigZombie = new BigZombie();
AbstractZombie gypsumZombie = new GypsumZombie();
bigZombie.display();
bigZombie.move();
bigZombie.attack();
System.out.println();
gypsumZombie.display();
gypsumZombie.move();
gypsumZombie.attack();
}
}
/**
* 1. 定义抽象策略角色,抽象僵尸类
*/
abstract class AbstractZombie {
public abstract void display();
public void attack() {
System.out.println("咬");
}
public void move() {
System.out.println("一步一步移动");
}
}
/**
* 2. 定义具体的策略角色,普通僵尸类
*/
class NormalZombie extends AbstractZombie {
@Override
public void display() {
System.out.println("我是普通僵尸");
}
}
/**
* 3. 定义具体策略角色,旗手僵尸
*/
class FlagZombie extends AbstractZombie {
@Override
public void display() {
System.out.println("我是旗手僵尸");
}
}
/**
* 重写攻击方法和移动方法
* 4. 定义具体策略角色,大头僵尸
*/
class BigZombie extends AbstractZombie {
@Override
public void display() {
System.out.println("我是大头僵尸");
}
@Override
public void attack() {
System.out.println("头撞");
}
}
/**
* 5. 定义具体策略角色,石膏僵尸
*/
class GypsumZombie extends AbstractZombie {
@Override
public void display() {
System.out.println("我是石膏僵尸");
}
@Override
public void move() {
System.out.println("一瘸一拐");
}
@Override
public void attack() {
System.out.println("武器");
}
}
没过多久用户们又玩腻了,用户玩腻了,又要加僵尸了。不过还好你已经得心应手了,不就是各种继承吗。
但是只是无脑加僵尸哪够,还有一堆用户吐槽你这游戏的BUG:你这僵尸遇到障碍物都不带停的,遇到植物应该停止移动,开始攻击。所以这些僵尸的各个行为在不同情况下是不一样的,这可咋办,你已经写了一堆僵尸类了,难倒要挨个类加判断逻辑改变行为?这个时候你想到了开闭原则:对扩展开放,对修改关闭。看来你的代码需要重构一下了。
第三版
僵尸的移动方式和攻击方式有不同的实现方式,而且要可以动态改变。所以先把这两个行为抽象成接口
package top.saodisheng.designpattern.strategy.v3;
/**
* description:
*
* @author 扫地生_saodisheng
* @date 2021-02-05
*/
public class StrategyPattern {
public static void main(String[] args) {
// 普通僵尸
NormalZombie normalZombie = new NormalZombie(new StepByStepMove(), new BiteAttack());
normalZombie.display();
normalZombie.move();
normalZombie.attack();
System.out.println("-----------");
// 旗手僵尸
FlagZombie flagZombie = new FlagZombie(new StepByStepMove(), new BiteAttack());
flagZombie.display();
flagZombie.move();
flagZombie.attack();
System.out.println("-----------");
// 大头僵尸
BigZombie bigZombie = new BigZombie(new StepByStepMove(), new HeadAttack());
bigZombie.display();
bigZombie.move();
bigZombie.attack();
System.out.println("-----------");
// 石膏僵尸
GypsumZombie gypsumZombie = new GypsumZombie(new LameMove(), new BiteAttack());
gypsumZombie.display();
gypsumZombie.move();
//如果石膏僵尸遇到了第一个植物
System.out.println("我遇到了第一个植物");
gypsumZombie.setAttackBehavior(new ArmsAttack());
gypsumZombie.move();
gypsumZombie.attack();
}
}
/**
* 1. 定义移动行为接口
*/
interface MoveBehavior {
void move();
}
/**
* 2. 定义攻击行为接口
*/
interface AttackBehavior {
void attack();
}
/**
* 3. 定义抽象策略角色,抽象类
*/
abstract class AbstractZombie {
MoveBehavior moveBehavior;
AttackBehavior attackBehavior;
/**
* 采用构造方法对两个行为模式进行注入
*
* @param moveBehavior
* @param attackBehavior
*/
public AbstractZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {
this.moveBehavior = moveBehavior;
this.attackBehavior = attackBehavior;
}
/**
* 预留一个抽象方法给不同的子类去实现
*/
abstract void display();
void move() {
moveBehavior.move();
}
void attack() {
attackBehavior.attack();
}
public MoveBehavior getMoveBehavior() {
return moveBehavior;
}
public void setMoveBehavior(MoveBehavior moveBehavior) {
this.moveBehavior = moveBehavior;
}
public AttackBehavior getAttackBehavior() {
return attackBehavior;
}
public void setAttackBehavior(AttackBehavior attackBehavior) {
this.attackBehavior = attackBehavior;
}
}
/**
* 4 定义各种僵尸子类
*/
class NormalZombie extends AbstractZombie {
public NormalZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {
super(moveBehavior, attackBehavior);
}
@Override
void display() {
System.out.println("我是普通僵尸");
}
}
class FlagZombie extends AbstractZombie {
public FlagZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {
super(moveBehavior, attackBehavior);
}
@Override
void display() {
System.out.println("我是旗手僵尸");
}
}
class BigZombie extends AbstractZombie {
public BigZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {
super(moveBehavior, attackBehavior);
}
@Override
void display() {
System.out.println("我是大头僵尸");
}
}
class GypsumZombie extends AbstractZombie {
public GypsumZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {
super(moveBehavior, attackBehavior);
}
@Override
void display() {
System.out.println("我是石膏僵尸");
}
}
/**
* 5 定义移动行为的子类
*/
class StepByStepMove implements MoveBehavior {
@Override
public void move() {
System.out.println("一步一步移动");
}
}
class LameMove implements MoveBehavior {
@Override
public void move() {
System.out.println("一瘸一拐");
}
}
/**
* 6 定义攻击行为子类
*/
class BiteAttack implements AttackBehavior{
@Override
public void attack() {
System.out.println("咬");
}
}
class HeadAttack implements AttackBehavior{
@Override
public void attack() {
System.out.println("头撞");
}
}
class ArmsAttack implements AttackBehavior{
@Override
public void attack() {
System.out.println("武器");
}
}
优缺点
优点:
- 多重条件不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间和空间要求选择不同的实现方式。
- 策略模式提供了对关闭原则的完美支持,可以在不修改代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移动到具体策略类中,实现了二者的分离。
缺点:
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成了很多策略类。
应用场景
- 当有很多类似的类,但它们执行某些行为的方式不同时,可以使用该策略。
- 使用该模式将类的业务逻辑与算法的实现细节隔离开来,这些算法在逻辑上下文中可能不那么重要。
- 当类中具有大量的条件运算符,并且在同一算法的不同变体之间切换时,可以使用该策略。
源码中的应用
#JDK
java.util.Comparator
java.util.concurrent.ThreadPoolExecutor
#Spring
org.springframework.beans.factory.support.InstantiationStrategy
......