装饰者模式

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
    }
}

这样子做的缺点:

  1. 没有做到代码重用,关于开服天数大于X天的要求,我们写了两份一模一样的代码。而且,以后还有相似的需求,那么重复的代码会越来越多;
  2. 如果再来一个条件:每个服只能开出一次。加上已有的两个条件,我们能组合出来的可能,也就是需要实现的类会变得更多!
    上面的方法显然不可行,再想想其它方法,比如这样:
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
FileInputStreamByteArrayInputStream就是实体类,也就是我们的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()的结果进行装饰,返回装饰的结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值