工具方法的封装 工厂模式+单例模式的应用

单例+工厂+配置文件做了一个可切换底层的 工具类封装。

1、需求

       项目中会大量用到工具类,比如字符串工具类、集合工具类、http工具类、object工具类等。有很多开源包,比较常用的有 apache 的工具包、hutool 工具包。我希望在这些第三方包的基础上做一层封装,可以随时切换工具包的底层实现。防止某个开源包有bug、不维护了,能够方便更换。

以封装 string工具类、coll 集合工具类为例子。

2、构思

      有不同的底层实现,究竟选哪一种底层实现,肯定是需要开发的时候传进入,才能进行判断选择。

     我想实现的是一处修改,全局都不用修改。所以这个控制选取底层实现的变量应该项目启动之前就确定了。

于是把这个变量放在配置类中。

       我只关心工具类的使用,并不关心创建,我希望我给你一个变量,你动态创建我需要的对象,我随处可用。很明显,将类的创建与使用进行解耦的工厂模式很适合这里。

3、实战

3.1  配置类

配置 type: 你是用 hutool、apache、system【自己实现】

包路径 packagePath:真正做实现的工具包的路径。

注意:我使用的是静态变量注入,需要用set方法,直接@value是不可以的。还有其他方法自行百度。

JAVA代码:

/**
 * @author xgz
 */
@Slf4j
@Component
@ConfigurationProperties(prefix = "custom.utils")
public class UtilsConfig {

    /** 工具底层类型 **/
    public static String type;

    /** 真正干活的实现类包路径 **/
    public static String packagePath;

    public void setType(String type){
        UtilsConfig.type = type;
        log.info("注入静态变量type={}成功", type);
    }
    public void setPackagePath(String packagePath){
        UtilsConfig.packagePath = packagePath;
        log.info("packagePath={}成功", packagePath);
    }
}

配置文件:

custom:
  # 工具类底层配置
  utils:
    # 底层类型
    type: hutool
    # 真正实现类所在的包
    package-path: com.xgzit.common.thirdpack.impl

 3.2、常量、枚举

工具类底层实现类型 的枚举,为了和配置类中的配置进行匹配。

import lombok.Getter;

import java.util.Objects;

public enum UtilsBaseEnum {
    /** 阿帕奇底层实现 **/
    APACHE("apache", "阿帕奇 底层实现"),
    /** hutool底层实现 **/
    HUTOOL("hutool", "hutool 底层实现"),
    /** 不使用工具自己底层实现 **/
    SYSTEM("system", "本系统自己的底层实现")
    ;
    @Getter
    private String value;
    @Getter
    private String label;

    UtilsBaseEnum(String value, String label){
        this.value = value;
        this.label = label;
    }
    /** 根据值获取枚举 **/
    public static UtilsBaseEnum getEnum(String value){
        if (Objects.isNull(value)){
            throw new RuntimeException("枚举值不能为空");
        }
         for (UtilsBaseEnum item : UtilsBaseEnum.values()) {
             if (item.value.equalsIgnoreCase(value)){
                 return item;
             }
         }
         throw new RuntimeException("请输入正常的枚举值");
     }
}

工具类型枚举 有字符串工具类、集合工具类、对象工具类等。

import lombok.Getter;

import java.util.Objects;

public enum UtilsTypeEnum {
    /** 字符串工具类 **/
    STRING("String", "字符串工具类"),
    /** 集合工具类 **/
    COLL("Coll", "集合工具类"),
    /** 对象工具类 **/
    OBJECT("Object", "对象工具类")
    ;
    @Getter
    private String value;
    @Getter
    private String label;

    UtilsTypeEnum(String value, String label){
        this.value = value;
        this.label = label;
    }
    /** 根据值获取枚举 **/
    public static UtilsTypeEnum getEnum(String value){
        if (Objects.isNull(value)){
            throw new RuntimeException("枚举值不能为空");
        }
         for (UtilsTypeEnum item : UtilsTypeEnum.values()) {
             if (item.value.equalsIgnoreCase(value)){
                 return item;
             }
         }
         throw new RuntimeException("请输入正常的枚举值");
     }
}

3.3、抽取工具类的公共方法作为接口

因为有2种类型的工具类,字符串工具类和 集合类型工具类。

抽取字符串工具类的通用方法,作为接口

public interface StringUtilsMethods {

    boolean isBlank(String str);

    boolean isNotBlank(String str);

    boolean isEmpty(String str);

    boolean isNotEmpty(String str);
}

抽取集合工具类的通用方法,作为接口

/**
 * 集合工具类方法
 * @author xgz
 */
public interface CollUtilsMethods {

    boolean isEmpty(Collection coll);

    boolean isNotEmpty(Collection coll);
}

3.4、不同的底层实现

以 apache工具类为底层实现的 字符串工具类

/**
 * 以 apache工具类为底层实现的 字符串工具类
 * @author xgz
 */
public class ApacheStringUtils implements StringUtilsMethods {

    public static final String type = "APACHE_STRING";

    @Override
    public boolean isBlank(String str) {
        System.out.println("apach 底层 str");
        return StringUtils.isBlank(str);
    }

    @Override
    public boolean isNotBlank(String str) {
        return StringUtils.isNotBlank(str);
    }

    @Override
    public boolean isEmpty(String str) {
        return StringUtils.isEmpty(str);
    }

    @Override
    public boolean isNotEmpty(String str) {
        return StringUtils.isNotEmpty(str);
    }
}

hutool工具为底层实现的字符串工具类

/**
 * 以 hutool 工具为底层实现的字符串工具类
 * @author xgz
 */
public class HutoolStringUtils implements StringUtilsMethods {

    public static final String type = "HUTOOL_STRING";

    @Override
    public boolean isBlank(String str) {
        System.out.println("hutool的底层实现 str ");
        return CharSequenceUtil.isBlank(str);
    }

    @Override
    public boolean isNotBlank(String str) {
        return CharSequenceUtil.isNotBlank(str);
    }

    @Override
    public boolean isEmpty(String str) {
        return CharSequenceUtil.isEmpty(str);
    }

    @Override
    public boolean isNotEmpty(String str) {
        return CharSequenceUtil.isNotEmpty(str);
    }
}

如果需要拓展,写上各种底层实现的工具类就是了。

工具类中的 type 字段的值: 必须是 底层实现类型 + "_" + 工具类型枚举 的组合,建议大写,其实无所谓,因为匹配时,无论是配置文件的,还是枚举类的,都会先全部转成大写,再进行匹配。

它的作用是生成映射,就是类型与实现类之间的映射。

import cn.hutool.core.collection.CollUtil;
import com.xgzit.common.thirdpack.factory.coll.CollUtilsMethods;

import java.util.Collection;
/**
 * 以 hutool 工具为底层实现的字符串工具类
 * @author xgz
 */
public class HutoolCollUtils implements CollUtilsMethods {

    public static final String type = "HUTOOL_COLL";

    
    @Override
    public boolean isEmpty(Collection coll) {
        System.out.println("hutool的底层实现 HUTOOL_COLL ");
        return CollUtil.isEmpty(coll);
    }

    @Override
    public boolean isNotEmpty(Collection coll) {
        System.out.println("hutool的底层实现 HUTOOL_COLL ");
        return CollUtil.isNotEmpty(coll);
    }
}

阿帕奇集合工具底层实现

import com.xgzit.common.thirdpack.factory.coll.CollUtilsMethods;
import org.apache.commons.collections4.CollectionUtils;

import java.util.Collection;

public class ApacheCollUtils implements CollUtilsMethods {
    public static final String type = "APACHE_COLL";

    @Override
    public boolean isEmpty(Collection coll) {
        System.out.println("apach 底层 coll");
        return CollectionUtils.isEmpty(coll);
    }

    @Override
    public boolean isNotEmpty(Collection coll) {
        System.out.println("apach 底层 coll");
        return CollectionUtils.isNotEmpty(coll);
    }
}

3.5、获取类型与实现类映射的工具类

原理是包扫描,包路径是 配置文件packagePath 的值。如果需要扫描子包,还需要修改一下代码。

@Slf4j
public class UtilsMapping {
    private static HashMap<String, Class> utilsMap = new HashMap<>();
    // 通过包扫描获取包下所有.class文件
    private static Class[] getClassByPackage(String packageName) {
        try {
            Enumeration<URL> resources = UtilsMapping.class.getClassLoader()
                .getResources(packageName.replaceAll("\\.", "/"));
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                String[] file = new File(url.getFile()).list();
                List<String> aClass = Arrays.stream(file)
                    .filter(item -> item.endsWith("class"))
                    .collect(Collectors.toList());
                Class[] classList = new Class[aClass.size()];
                for (int i = 0; i < aClass.size(); i++) {
                    classList[i] = Class
                            .forName(packageName + "." + aClass.get(i)
                            .replaceAll("\\.class", ""));
                }
                return classList;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new Class[0];
    }

    public static Map<String, Class> getMapping(){
        Class[] classByPackage = getClassByPackage(UtilsConfig.packagePath);
        for (Class aClass : classByPackage) {
            try {
                Field type = aClass.getDeclaredField("type");
                String o = (String) type.get(null);
                if (Objects.nonNull(o)) {
                    utilsMap.put(o.toUpperCase(), aClass);
                }
            } catch (NoSuchFieldException | IllegalAccessException e) {
                log.info("{}没有type字段",aClass.getSimpleName());
            }
        }
        return utilsMap;
    }
}

这个工具的getMapping方法会得到这样的映射, key是type,value是实现类。

关键代码是 utilsMap.put(o.toUpperCase(), aClass);  意味着key会先变成大写,做了一个忽略大小写的处理。

 3.6  工厂接口

只有一个方法,获取工具类。

如果是很多个方法,方法还返回接口,就变成抽象工厂,这里不需要,只需要创建一种工具就ok。

/**
 * 工具工厂 接口  获取各种类型的工具  字符串String、coll类型
 * @author xgz
 */
public interface UtilsFactory<T> {

    T getUtil();
}

3.7 工厂实现类

我们最终要获取的是 字符串工具类、集合工具类,并不关心底层实现。这个工厂实现类就是 根据我们传入的配置值,创建对应的工具类。工具类创建一次就可以了,采用线程安全的懒汉式单例。

@Component
public class StringUtilFactory implements UtilsFactory<StringUtilsMethods> {

    private static final String TYPE = UtilsTypeEnum.STRING.getValue().toUpperCase();

    private volatile static StringUtilsMethods strUtils  = null;

    @Override
    public StringUtilsMethods getUtil() {
        if (strUtils == null){
            synchronized (StringUtilFactory.class){
                if (strUtils == null){
                    UtilsBaseEnum anEnum = UtilsBaseEnum.getEnum(UtilsConfig.type);
                    Map<String, Class> mapping = UtilsMapping.getMapping();
                    Class aClass = mapping.get(anEnum.getValue().toUpperCase() + "_" + TYPE);
                    try {
                        strUtils = (StringUtilsMethods)aClass.newInstance();
                    } catch (InstantiationException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return strUtils;

    }
}

     主要逻辑是 根据配置类的 type 去 枚举类里面找,防止配置文件乱写。找不到会报错。也可以改成默认值形式。找到之后 和工厂自身的静态变量 TYPE 进行拼接,就是 UtilsMapping 的key的样子,再去这个映射类中找到对应的类,返回。用单例的话就不会重复创建对象了。

@Component
public class CollUtilFactory implements UtilsFactory<CollUtilsMethods> {

    private String TYPE = UtilsTypeEnum.COLL.getValue().toUpperCase();
    private volatile static CollUtilsMethods coll;


    @Override
    public CollUtilsMethods getUtil() {
        if (coll == null) {
            synchronized (CollUtilFactory.class) {
                UtilsBaseEnum anEnum = UtilsBaseEnum.getEnum(UtilsConfig.type);
                Map<String, Class> mapping = UtilsMapping.getMapping();
                Class aClass = mapping.get(anEnum.getValue().toUpperCase() + "_" + TYPE);
                try {
                    coll = (CollUtilsMethods) aClass.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return coll;
    }
}

3.8 使用 

首先要在配置文件配好。

然后在需要的地方注入,要什么工具类,注入什么工厂。

// 注入
private final StringUtilFactory stringUtilFactory;

// 先获取工具类,再调用工具类的方法
stringUtilFactory.getUtil().isBlank("测试字符串");

4、拓展

切换 底层实现只需 配置文件修改 type 的值。

拓展情况1:

增加一种已实现的工具类底层实现,比如 xxx实现的字符串工具类。

只需要写一个实现类实现 字符串的通用方法,type为 xxx_STRING

然后在 底层实现类型 枚举 加上这个底层实现。 即可。

拓展情况2:

如果是新加入 比如说对象类型工具类。现在只有字符串类型、集合类型工具类。

那就需要仿造 字符串类型或者集合类型写。

1、对象类型工具类的公共方法 抽取成接口

2、需要对象类型工具类的 不同底层实现类,实现上面的接口

3、写一个对象工具工厂实现类,实现工厂接口。类似 StringUtilFactory,用单例提供具体的实现类

4、在 工具类型枚举 加上这种类型的工具,如果新增了底层实现,要在 底层实现枚举类 新增一条枚举。

最后附上全部代码图:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值