写在前面
之前搞过一个报名送油卡的活动,这个活动不定期举办,因此需要考虑可配置性,起初考虑后台做个时间配置框,根据指定的时间来生效,但是后续发现这样有个弊端,即只能针对一个活动或者相同时间的多个活动生效,而实际上我们的活动有多个,权衡再三决定使用功能开关来实现。
功能开关
功能开关其实是一种代码可配置性的实践,说到底就是通过控制开关的状态来实现对功能的决定控制。
功能开关的实现有很多种方式,可以使用MySQL或者Redis等数据库,出于对后续数据扩展和对存储容量的考量,此处使用Bit数组来实现。
Bit数组原理
既然是数组,那么下标必然从0开始,bit只有两种取值,要么为0,要么为1:
而0和1正好对应开关的关闭和启用,即ON和OFF状态。之后开发者只需定义好每个开关所在的Bit数组的索引号和状态即可,这样后续就可以通过判断开关的状态来实现对功能的控制:
可以看到使用这种方式所占用的内存空间非常少,理论上只需占用2n位的内存,n为开关的数量。
实战演示
Java中对于Bit数组可以使用BitSet来实现 ,里面有很多方法,这里我们摘几个用到的方法:
1 2 3 4 5 6 7 8
| public class BitSet implements Cloneable, java.io.Serializable { //给指定位置设置值 public void set(int bitIndex, boolean value) {} //给指定位置的值取反操作 public void clear(int bitIndex) {} //获取指定位置的值 public boolean get(int bitIndex) {} }
|
第一步,新建开关状态常量SwitchConst:
1 2 3 4 5 6 7 8 9 10 11 12 13
| /** * 开关常量 */ public class SwitchConst { /** * 启用 */ public static final boolean ON = true; /** * 关停 */ public static final boolean OFF = false; }
|
由于开关只有开和闭这两种状态,因此可以不使用枚举类。
第二步,新建功能开关枚举类SwitchEnum,这个就是开发者所要定义的开关:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| /** * 功能开关枚举类 */ public enum SwitchEnum { /** * HTTPS */ HTTPS(0,SwitchConst.ON,"HTTPS设置"), /** * 充值会员 */ RECHARGE(1,SwitchConst.OFF,"充值送油卡");
/** * 下标 */ private int index; /** * 默认状态 */ private boolean status; /** * 描述 */ private String desc;
SwitchEnum(int index,boolean status,String desc){ this.index = index; this.status = status; this.desc = desc; }
public Integer getIndex(){ return this.index; }
public boolean getStatus(){ return this.status; }
public String getDesc(){ return this.desc; }
@Override public String toString() { return String.format("name=%s,desc=%s",name(),desc); }
|
第三步,新建开关状态切换接口Switch,里面定义切换开关状态和判断接口状态的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| /** * 开关转态切换 */ public interface Switch { /** * 启用开关 * @param switchEnum */ void turnOn(SwitchEnum switchEnum);
/** * 停用开关 * @param switchEnum */ void turnOff(SwitchEnum switchEnum);
/** * 判断开关状态 * @param switchEnum */ boolean judgeStatus(SwitchEnum switchEnum); }
|
第四步,定义一个BitSetSwitch,表示基于BitSet实现的开关,因此需要实现Switch接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public enum BitSetSwitch implements Switch{ MYBITSET;
BitSetSwitch(){ init(); }
private BitSet bitSet = new BitSet();
@Override public void turnOn(SwitchEnum switchEnum) { bitSet.set(switchEnum.getIndex(),SwitchConst.ON); }
@Override public void turnOff(SwitchEnum switchEnum) { bitSet.clear(switchEnum.getIndex()); }
@Override public boolean judgeStatus(SwitchEnum switchEnum) { return bitSet.get(switchEnum.getIndex()); }
private void init(){ Stream.of(SwitchEnum.values()).forEach(item->bitSet.set(item.getIndex(),item.getStatus())); } }
|
可以看到这里我们将BitSetSwitch设置为了一个枚举类,目的就是通过单例生成一个BitSetSwitch对象。注意不能将其设置为普通的类,如果那样的话,在使用的时候就需要通过new关键字生成BitSetSwitch对象,而且每调用一次就得新创建一个对象,这肯定是不行的,功能开关肯定是全局唯一的。
第五步,新建测试类SwitchTest,测试一下功能开关的使用:
1 2 3 4 5 6 7 8 9
| public class SwitchTest { public static void main(String[] args) { BitSetSwitch bitSetSwitch = BitSetSwitch.MYBITSET; bitSetSwitch.turnOn(SwitchEnum.HTTPS); bitSetSwitch.turnOff(SwitchEnum.RECHARGE); System.out.println(String.format("开关【%s】,状态为:%s",SwitchEnum.HTTPS,bitSetSwitch.judgeStatus(SwitchEnum.HTTPS))); System.out.println(String.format("开关【%s】,状态为:%s",SwitchEnum.RECHARGE,bitSetSwitch.judgeStatus(SwitchEnum.RECHARGE))); } }
|
之后运行该方法,可以发现控制台输出如下信息:
1 2
| 开关【name=HTTPS,desc=HTTPS设置】,状态为:true 开关【name=RECHARGE,desc=充值送油卡】,状态为:false
|
这说明当前项目开启了HTTPS设置,但是关停了充值送油卡这一活动。后期要实现灵活控制,可配合配置中心在配置文件中进行控制。
小结
功能开关在实际开发过程中非常实用,因此有必要在理解的基础上进行灵活使用。