开闭原则最佳实践-Java反射、单例模式、策略模式和工厂模式混用消除if-else
开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”
背景
一款服务为用户推出优惠套餐,每款套餐又有二级套餐分类,比如:有一款套餐A,可选基础套餐A和高级套餐A。 需要根据二级套餐Id获取二级套餐名,和二级套餐所属的一级套餐。 起初只推出了两款套餐,所以我老老实实的用if-else:
if(packageId == basePackageAId){
getBasePackageAName;
getPackageAId;
}else if(packageId == superPackageAId){
getSuperPackageAName;
getPackageAId;
}else if(packageId == basePackageBId){
getBasePackageBName;
getPackageBId;
}else if(packageId == superPackageBId){
getSuperPackageBName;
getPackageBId;
}
将来服务又要再推出好几十款套餐,还能继续追加if-else吗?不追求代码质量,肯定不介意继续追加if-else。所以我对代码进行了整改,使代码整洁、易维护 。
策略模式应用
将所有的套餐抽象出一个超类:BasePackage,然后每款套餐都继承自BasePackage。 BasePackage定义:
/**
*套餐超类
*/
public abstract class BasePackage {
// 维护package的id和名称,key:packageId,value:packageName
Map<String,String> packageMap;
// 初始化packageMap
BasePackage(Map<String,String> packageMap) {
this.packageMap = packageMap;
}
// 根据packageId获取packageName
public final String getPackageName(String packageId) {
return packageMap.get(packageId);
}
// 获取二级套餐所属的一级套餐Id
public abstract String getParentPackageId();
}
套餐A定义:
/**
*套餐A
*/
public class PackageA extends BasePackage {
// 套餐A基础套餐
static final String BASEPACKAGEA_ID = "basePackageAId";
// 套餐A高级套餐
static final String SUPERPACKAGEA_ID = "superPackageAId";
// 维护套餐A的二级套餐
private static final Map<String,String> packageAMap = new HashMap<String,String>();
// 初始化packageAMap
static {
packageAMap.put(BASEPACKAGEA_ID, "basePackageAName");
packageAMap.put(SUPERPACKAGEA_ID, "superPackageAName");
}
// PackageA构造器:初始化BasePackage 的packageMap
public PackageA() {
super(packageAMap);
}
// 重写getParentPackageId,获取二级套餐所属的一级套餐Id
@Override
public String getParentPackageId() {
return "packageAId";
};
}
工厂模式应用
提供一个package工厂,可以根据二级套餐Id获取对应的Package实例。 PackageFactory定义:
/**
*package工厂
*/
public class PackageFactory {
// 维护各种套餐对应的套餐实例,key:二级套餐Id,value:套餐实例
private static Map<String, BasePackage> packageInstanceMap = new HashMap<String, BasePackage>();
// 初始化packageInstanceMap
static {
packageInstanceMap.put(PackageA.BASEPACKAGEA_ID, new PackageA());
packageInstanceMap.put(PackageA.SUPERPACKAGEA_ID, new PackageA());
}
// 根据二级套餐Id获取对应的Package实例
public static BasePackage createPackage(String packageId) {
return packageInstanceMap.get(packageId);
}
}
现在可以将前文中一大坨的 if-else用两行代码替代,彻底消除了if-else:
PackageFactory.createPackage(packageId).getPackageName(packageId);
PackageFactory.createPackage(packageId).getParentPackageId();
现在系统代码变得整洁和可维护了,但是,Package、PackageFactory 类定义的优雅吗?答案是否定的,每个二级套餐对应的套餐实例居然是重新new的。往夸张了想:如果系统中有好几万种套餐,每种套餐下又有好几万种二级套餐。那么一个服务系统,单单就一个套餐业务就存在上亿个套餐对象,系统性能肯定是不理想的。为了解决这个问题,将Package定义成单例的。
单例模式应用
进一步优化PackageA和PackageFactory定义。 套餐A单例实现:
/**
*单例套餐A
*/
public class PackageA extends BasePackage {
// 套餐A基础套餐
static final String BASEPACKAGEA_ID = "basePackageAId";
// 套餐A高级套餐
static final String SUPERPACKAGEA_ID = "superPackageAId";
// 维护套餐A的二级套餐
private static final Map<String,String> packageAMap = new HashMap<String,String>();
// 初始化packageAMap
static {
packageAMap.put(BASEPACKAGEA_ID, "basePackageAName");
packageAMap.put(SUPERPACKAGEA_ID, "superPackageAName");
}
// PackageA私有构造器:初始化BasePackage 的packageMap
private PackageA() {
super(packageAMap);
}
// 持有packageA实例的私有静态类
private static class PackageAHolder {
private static PackageA packageA = new PackageA();
}
// 获取Package的单例
public static PackageA getInstance() {
return PackageAHolder.packageA;
}
// 重写getParentPackageId,获取二级套餐所属的一级套餐Id
@Override
public String getParentPackageId() {
return "packageAId";
};
}
PackageFactory持有单例的Package:
/**
*持有单例Package的package工厂
*/
public class PackageFactory {
// 维护各种套餐对应的套餐实例,key:二级套餐Id,value:套餐实例
private static Map<String, BasePackage> packageInstanceMap = new HashMap<String, BasePackage>();
// 初始化packageInstanceMap
static {
packageInstanceMap.put(PackageA.BASEPACKAGEA_ID, PackageA.getInstance());
packageInstanceMap.put(PackageA.SUPERPACKAGEA_ID, PackageA.getInstance());
}
// 根据二级套餐Id获取对应的Package实例
public static BasePackage createPackage(String packageId) {
return packageInstanceMap.get(packageId);
}
}
这份业务代码暂时是不满足开闭原则的,以后每新增一种套餐不可能都去修改PackageFactory 吧。如果只需新增套餐类,而不用修改PackageFactory ,才是满足开闭原则的。借助java反射优化PackageFactory ,不用在PackageFactory手动获取Package实例。
java反射应用
引入reflections maven包,用于反射获取BasePackage所有子类Package的Class。
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
PackageFactory反射初始化packageInstanceMap :
/**
*持有单例Package的package工厂
*/
public class PackageFactory {
// Package字段修饰符
private final static String MODIFIER = "static final";
// 获取Package单例的方法名
private final static String GETINSTANCE = "getInstance";
// 维护各种套餐对应的套餐实例,key:二级套餐Id,value:套餐实例
private static Map<String, BasePackage> packageInstanceMap = new HashMap<String, BasePackage>();
// 反射初始化packageInstanceMap
static {
final String packageName = PackageFactory.class.getPackage().getName();
Reflections reflections = new Reflections(packageName, new SubTypesScanner());
Set<Class<? extends BasePackage>> subTypes = reflections.getSubTypesOf(BasePackage.class);
subTypes.forEach(subtype -> {
try {
Method getInstance = subtype.getMethod(GETINSTANCE);
BasePackage package = (BasePackage) getInstance.invoke(null);
Field[] declaredFields = subtype.getDeclaredFields();
for (Field field : declaredFields) {
if (StringUtils.equals(MODIFIER, Modifier.toString(field.getModifiers()))) {
packageInstanceMap.put(field.get(subtype).toString(), package);
}
}
} catch(Exception e) {
}
});
}
// 根据二级套餐Id获取对应的Package实例
public static BasePackage createPackage(String packageId) {
return packageInstanceMap.get(packageId);
}
}
注:JDK8,在static代码块中,并行流并行执行lambada表达式时,会出现死锁问题。
自此,套餐业务代码满足开闭原则。下文是代码基于Spring框架的进一步精简,感兴趣的童鞋可以继续阅读。
注入到Spring IOC容器实现单例
基于Spring框架开发系统,Package中编写具体业务代码时,避免不了自动装配注入到Spring IOC容器中的bean,自然的将Package实例也注入到Spring IOC容器中。由于注入到Spring IOC容器的实例默认是单例,所以Package单例不需要手动实现。 Package实例注入到Spring IOC容器中:
/**
*单例注入到Spring IOC容器的套餐A
*/
Component
public class PackageA extends BasePackage {
// 套餐A基础套餐
static final String BASEPACKAGEA_ID = "basePackageAId";
// 套餐A高级套餐
static final String SUPERPACKAGEA_ID = "superPackageAId";
// 维护套餐A的二级套餐
private static final Map<String,String> packageAMap = new HashMap<String,String>();
// 初始化packageAMap
static {
packageAMap.put(BASEPACKAGEA_ID, "basePackageAName");
packageAMap.put(SUPERPACKAGEA_ID, "superPackageAName");
}
// PackageA私有构造器:初始化BasePackage 的packageMap
private PackageA() {
super(packageAMap);
}
// 重写getParentPackageId,获取二级套餐所属的一级套餐Id
@Override
public String getParentPackageId() {
return "packageAId";
};
}
实现ApplicationContextAware接口获取Spring IOC容器中的bean
实现ApplicationContextAware接口的setApplicationContext(ApplicationContext applicationContext)方法,通过ApplicationContext 获取注入到Spring IOC容器中的bean,ApplicationContext的getBeansOfType方法可以获取指定类型的bean(包括子类),故可以将上文引入的reflections maven包给移除。 实现ApplicationContextAware的PackageFactory:
/**
*持有单例Package的package工厂
*/
Component
public class PackageFactory implements ApplicationContextAware {
// Package字段修饰符
private final static String MODIFIER = "static final";
// 维护各种套餐对应的套餐实例,key:二级套餐Id,value:套餐实例
private static Map<String, BasePackage> packageInstanceMap = new HashMap<String, BasePackage>();
// 反射初始化packageInstanceMap
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext.getBeansOfType(BasePackage.class).values().forEach(package -> {
Class<? extends BasePackage> packageClass = package.getClass();
Field[] declaredFields = packageClass.getDeclaredFields();
for (Field field : declaredFields) {
if (StringUtils.equals(MODIFIER, Modifier.toString(field.getModifiers()))) {
try {
packageInstanceMap.put(field.get(packageClass).toString(), package);
} catch (IllegalAccessException e) {
}
}
}
});
}
// 根据二级套餐Id获取对应的Package实例
public static BasePackage createPackage(String packageId) {
return packageInstanceMap.get(packageId);
}
}
Spring初始化Spring IOC容器中的bean时,PackageFactory实现的setApplicationContext方法被执行,故串行初始化packageInstanceMap 。
通过引入Spring框架,Package 和PackageFactory 被进一步精简优化。