深圳幻海软件技术有限公司 欢迎您!

二进制在互联网业务开发中的精妙应用

2023-02-28

背景在互联网业务开发中,我们经常有这样的一个场景,比如有一个活动,这个活动可以是一个红包的活动,也可以是一个优惠券活动,也可以是一个秒杀活动等等,要求这个活动只能在某些场景下使用。例子:如京东上面有很多优惠券,有的优惠券只能在京东上使用,有的优惠券只能在京东7Fresh上使用,有的优惠券只能在京东、

背景

在互联网业务开发中,我们经常有这样的一个场景,比如有一个活动,这个活动可以是一个红包的活动,也可以是一个优惠券活动,也可以是一个秒杀活动等等,要求这个活动只能在某些场景下使用。

例子:

  • 如京东上面有很多优惠券
  • 有的优惠券只能在京东上使用,
  • 有的优惠券只能在京东7Fresh上使用,
  • 有的优惠券只能在京东、京东7Fresh上使用。

我们立马能想到一个方案,就是新增一个活动类型字段activityType,通过这个字段来区分这些活动。

那么如何统一、高效、可扩展地存储这个活动标识,以便后续通过这个标识,来判断这个活动能否在特定的某些场景中使用呢?

方案一

我们可以通过枚举实现,每个枚举包含2个属性——标识、场景,通过标识来判断活动是否能在该场景中使用。

1.存储标识

如只能在京东上使用的标识为1,只能在淘宝特价版上使用的标识为3,等等。

package com.example.activitytype.constants;

/**
 * 活动类型枚举
 *
 * @author hongcunlin
 */
public enum ActivityType {
    /**
     * 京东
     */
    JD(1, "京东"),

    /**
     * 京东极速版
     */
    JDJSB(2, "京东极速版本"),

    /**
     * 京东极速版
     */
    JD_JDJSB(3, "京东、京东极速版本");

    /**
     * 标识
     */
    public Integer code;

    /**
     * 场景
     */
    public String desc;

    /**
     * 枚举
     *
     * @param code 标识
     * @param desc 场景
     */
    ActivityType(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}
  • 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.

枚举里边列举几个活动的类型对应的标识,当然这个标识的数据类型,也可以为字符串,这里简单采用整数来实现。

2.判断使用

上面是基础,都是为下面的服务提供数据支撑而已,下面的服务才是我们要直面的业务,如判断这个活动能否在京东极速版上使用,我们的实现方案对应为:

package com.example.activitytype.service.impl;

import com.example.activitytype.constants.ActivityType;
import com.example.activitytype.service.ActivityTypeService;
import org.springframework.stereotype.Service;

/**
 * 活动服务
 *
 * @author hongcunlin
 */
@Service
public class ActivityTypeServiceImpl implements ActivityTypeService {
    /**
     * 能否在京东极速版使用
     *
     * @param code 标识
     * @return true能/false不能
     */
    @Override
    public boolean isCanUseInJdjsb(Integer code) {
        return ActivityType.JDJSB.code.equals(code) ||
                ActivityType.JD_JDJSB.code.equals(code);
        // TODO 后续这里需要不断维护
    }
}
  • 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.

3.代码测试

测试结果和我们预期的一样

package com.example.activitytype;

import com.example.activitytype.service.ActivityTypeService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class ActivityTypeTest {
    /**
     * 活动服务
     */
    @Resource
    private ActivityTypeService activityTypeService;

    /**
     * 能否在京东极速版使用测试
     */
    @Test
    void isCanUseInJdjsbTest() {
        // 1,京东,false
        System.out.println(activityTypeService.isCanUseInJdjsb(1));
        // 2,京东极速版本,true
        System.out.println(activityTypeService.isCanUseInJdjsb(2));
        // 3,京东、京东极速版本,true
        System.out.println(activityTypeService.isCanUseInJdjsb(3));
    }
}
  • 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.

4.评价

这个方案的优点就是前期实现起来很简单。

缺点就是随着维护的活动类型越来越多,我们需要补充很多判断语句,并且每新增1种活动类型我们需要把涉及的方法都改一遍,这对我们开发人员来说,是完全不可接受的。

方案二

我们可以通过二进制字符串01来存储这些场景,0代表不能使用,1代表能使用,后续我们通过判断二进制字符串的某个位置的值是否为1,就可以轻松判断是否能在这个场景使用了。

1.存储位置

我们的枚举,由一个笼统标识,改为存储二进制的位置。

package com.example.activitytype.constants;

/**
 * 活动类型枚举
 *
 * @author hongcunlin
 */
public enum ActivityIndex {
    /**
     * 001
     * 京东
     */
    JD(1, "京东"),

    /**
     * 010
     * 京东极速版
     */
    JDJSB(2, "京东极速版本"),

    /**
     * 100
     * 京东七鲜
     */
    JD7F(4, "7Fresh");

    /**
     * 位置
     */
    public Integer index;

    /**
     * 场景
     */
    public String desc;

    /**
     * 枚举
     *
     * @param index 位置
     * @param desc  场景
     */
    ActivityIndex(Integer index, String desc) {
        this.index = index;
        this.desc = desc;
    }
}
  • 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.

如下图所示,只能能在京东极速版使用的标识,我们存储为2(二进制为010),通过判断第2位是否为1,就可以判断是否只能在京东极速版使用了。

2.判断使用

根据所在位为1,即可判断能否在该场景下使用

package com.example.activitytype.service.impl;

import com.example.activitytype.constants.ActivityIndex;
import com.example.activitytype.service.ActivityIndexService;
import org.springframework.stereotype.Service;

/**
 * 活动服务
 *
 * @author hongcunlin
 */
@Service
public class ActivityIndexServiceImpl implements ActivityIndexService {
    /**
     * 能否在京东极速版使用
     *
     * @param code 标识
     * @return true能/false不能
     */
    @Override
    public boolean isCanUseInJdjsb(Integer code) {
        // 使用 位运算 判断 值的二进制 指定位是否为1
        return (code & ActivityIndex.JDJSB.index) != 0;
    }
}
  • 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.

这里采用了位运算,简单高效。

3.代码测试

代码的运行结果,和我们预期的一样

package com.example.activitytype;

import com.example.activitytype.service.ActivityIndexService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class ActivityIndexTest {
    /**
     * 活动服务
     */
    @Resource
    private ActivityIndexService activityIndexService;

    /**
     * 能否在京东极速版使用测试
     */
    @Test
    void isCanUseInJdjsbTest() {
        // 010,true
        System.out.println(activityIndexService.isCanUseInJdjsb(2));
        // 111,true
        System.out.println(activityIndexService.isCanUseInJdjsb(7));
        // 100,false
        System.out.println(activityIndexService.isCanUseInJdjsb(4));
        // 001,false
        System.out.println(activityIndexService.isCanUseInJdjsb(1));
    }
}
  • 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.

4.评价

我们枚举的存储,不像方案一中存在冗余的特征,如JDJSB、JD_JDJSB存在交集JDJSB,这是不符合编程中的OOP思想的。

此外,我们通过位运算判断,速度更快,也就是说性能更好。

最后,我们的代码后续随着活动类型的新增,无需开发,也就是维护性更好。

最后

本文中的案例代码已经上传到github了,有需要同学可以自行下载

​​https://github.com/larger5/activity-type-index​​