架构设计系列文章
作为一名开发人员,在软件开发过程中,我们通常会面临一些不断重复发生的问题,而设计模式就是这些问题的通用解决方案,这些解决方案是众多开发人员经过相当长的一段时间的试验和错误所总结出来的。
那么,设计模式到底是什么呢?
设计模式是一套经过验证、被反复使用的,用于解决特定场景、重复出现地特定问题的解决方案。
简单来说,设计模式是在软件开发过程中,用于解决特定问题的解决方案。
在介绍为什么使用设计模式之前,我们先来看看在软件开发中,设计模式的两个主要用途。
设计模式已经经历了很长一段时间的发展,它们是软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员,通过一种简单快捷的方式来写出更优雅的代码。
设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。
基于上面的两个主要用途,我们来提炼下为什么要使用设计模式的原因。
诚然设计模式是一系列通用的技术性解决方案,然而我们需要明白,技术是为了业务服务的。
在实际开发过程中,我们不要为了设计模式而设计,这只会对开发有害。
因此,设计模式的正确打开方式是,以满足业务需要即可。
对于如何才能用好设计模式,我觉得需要遵守以下几个原则:
开发的最终目的是需求的实现,因此设计模式需要为需求服务。
引入设计模式是为了简化问题,当我们觉得问题变得更加复杂的时候,说明简单的问题已经被复杂化,需要评估问题本身是否需要设计模式。
软件开发是一项实践性工作,没有不会下棋的围棋大师,也没有不会编程的架构师。掌握设计模式需要理论积累以及实践积累,在实践过程中进行“渐悟”。
切记,设计模式要活学活用,不要生搬硬套。
下面我们一起来看看几个常用设计模式的具体应用,感兴趣的小伙伴,可以点击下面的链接找到对应的代码实现。
public interface CacheService<K, R> {
default Cache getNativeL2cache() {
throw new L2CacheException("未实现方法CacheService.getNativeL2cache(),请检查代码");
}
// ---------------------------------------------------
// 第一部分:业务逻辑,业务开发时,仅仅只需实现如下几个方法
// 变化的部分,不同业务实现逻辑不同
// ---------------------------------------------------
String getCacheName();
String buildCacheKey(K key);
R queryData(K key);
Map<K, R> queryDataList(List<K> keyList);
// ---------------------------------------------------
// 第二部分:缓存操作,将缓存操作封装为默认方法实现,简化业务开发
// 不变的部分,下面的方法都是模板方法
// ---------------------------------------------------
default R get(K key) {
return (R) this.getNativeL2cache().getIfPresent(this.buildCacheKey(key));
}
// 省略其他模板方法 ... ...
default Map<K, R> batchGetOrLoad(List<K> keyList) {
return this.getNativeL2cache().batchGetOrLoad(keyList, k -> this.buildCacheKey(k), notHitCacheKeyList -> this.queryDataList(notHitCacheKeyList));
}
default Map<K, R> batchReload(List<K> keyList) {
Map<K, R> value = this.queryDataList(keyList);
this.getNativeL2cache().batchPut(value, k -> this.buildCacheKey(k));
return value;
}
}
// 简略的代码
/**
* 扩展点接口标记
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* 默认扩展名
*/
String value() default "";
}
/**
* 【接口】热key探测
*/
@SPI("sentinel")
public interface HotkeyService extends Serializable {
void init(CacheConfig.Hotkey hotkey, List<String> cacheNameList);
boolean isHotkey(String cacheName, String key);
}
/**
* 【实现策略】京东热key探测
*/
@Slf4j
public class JdHotkeyService implements HotkeyService {
// 省略其他方法...
@Override
public boolean isHotkey(String cacheName, String key) {
StringBuilder sb = new StringBuilder(cacheName).append(CacheConsts.SPLIT).append(key);
return JdHotKeyStore.isHotKey(sb.toString());
}
}
/**
* 【实现策略】sentinel热key探测
*/
@Slf4j
public class SentinelHotkeyService implements HotkeyService {
// 省略其他方法...
@Override
public boolean isHotkey(String cacheName, String key) {
Entry entry = null;
try {
entry = SphU.entry(cacheName, EntryType.IN, 1, key);
return false;// 返回 false 表示不是热key
} catch (BlockException ex) {
if (log.isDebugEnabled()) {
log.debug("sentinel 识别到热key, resource={}, key={}, rule={}", cacheName, key, ex.getRule().toString());
}
return true;// 返回 true 表示热key
} finally {
if (entry != null) {
entry.exit(1, key);
}
}
}
}
/**
* 【】基于SPI机制的热key策略加载
*/
@Slf4j
@Configuration
@ConditionalOnProperty(name = "l2cache.config.hotkey.type")
public class HotKeyConfiguration {
@Autowired
L2CacheProperties l2CacheProperties;
@Autowired
ApplicationContext context;
@PostConstruct
public void init() {
CacheConfig.Hotkey hotKey = l2CacheProperties.getConfig().getHotKey();
if (ObjectUtil.isEmpty(hotKey.getType())) {
log.error("未配置 hotkey type,不进行初始化");
return;
}
HotkeyService hotkeyService = ServiceLoader.load(HotkeyService.class, hotKey.getType());
if (ObjectUtil.isNull(hotkeyService)) {
log.error("非法的 hotkey type,无匹配的HotkeyService实现类, hotkey type={}", hotKey.getType());
return;
}
hotkeyService.init(hotKey, getAllCacheName());
log.info("Hotkey实例初始化成功, hotkey type={}", hotKey.getType());
}
// 省略其他方法...
}
设计模式使代码编写真正工程化,它是软件工程的基石,如同大厦的一块块砖石一样。
每种设计模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,因此在项目中合理地运用设计模式可以完美地解决很多问题,这也是设计模式能被广泛应用的原因。