0. 不断拓展的需求
卡牌游戏有着一套严格的战力控制方案。例如我们现在开启一个机关兽活动,为了不让新服务器的玩家拥有机关兽,我们一般会做一个限制:开服天数大于X天的服务器才能开出机关兽活动。
现在让你来实现这个返回活动时间的接口,你会如何设计?
听起来很简单:
public interface Time {
long[] getActivityTime();
}
public class Time_ZoneOpenDaysLimit implements Time {
private String timeParams;
@Override
public long[] getActivityTime() {
//如果满足开服条件,返回活动时间,否则返回null
}
}
如果过阵子,我们要开启一个红将活动和战马活动,红将活动要求:开服日期在Y年M月以前的服务器才能开出。战马活动要求:开服天数大于X天,且开服日期在Y年M月以前的服务器才能开出。
如果我们还是像上面那样子实现的话:
public class Time_ZoneOpenDateLimit implements Time {
@Override
public long[] getActivityTime() {
//如果满足条件,返回活动时间,否则返回null
}
}
public class Time_ZoneOpenDaysLimitAndZoneOpenDateLimit implements Time {
@Override
public long[] getActivityTime() {
//如果满足条件,返回活动时间,否则返回null
}
}
这样子做的缺点:
- 没有做到代码重用,关于开服天数大于X天的要求,我们写了两份一模一样的代码。而且,以后还有相似的需求,那么重复的代码会越来越多;
- 如果再来一个条件:每个服只能开出一次。加上已有的两个条件,我们能组合出来的可能,也就是需要实现的类会变得更多!
上面的方法显然不可行,再想想其它方法,比如这样:
public class ActivityTime {
private boolean zoneOpenDayLimit;
private boolean zoneOpenDateLimit;
public long[] getActivityTime() {
if (zoneOpenDateLimit) {
//如果不符合条件直接返回null
}
if (zoneOpenDayLimit) {
//如果不符合条件直接返回null
}
//返回活动时间
}
}
这种实现方式虽然解决上面提到的两个问题,却带来了新的问题:每次有新的条件,我们都得修改原有接口。
这违背了设计原则:Classes should be open for extension, but closed for modification.(有新需求时,我们应该拓展类,而不是修改类)
2. 装饰者模式
The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
装饰者模式可以动态地给一个对象添加额外的功能,比起继承,装饰者模式能更灵活的拓展功能。
光看定义很难看懂,我们看一下使用装饰者模式实现的时间类的UML图:
画一个比较方便理解的图:
再具体看一下代码:
package time.decorator;
public interface Time {
public long[] getActivityTime();
}
public abstract class AbstractTime implements Time {
}
public class TimeRange extends AbstractTime {
@Override
public long[] getActivityTime() {
// 返回活动时间,不受任何限制
}
}
public abstract class TimeDecorator implements Time {
}
public class ZoneOpenDateDecorator extends TimeDecorator {
private Time wrappedTime;
public ZoneOpenDateDecorator(Time wrappedTime) {
this.wrappedTime = wrappedTime;
}
@Override
public long[] getActivityTime() {
// 如果不满足条件,返回null,否则返回活动时间
return wrappedTime.getActivityTime();
}
}
public class ZoneOpenDayDecorator extends TimeDecorator {
Time wrappedTime;
public ZoneOpenDayDecorator(Time wrappedTime) {
this.wrappedTime = wrappedTime;
}
@Override
public long[] getActivityTime() {
// 如果不满足条件,返回null,否则返回活动时间
return wrappedTime.getActivityTime();
}
}
public class Test {
public static void main(String[] args) {
// 返回活动时间,不受任何限制
Time time1 = new TimeRange();
long[] activityTime1 = time1.getActivityTime();
// 返回活动时间,受开服时间限制
Time time2 = new ZoneOpenDateDecorator(new TimeRange());
long[] activityTime2 = time2.getActivityTime();
// 返回活动时间,受开服天数限制
Time time3 = new ZoneOpenDateDecorator(new ZoneOpenDayDecorator(new TimeRange()));
long[] activityTime3 = time3.getActivityTime();
}
}
装饰类只知道它所装饰的是一个Time
,但是这个Time
是另一个装饰类还是具体的时间类,它并不关心。它所做的,就是给自己所包装的Time
加上一些装饰(条件限制),返回装饰后的结果。
2. Java IO
Java IO的设计模式也采用了装饰者模式:
这里的InputStream
,就是上面我们提到的Time
;
FilterInputStream
就是装饰者,是我们的TimeDecorator
;
FileInputStream
和ByteArrayInputStream
就是实体类,也就是我们的TimeRange
。
InputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“test.txt”)));
//read()就是我们的getActivityTime()
while((c = in.read()) >= 0) {
System.out.print((char)c);
}
现在来看这段代码,应该就能理解它的用意了。
in
是一个DataInputStream
,调用read()
,就是将BufferedInputStream#read()
的结果进行装饰,返回装饰的结果。而BufferedInputStream#read()
,就是将FileInputStream#read()
的结果进行装饰,返回装饰的结果。