前言
书接上文,上一篇对 Java 中常用的结构性设计模式做了介绍与分析,本篇就对 Java 中常用的行为型设计模式进行介绍与分析。目录:
- 责任链模式
- 命令模式
- 迭代器模式
- 观察者模式
- 策略模式
- 模版方法模式
简单介绍
- 空对象模式
在软件工程中, 行为型模式为设计模式的一种类型,用来识别对象之间的常用交流模式并加以实现。如此,可在进行这些交流活动时增强弹性。摘自 Wiki
责任链模式
责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。 摘自 Wiki
责任链模式,用来链式的处理一些类型相同,但是实现不同的逻辑,它可以用来处理相同对象(处理后不移除),也可以用来处理一组对象(处理后移除)。顾名思义是一条链条,这条链条可以无止尽的长下去,链条上的每一环都是相同的,那么就很清楚了,责任链其实就是通过一个类提供了一些返回自身(链头),处理自身并返回自身(链环)与处理自身并返回结果(链尾)的方式实现的。Servlet 规范中的 filter。以及 Java 8 新特性中的许多行为接口都采用了这种模式。其实组合模式和迭代器模式从调用方式上来说也是一种责任链(如果对象本身是树干,那么继续调用,直到处理叶子位置)。
先来看一下可以链式调用,处理后返回自身的责任链
/**链式处理类**/
public class ChainBody {
/**获得需要链式处理对象**/
private String message;
/**转换成链式处理类对象**/
ChainBody createHead(String message) {
ChainBody chainBody = new ChainBody();
chainBody.message = message;
return chainBody;
}
/**第一个链式处理方法**/
ChainBody processToFstBody() {
this.message += ",第一次处理完成";
return this;
}
/**第二个链式处理方法**/
ChainBody processToSndBody() {
this.message += ",第二次处理完成";
return this;
}
/**链式处理结束方法**/
void finish() {
System.out.println(this.message);
}
public static void main(String[] args) {
String message = "测试消息";
new ChainBody().createHead(message).processToFstBody().processToSndBody().finish();
}
}
链式处理一个数组链表中的对象
public class ChainBody<T> {
public ChainBody process(List<T> messages) {
ListIterator iterator = messages.listIterator();
if (iterator.hasNext()) {
T s = (T) iterator.next();
System.out.println("现在开始处理 " + s);
iterator.remove();
return process(messages);
} else {
return null;
}
}
public static void main(String[] args) {
List<String> messages = new ArrayList<String>();
messages.add("第一条消息");
messages.add("第二条消息");
messages.add("第三条消息");
new ChainBody().process(messages);
}
}
或者定义含义明确的链式处理,这里举一个 Java 8 新特性函数式编程的例子
/**定义行为接口**/
@FunctionalInterface
public interface ChainAble<T> {
/**抽象方法,接收具体实现**/
void process(T t);
/**默认链式调用方法,由于函数式接口只有一个抽象方法,所以它最多只能实现两个链环,如果需要实现递归或是其他方式处理更多链环,将势必在函数式接口中添加逻辑,这样函数式接口就违背了它的设计初衷**/
default ChainAble<T> thenProcess(ChainAble<T> chainAble) {
return (T t) -> {
process(t);
chainAble.process(t);
};
}
}
class Test {
public static void main(String[] args) {
/**链式调用处理同一个对象**/
ChainAble<String> chainAble = t -> {
System.out.println("开始处理 " + t);
};
chainAble.thenProcess(t -> {
System.out.println("然后开始处理 " + t);
}).process("一条测试消息");
/**链式调用处理链表中的对象,这个例子可能不够恰当,因为传入函数式接口抽象方法中的实现有重复的部分**/
List<String> testList = new ArrayList<>();
testList.add("第一条处理消息");
testList.add("第二条处理消息");
ChainAble<List<String>> chainAble1 = t -> {
Iterator iterator = t.listIterator();
if (iterator.hasNext()) {
String message = (String) iterator.next();
System.out.println("开始处理 " + message);
iterator.remove();
}
};
chainAble1.thenProcess(t -> {
Iterator iterator = t.listIterator();
if (iterator.hasNext()) {
String message = (String) iterator.next();
System.out.println("然后开始处理 " + message);
iterator.remove();
}
}).process(testList);
}
}
最后再举一个 Wiki 打印日志的例子
/**链式处理抽象类**/
abstract class Logger
{
/**mask 参数,用来作为是否处理的判断条件**/
public static int ERR = 3;
public static int NOTICE = 5;
public static int DEBUG = 7;
protected int mask;
//下一个链式处理对象
protected Logger next;
public Logger setNext( Logger l)
{
next = l; //缓存下一个处理对象
return this; //返回自身
}
/**链式处理逻辑**/
public final void message( String msg, int priority )
{
if ( priority <= mask ) //符合处理条件
{
writeMessage( msg ); //调用子类进行处理
if ( next != null ) //判断如果还有链环
{
next.message( msg, priority ); //递归处理
}
}
}
/**处理抽象方法**/
protected abstract void writeMessage( String msg );
}
/**链式调用对象子类**/
class StdoutLogger extends Logger
{
/**条件赋值,其实这个方法也可以放在抽象中**/
public StdoutLogger( int mask ) { this.mask = mask; }
/**子类处理方法**/
protected void writeMessage( String msg )
{
System.out.println( "Writting to stdout: " + msg );
}
}
class EmailLogger extends Logger
{
public EmailLogger( int mask ) { this.mask = mask; }
protected void writeMessage( String msg )
{
System.out.println( "Sending via email: " + msg );
}
}
class StderrLogger extends Logger
{
public StderrLogger( int mask ) { this.mask = mask; }
protected void writeMessage( String msg )
{
System.out.println( "Sending to stderr: " + msg );
}
}
public class ChainOfResponsibilityExample
{
public static void main( String[] args )
{
/**通过链式调用 setNext 方法建立链条,每次返回的都是自身,注意 return this 返回的不是同一个对象,而是 new 出来以后的对象,重新排版以后会看的更清楚一些,其实是从 new StderrLogger( Logger.ERR ) 开始向外依次调用的**/
Logger l = new StdoutLogger( Logger.DEBUG).setNext(
new EmailLogger( Logger.NOTICE ).setNext(
new StderrLogger( Logger.ERR )
)
);
//StdoutLogger 子类处理
l.message( "Entering function y.", Logger.DEBUG );
//StdoutLogger 与 EmailLogger 子类处理
l.message( "Step1 completed.", Logger.NOTICE );
//由所有子类处理
l.message( "An error has occurred.", Logger.ERR );
}
}
责任链主要是分离了相同对象的不同处理方式,可以提高代码的可读性。
命令模式
在面向对象编程的范畴中,命令模式(英语:Command pattern)是一种设计模式,它尝试以对象来代表实际行动。命令对象可以把行动(action) 及其参数封装起来,于是这些行动可以被:
重复多次
取消(如果该对象有实现的话)
取消后又再重做 摘自 Wiki
命令模式顾名思义就是下达命令的模式,命令下达类并不清楚实际完成命令的方式,命令由不同的命令实现类来完成。举个例子,在工作中一个团队通常由策略者,管理者,执行者组成,策略者会告诉管理者想要实行的策略,但是他并不清楚具体实行的方式,或者是由那个执行者来执行,而管理者对于执行者更了解,会根据执行者各自职能与擅长范围的不同分配具体任务,具体任务由执行者执行。
现在将以上逻辑通过命令者模式体现出来
/**命令执行类接口**/
interface Employee {
void execute(String s);
}
/**命令类,策略者**/
public class Commander {
/**策略**/
String thought;
/**策略者手下的管理者**/
Employee manager;
/**初始化策略者**/
public Commander(String thought, Employee manager) {
this.thought = thought;
this.manager = manager;
}
/**发布策略**/
public void command() {
/**管理者执行策略,命令者并不知道管理者如何执行**/
manager.execute(thought);
}
}
/**命令执行类,管理者,实现执行类接口**/
class Manager implements Employee {
/**管理者手下的执行者**/
List<Employee> executors;
/**初始化管理者**/
public Manager(List<Employee> executors) {
this.executors = executors;
}
/**命令执行方法**/
@Override
public void execute(String thought) {
/**遍历执行者,执行具体方法**/
Iterator<Employee> iterator = executors.listIterator();
while(iterator.hasNext()) {
iterator.next().execute(thought);
iterator.remove();
}
/**根据自己的想法分配任务给不同的执行者**/
executeParticular(thought + "五彩黑");
executeParticular(thought + "Cms 后台") ;
executeParticular(thought + "压力测试");
}
/**细分任务方法**/
public void executeParticular(String myThought) {
if (myThought.indexOf("后台") != -1) {
new CodeExecutor().execute(myThought);
} else if (myThought.indexOf("压力") != -1) {
new TestExecutor().execute(myThought);
} else if (myThought.indexOf("五彩黑") != -1) {
new DesignExecutor().execute(myThought);
}
}
}
/**执行者抽象类,仅作为范型辅助类使用**/
abstract class Executor implements Employee {
}
/**程序员执行者类**/
class CodeExecutor extends Executor {
/**程序员具体执行逻辑**/
@Override
public void execute(String thought) {
System.out.println("我是程序员,我负责实现 " + thought + " 的逻辑");
}
}
/**设计执行者类**/
class DesignExecutor extends Executor {
/**设计具体执行逻辑**/
@Override
public void execute(String thought) {
System.out.println("我是设计,我负责设计 " + thought + " 的样式");
}
}
/**测试执行者类**/
class TestExecutor extends Executor {
/**测试具体执行逻辑**/
@Override
public void execute(String thought) {
System.out.println("我是测试,我负责测试 " + thought + " 的功能");
}
}
class CommandTest {
public static void main(String[] args) {
/**为部门添加执行者**/
List<Employee> executors = new ArrayList<>();
executors.add(new DesignExecutor());
executors.add(new CodeExecutor());
executors.add(new TestExecutor());
/**为部门添加管理者,以及建立他与执行者的关系**/
Employee manager = new Manager(executors);
/**为部门添加策略者,以及建立他与管理者的关系**/
Commander commander = new Commander("让猪飞", manager);
/**策略者发布策略**/
commander.command();
}
}
命令模式在 Spring 的源码中被广泛的使用。平时开发中也可以通过这个模式来屏蔽方法调用类对于具体执行类内部实现方式的了解,达到解耦与面向接口编程的目的。
迭代器模式
在 面向对象编程里,迭代器模式是一种设计模式,是一种最简单也最常见的设计模式。它可以让用户透过特定的接口巡访容器中的每一个元素而不用了解底层的实现。
此外,也可以实现特定目的版本的迭代器。 摘自 Wiki
迭代器模式可以说是说句结构的一种衍生,通过迭代器接口提供统一的迭代相关方法,来屏蔽调用者对于数据结构内部的感知。迭代器通常会有一个游标属性,用来表示当前迭代位置,下面实现一个乞丐版的步长为 5 的迭代器,这里只实现了步移方法。
/**迭代器接口**/
interface MyIterator<T> {
/**定义步移方法**/
T next();
}
/**数据结构类**/
public class MyList<T> {
/**维护一个链表**/
T[] array;
/**获取迭代器方法,如果传入参数为“five”,获取五步迭代器,否则获取十步迭代器**/
MyIterator iterator(String s) {
return s.equals("five") ? new FiveStepIterator() : new TenStepIterator();
}
/**初始化数据结构类对象**/
public MyList(T[] array) {
this.array = array;
}
/**抽象迭代器类,由于不同的迭代器都需要游标,所以抽象出来**/
abstract class StepIterator implements MyIterator<T> {
int cursor;
}
/**五步迭代器实现类**/
class FiveStepIterator extends StepIterator {
@Override
public T next() {
cursor += 5; //游标加五
return array[cursor]; //返回对应位置数据
}
}
/**十步迭代器**/
class TenStepIterator extends StepIterator {
@Override
public T next() {
cursor += 10; //游标加六
return array[cursor]; //返回对应位置数据
}
}
}
class Test {
public static void main(String[] args) {
//需要维护的数组
Integer[] array = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18};
//初始化数据结构对象
MyList myList = new MyList(array);
//获取对象的五步迭代器
MyIterator<Integer> fiveStepIterator = myList.iterator("five");
//获取五步后的数据
Integer fiveAfter = fiveStepIterator.next();
System.out.println(fiveAfter);
//获取对象的十步迭代器
MyIterator<Integer> tenStepIterator = myList.iterator("any");
//获取十步后的数据,注意这里的调用方式是完全相同的,调用者完全不知道具体的步骤,由于这里没有实现清空游标,所以会返回 5 + 10 = 15 步后的数据
Integer tenAfter = tenStepIterator.next();
System.out.println(tenAfter);
}
}
迭代器模式在 Java 8 数据结构中大量应用,配合桥接模式使用,是非常重要的设计模式。
观察者模式
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。摘自 Wiki
观察者模式又称为发布/订阅模式,是许多组件以及功能实现的基础,出现的原因是因为已有业务不能改动的情况下,需要增加一些新的功能,这一点上与动态代理类似,观察者模式也是依靠反射调用方法实现的,这一点与动态代理相同。不同的是动态代理会代理方法中的每一个方法,而观察者模是有选择性的(通过传入观察者类对象与方法对象)。观察者模式通常会预先定义一组对于变化的操作方法,然后在被观察对象发生此类变化时,回调观察者类的观察方法,比如 Swing,Zookeeper,以及配合 Kafka 实现高性能的实践消费实践等,这里先来举一个鼠标事件的例子。
先来看一下没有使用观察者模式之前的鼠标事件
/**鼠标类**/
public class Mouse {
public void click() {
System.out.println("鼠标单击");
}
}
public class MouseTest {
public static void main(String[] args) {
/**新建一个鼠标对象**/
Mouse mouse = new Mouse();
/**调用点击方法**/
mouse.click();
}
}
再来看下使用观察者模式以后的鼠标事件实现
/**首先定义事件封装类**/
public class Event {
/**事件源,对应 Event,非必须定义,这里只是为了打印查看**/
private Object source;
/**通知目标,对应 EventCallback,必须定义,否则不知道观察者类是哪个**/
private Object target;
/**回调方法,对应需要调用通知目标的方法,必须定义,否则不知道回调观察者类的哪个方法**/
private Method callback;
/**触发内容,事件源需要触发的内容,必须定义,否则不知道观察的内容是什么**/
private String trigger;
/**触发时间,非必须定义,这里只是为了打印查看**/
private Long time;
/**构建事件对象,对必须定义的属性赋值,这里不传入 trigger 是为了解耦,使同一个被观察对象可以传入不同的内容**/
public Event(Object target, Method callback) {
this.target = target;
this.callback = callback;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Method getCallback() {
return callback;
}
public void setCallback(Method callback) {
this.callback = callback;
}
public String getTrigger() {
return trigger;
}
/**重写 trigger set 方法,返回自身,同样为了解耦**/
Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
/**打印用**/
@Override
public String toString() {
return "Event{" +
"source=" + source +
", target=" + target +
", callback=" + callback +
", trigger='" + trigger + '\'' +
", time=" + time +
'}';
}
}
/**预定义的监听触发枚举类型**/
public enum MouseEventType {
/**监听器监听这些枚举对象**/
ON_CLICK,
ON_DOUBLE_CLICK,
ON_UP,
ON_DOWN,
ON_WHEEL,
ON_MOVE,
ON_OVER
}
/**监听类,作用是维护监听触发事件枚举与事件对象的容器,同时实现触发方法**/
public class EventListener {
/**监听对象的容器,Key 为预定义监听触发枚举类型对象,Value 为事件对象,存储不同监听触发枚举累对象对应的事件对象,使用数据结构对象可以存储一些列对象,并在方法中遍历这些对象并使用相同的逻辑处理,这里就是所谓的发布的实现,在本例中没有写这样的方法**/
protected Map<Enum, Event> events = new HashMap<Enum, Event>();
public void addListender(Enum eventType, Object target, Method callback) {
/**注册到容器中**/
events.put(eventType, new Event(target, callback));
}
/**触发方法**/
protected void trigger(Enum call) {
if (!events.containsKey(call)) { //如果容器中不存在
return;
}
trigger(events.get(call).setTrigger(call.toString())); //调用触发事件内部方法,所以通过触发类型触发的其实是它对应的事件对象,这里对触发事件对象的关键属性 trigger 进行了赋值
}
/**触发事件内部方法**/
private void trigger(Event event) {
try {
event.setSource(this); //将事件的源对象赋值为自身
event.setTime(System.currentTimeMillis()); //为 time 属性赋值
/**event.getCallback() 获得的是观察类的方法,通过反射调用**/
event.getCallback().invoke(event.getTarget(), event);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/**改造 Mouse 类**/
public class Mouse extends EventListener {
public void click() {
/**自身逻辑不变**/
System.out.println("鼠标单击");
/**调用事件触发,这里还是主动去调用的,所以“观察”的含义不是从程序本身触发的,千万不要与轮询触发搞混了**/
this.trigger(MouseEventType.ON_CLICK);
}
public void doubleClick() {
System.out.println("鼠标双击");
this.trigger(MouseEventType.ON_DOUBLE_CLICK);
}
public void up() {
System.out.println("鼠标弹起");
this.trigger(MouseEventType.ON_UP);
}
public void down() {
System.out.println("鼠标按下");
this.trigger(MouseEventType.ON_DOWN);
}
public void wheel() {
System.out.println("鼠标滚动");
this.trigger(MouseEventType.ON_WHEEL);
}
public void move() {
System.out.println("鼠标移动");
this.trigger(MouseEventType.ON_MOVE);
}
public void over() {
System.out.println("鼠标悬停");
this.trigger(com.gupaoedu.vip.pattern.observer.mouse.MouseEventType.ON_OVER);
}
}
/**观察者类,该类的对象与方法将作为构建事件对象的参数**/
public class MouseEventCallback {
/**观察者对于点击事件的处理**/
public void onClick(Event e) {
System.out.println("这是鼠标单击以后要执行的逻辑");
System.out.println("============触发了鼠标单击事件===========\n" + e);
}
public void onDoubleClick(Event e) {
System.out.println("============触发了鼠标双击事件===========\n" + e);
}
public void onUp(Event e) {
System.out.println("============触发了鼠标弹起事件===========\n" + e);
}
public void onDown(Event e) {
System.out.println("============触发了鼠标按下事件===========\n" + e);
}
public void onMove(Event e) {
System.out.println("============触发了鼠标移动事件===========\n" + e);
}
public void onWheel(Event e) {
System.out.println("============触发了鼠标滚动事件===========\n" + e);
}
public void onOver(Event e) {
System.out.println("============触发了鼠标悬停事件===========\n" + e);
}
}
public class MouseTest {
public static void main(String[] args) {
/**
* 通过之前的例子知道,Mouse 类本来就已经存在了,而观察者类 MouseEventCallback 是后来加上,这两者没有必然的联系
* 现在 Mouse 继承于监听类,监听类维护了一组触发事件与事件源的,Mouse 通过 addListener 方法将自己注册到监听器中,注册就是将自己通过观察者类与观察者类方法参数构造成一个事件对象,存放在监听器的容器中,其实就是存放在 Mouse 中
* 并且需要注意的是,Mouse#click 方法也进行了改造,在完成原有的逻辑后,会调用监听器的触发方法,传入触发事件类型
* 最后在监听器中,通过触发类型找到对应的事件源,根据触发类型设置触发内容,通过反射调用事件源的指定观察者方法,传入事件源的观察者类对象与事件源
**/
try {
/**新建鼠标对象**/
Mouse mouse = new Mouse();
/**新建观察者对象**/
MouseEventCallback callback = new MouseEventCallback();
/**获取观察者对象指定观察方法对象**/
Method onClick = callback.getClass().getMethod("onClick", Event.class);
/**向监听器中注册,其实就是存放在 mouse 对象中**/
mouse.addListener(MouseEventType.ON_CLICK, callback, onClick);
/**调用 click 方法,这里为了方便直接在 addListener 后就调用,看上去像穿行话,其实根据监听器的实现可知,只要获取到了这个 mouse 对象,在任何地方调用 click 都是一样的,click 方法会判断当前 mouse 对象的容器中是否存在对应的触发枚举类型,如果有就获取事件,反射调用事件源对象中观察类对象的指定观察方法**/
mouse.click();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
使用观察者模式完美的解决了为 Mouse#click 方法新增实现的问题,同时将新的方法实现类与 Mouse 进行了解耦,并且通过容器的特性保证了只要容器本身不发生更改,即使 Mouse#click 原有的逻辑发生了改变,新增的逻辑也不会改变。
这里千万不要将 “观察” 理解为轮询状态,如果状态发生变化那么就触发这样的含义。“观察” 在这里的含义更像是观察事件的发生,并找寻这个事件对应的触发类型,也就是说没有预先定义的触发类型,观察者就是一纸空谈,找到类型后对于新的事件(Event)产生响应。当然配合更多逻辑来实现轮询相应也是可以的,定义一个轮询类来保存对象的状态就可以了。
策略模式
策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。
策略模式:
定义了一族算法(业务规则);
封装了每个算法;
这族的算法可互换代替(interchangeable)。摘自 Wiki
这里 Wiki 说的比较清楚,策略模式是用于那些在某些流程中,具体某一步的步骤实现可选择替换的一种模式,比如在网上购物,在支付步骤时可以选择银联支付,支付宝支付,微信支付等等,就是一种典型的策略模式,在登陆步骤中可以选择账号密码登陆,二维码登陆,微信登陆,微博登陆等,也是一种策略模式。但是要注意的是,这些策略都是事先定义封装好的,调用者只有选择的权利,并不清楚其内部实现。这里就以支付步骤为例实现一个乞丐版的策略模式
/**支付接口,也就是可以选择的策略的接口**/
public interface Pay {
/**可选择策略的方法**/
void pay(Integer amount);
}
/**策略实现类**/
class ZhiFuBaoPay implements Pay {
/**实现策略的方式**/
public void pay(Integer amount) {
System.out.println("你正在使用支付宝付款,付款金额为 " + amount);
}
}
/**策略实现类**/
class WechatPay implements Pay {
/**实现策略的方式**/
public void pay(Integer amount) {
System.out.println("你正在使用微信付款,付款金额为 " + amount);
}
}
/**可选择的策略类型**/
enum PayType {
/**每种类型对应各自的策略类**/
ZHIFUBAO(new ZhiFuBaoPay()),
WECHAT(new WechatPay());
private Pay pay;
PayType(Pay pay) {this.pay = pay;}
/**获取策略类**/
Pay getPay() {
return pay;
}
}
class Test {
public static void main(String[] args) {
/**调用不同的策略类实现策略,只能选择,不能定义,不清楚实现**/
PayType.ZHIFUBAO.getPay().pay(500);
PayType.WECHAT.getPay().pay(200);
}
}
策略类是迪米特法则的实现之一,通过提供统一的接口,以及预先定义,完全封闭的策略类来达到解耦的目的。
模版方法模式
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。摘自 Wiki
Wiki 也说的比较清楚,就是对于已经定义的一个算法步骤(通常是一个方法,包含了多个字方法),不能再改变这个方法的流程,但是可以 自定义实现(注意这里就与策略模式区分开来了,策略模式只能选择预先封装好的策略) 某些部分的逻辑。这里以多人游戏为例,多人游戏通常可以分为开始,淘汰,结束三个阶段,而每种游戏实现这三个节点的方式不同
/**多人游戏抽象类,定义了模版方法,这些模版方法可以有默认实现,也可以不实现**/
public abstract class Game {
Integer gamersCount;
abstract void gameName();
abstract Integer gamersCount();
abstract void battle();
abstract void gameOver();
protected void start() {
gameName();
gamersCount = gamersCount();
while(gamersCount > 1) {
battle();
}
gameOver();
}
}
/**象棋多人(2 人)游戏,实现自己的逻辑**/
class ChessGame extends Game {
void gameName() {
System.out.println("象棋游戏开始!");
}
Integer gamersCount() {
return 2;
}
void battle() {
this.gamersCount--;
System.out.println("你赢了!");
}
void gameOver() {
System.out.println("象棋游戏结束!");
}
}
/**吃鸡多人(100 人)游戏,实现自己的逻辑**/
class ChickenGame extends Game {
void gameName() {
System.out.println("吃鸡游戏开始!");
}
Integer gamersCount() {
return 100;
}
void battle() {
System.out.println("钢枪胜利!还有 " + this.gamersCount-- + " 名玩家!");
}
void gameOver() {
System.out.println("吃鸡游戏结束!");
}
}
class Test {
public static void main(String[] args) {
Game game = new ChessGame();
game.start();
game = new ChickenGame();
game.start();
}
}
模版方法在 Java 中也有最佳实践类,比如 JdbcTemplate。
空对象模式
In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral (“null”) behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof). 摘自 Wiki
在面向对象编程中,一个空对象是一个没有引用值或者被明确定义为"null"的行为。空对象设计模式描述了这种对象和它们的行为(或者没有)。
如果不使用空对象模式,对于程序中可能出现 null 的地方就要添加判断,或者进行捕获,而这些代码实际上与业务逻辑可能是没有关系的,这将影响代码的可读性。而使用空对象模式一方面对出现 null 的情况提供了更优雅的解决方式,一方面提高了程序的可读性。一个空对象模式通常由正常对象类,空对象类,空对象接口,以及对象工厂四部分组成,空对象类中是对于空对象的处理逻辑,空对象接口是为了封装,屏蔽外界对空对象模式实现的感知,对象工厂中判断是返回正常对象还是空对象,这里就举一个通过学号获取学生的例子。
public interface StudentNullable {
StudentNullable get(Integer no);
void getMsg();
}
class Student implements StudentNullable {
private String msg;
public Student() {
msg = "我是 " + this.toString() + " 号同学";
}
public StudentNullable get(Integer no) {
return new Student();
}
public void getMsg() {
System.out.println(msg);
}
}
class NullStudent implements StudentNullable {
public static NullStudent nullStudent = new NullStudent();
public StudentNullable get(Integer no) {
return nullStudent;
}
public void getMsg() {
System.out.println("没有这个学号的同学!");
}
}
class StudentFactory {
static StudentNullable[] studentNullables = {new Student(), new Student(), new Student(), new Student(), new Student()};
public static StudentNullable findStudent(Integer no) {
int length = studentNullables.length;
if (no < length) {
return studentNullables[no];
} else {
return NullStudent.nullStudent;
}
}
}
class Class {
public static void main(String[] args) {
StudentFactory.findStudent(1).getMsg();
StudentFactory.findStudent(100).getMsg();
}
}
至此所有的 Java 8 行为模式常用设计模式就介绍与分析完毕了,下一篇将介绍 Java 8 并发设计模式。