工作中常用到哪些设计模式
1.策略模式
策略模式的定义与特点
策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下。
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if...else 语句、switch...case 语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
其主要缺点如下。
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类,增加维护难度。
1.1 业务场景
假设有这样的业务场景,大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码:
if(type=="A"){
//按照A格式解析
}else if(type=="B"){
//按B格式解析
}else{
//按照默认格式解析
}
这个代码可能会存在哪些问题呢?
- 如果分支变多,这里的代码就会变得臃肿,难以维护,可读性低。
- 如果你需要接入一种新的解析类型,那只能在原有代码上修改。
说得专业一点的话,就是以上代码,违背了面向对象编程的开闭原则以及单一原则。
-
开闭原则(对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码。
-
单一原则(规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。
如果你的代码就是酱紫:有多个if...else
等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。
1.2 策略模式定义
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。
策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
1.3 策略模式使用
策略模式怎么使用呢?:
一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法)
不同策略的差异化实现(就是说,不同策略的实现类)
使用策略模式
1.3.1 一个接口,两个方法
public interface IFileStrategy {
//属于哪种文件解析类型
FileTypeResolveEnum gainFileType();
//封装的公用算法(具体的解析方法)
void resolve(Object objectparam);
}
1.3.2 不同策略的差异化实现
A 类型策略具体实现
@Component
public class AFileResolve implements IFileStrategy {
@Override
public String getFileType() {
return "AStrategy";
}
@Override
public void resolve(Object objectparam) {
logger.info("A 类型解析文件,参数:{}",objectparam);
//A类型解析具体逻辑
}
}
B 类型策略具体实现
@Component
public class BFileResolve implements IFileStrategy {
@Override
public String getFileType() {
return "BStrategy";
}
@Override
public void resolve(Object objectparam) {
logger.info("B 类型解析文件,参数:{}",objectparam);//b类型解析具体逻辑
}
}
默认类型策略具体实现
@Component
public class DefaultFileResolve implements IFileStrategy {
@Override
public String getFileType() {
return "DefaultStrategy";
}
@Override
public void resolve(Object objectparam) {
logger.info("默认类型解析文件,参数:{}",objectparam);
}
}
1.3.3 使用策略模式
如何使用呢?我们借助spring的生命周期,使用ApplicationContextAware接口,把对用的策略,初始化到map里面。然后对外提供resolveFile方法即可。
/**
* @author 重庆阿汤哥
*/
@Component
public class StrategyUseService implements ApplicationContextAware{
private Map<String, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
public void resolveFile(String strategyType, Object objectParam) {
IFileStrategy iFileStrategy = iFileStrategyMap.get(strategyType);
if (iFileStrategy != null) {
iFileStrategy.resolve(objectParam);
}
}
//把不同策略放到map
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.getFileType(), strategyService));
}
}
1.4用Map+函数式接口来实现策略模式
我想大家肯定都或多或少的看过各种“策略模式”的讲解、布道等等,这篇文章就是来好好“澄清”一下策略模式,并尝试回答以下的问题:
- 策略模式是如何优化业务逻辑代码结构的?
- 杀鸡焉用宰牛刀?就是几个if else场景我需要用到策略模式?!
- 有没有什么更好的代码结构来实现策略模式的吗?
策略模式是如何优化业务逻辑代码结构的?
要回答这个问题,我们还得先扒一扒策略模式的定义,从定义着手来理解它:
策略模式的教科书定义
它的定义很精简:一个类的行为或其算法可以在运行时更改。我们把它降维到代码层面,用人话翻译一下就是,运行时我给你这个类的方法传不同的“key”,你这个方法会执行不同的业务逻辑。细品一下,这不就是 if else 干的事吗?
策略模式优化了什么?
其实策略模式的核心思想和 if else如出一辙,根据不同的key动态的找到不同的业务逻辑,那它就只是如此吗?
实际上,我们口中的策略模式其实就是在代码结构上调整,用接口+实现类+分派逻辑来使代码结构可维护性好点。
接下来代码上需要用到Java8的特性——利用Map与函数式接口来实现。
直接show代码结构:为了简单演示一个思路,代码用String 类型来模拟一个业务BO
其中:
- getCheckResult() 为传统的做法
- getCheckResultSuper()则事先在Map中定义好了“判断条件”与“业务逻辑”的映射关系,具体讲解请看代码注释
/** * 某个业务服务类 */ @Service public class BizService { /** * 传统的 if else 解决方法 * 当每个业务逻辑有 3 4 行时,用传统的策略模式不值得,直接的if else又显得不易读 */ public String getCheckResult(String order) { if ("校验1".equals(order)) { return "执行业务逻辑1"; } else if ("校验2".equals(order)) { return "执行业务逻辑2"; }else if ("校验3".equals(order)) { return "执行业务逻辑3"; }else if ("校验4".equals(order)) { return "执行业务逻辑4"; }else if ("校验5".equals(order)) { return "执行业务逻辑5"; }else if ("校验6".equals(order)) { return "执行业务逻辑6"; }else if ("校验7".equals(order)) { return "执行业务逻辑7"; }else if ("校验8".equals(order)) { return "执行业务逻辑8"; }else if ("校验9".equals(order)) { return "执行业务逻辑9"; } return "不在处理的逻辑中返回业务错误"; } /** * 业务逻辑分派Map * Function为函数式接口,下面代码中 Function<String, String> 的含义是接收一个Stirng类型的变量,返回一个String类型的结果 */ private Map<String, Function<String, String>> checkResultDispatcher = new HashMap<>(); /** * 初始化 业务逻辑分派Map 其中value 存放的是 lambda表达式 */ @PostConstruct public void checkResultDispatcherInit() { checkResultDispatcher.put("校验1", order -> String.format("对%s执行业务逻辑1", order)); checkResultDispatcher.put("校验2", order -> String.format("对%s执行业务逻辑2", order)); checkResultDispatcher.put("校验3", order -> String.format("对%s执行业务逻辑3", order)); checkResultDispatcher.put("校验4", order -> String.format("对%s执行业务逻辑4", order)); checkResultDispatcher.put("校验5", order -> String.format("对%s执行业务逻辑5", order)); checkResultDispatcher.put("校验6", order -> String.format("对%s执行业务逻辑6", order)); checkResultDispatcher.put("校验7", order -> String.format("对%s执行业务逻辑7", order)); checkResultDispatcher.put("校验8", order -> String.format("对%s执行业务逻辑8", order)); checkResultDispatcher.put("校验9", order -> String.format("对%s执行业务逻辑9", order)); } public String getCheckResultSuper(String order) { //从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式 Function<String, String> result = checkResultDispatcher.get(order); if (result != null) { //执行这段表达式获得String类型的结果 return result.apply(order); } return "不在处理的逻辑中返回业务错误"; } }
2. 责任链模式
2.1 业务场景
我们来看一个常见的业务场景,下订单。下订单接口,基本的逻辑,一般有参数非空校验、安全校验、黑名单校验、规则拦截等等。很多伙伴会使用异常来实现:
public class Order {
public void checkNullParam(Object param){
//参数非空校验
throw new RuntimeException();
}
public void checkSecurity(){
//安全校验
throw new RuntimeException();
}
public void checkBackList(){
//黑名单校验
throw new RuntimeException();
}
public void checkRule(){
//规则拦截
throw new RuntimeException();
}
public static void main(String[] args) {
Order order= new Order();
try{
order.checkNullParam();
order.checkSecurity ();
order.checkBackList();
order2.checkRule();
System.out.println("order success");
}catch (Exception e){
System.out.println("order fail");
}
}
}
这段代码使用了异常来做逻辑条件判断,如果后续逻辑越来越复杂的话,会出现一些问题:如异常只能返回异常信息,不能返回更多的字段,这时候需要自定义异常类。
并且,阿里开发手册规定:禁止用异常做逻辑判断。
【强制】 异常不要用来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
如何优化这段代码呢?可以考虑责任链模式
2.2 责任链模式定义
模式的定义与特点
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
注意:责任链模式也叫职责链模式。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式,其主要优点如下。
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
其主要缺点如下。
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式。
责任链模式为请求创建了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递:
2.3 责任链模式使用
职责链模式主要包含以下角色。
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
2.3.1 一个接口或者抽象类
这个接口或者抽象类,需要:
- 有一个指向责任下一个对象的属性
- 一个设置下一个对象的set方法
- 给子类对象差异化实现的方法(如以下代码的doFilter方法)
/**
*
*/
public abstract class AbstractHandler {
//责任链中的下一个对象
private AbstractHandler nextHandler;
/**
* 责任链的下一个对象
*/
public void setNextHandler(AbstractHandler nextHandler){
this.nextHandler = nextHandler;
}
/**
* 具体参数拦截逻辑,给子类去实现
*/
public void filter(Request request, Response response) {
doFilter(request, response);
if (getNextHandler() != null) {
getNextHandler().filter(request, response);
}
}
public AbstractHandler getNextHandler() {
return nextHandler;
}
abstract void doFilter(Request filterRequest, Response response);
}
2.3.2 每个对象差异化处理
责任链上,每个对象的差异化处理,如本小节的业务场景,就有参数校验对象、安全校验对象、黑名单校验对象、规则拦截对象
/**
* 参数校验对象
**/
@Component
@Order(1) //顺序排第1,最先校验
public class CheckParamFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
System.out.println("非空参数检查");
}
}
/**
* 安全校验对象
*/
@Component
@Order(2) //校验顺序排第2
public class CheckSecurityFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//invoke Security check
System.out.println("安全调用校验");
}
}
/**
* 黑名单校验对象
*/
@Component
@Order(3) //校验顺序排第3
public class CheckBlackFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//invoke black list check
System.out.println("校验黑名单");
}
}
/**
* 规则拦截对象
*/
@Component
@Order(4) //校验顺序排第4
public class CheckRuleFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//check rule
System.out.println("check rule");
}
}
2.3.3 对象链连起来(初始化)&& 使用
@Component
public class HandlerDemo {
//自动注入各个责任链的对象
@Autowired
private List<AbstractHandler> abstractHandleList;
private AbstractHandler abstractHandler;
//spring注入后自动执行,责任链的对象连接起来
@PostConstruct
public void initializeChainFilter(){
for(int i = 0;i<abstractHandleList.size();i++){
if(i == 0){
abstractHandler = abstractHandleList.get(0);
}else{
AbstractHandler currentHander = abstractHandleList.get(i - 1);
AbstractHandler nextHander = abstractHandleList.get(i);
currentHander.setNextHandler(nextHander);
}
}
}
//直接调用这个方法使用
public Response exec(Request request, Response response) {
abstractHandler.filter(request, response);
return response;
}
public AbstractHandler getAbstractHandler() {
return abstractHandler;
}
public void setAbstractHandler(AbstractHandler abstractHandler) {
this.abstractHandler = abstractHandler;
}
}
运行结果如下:
非空参数检查
安全调用校验
校验黑名单
check rule
3. 模板方法模式
3.1 业务场景
假设我们有这么一个业务场景:内部系统不同商户,调用我们系统接口,去跟外部第三方系统交互(http方式)。走类似这么一个流程,如下:
一个请求都会经历这几个流程:
- 查询商户信息
- 对请求报文加签
- 发送http请求出去
- 对返回的报文验签
这里,有的商户可能是走代理出去的,有的是走直连。假设当前有A,B商户接入,不少伙伴可能这么实现,伪代码如下:
// 商户A处理句柄
public class CompanyAHandler implements RequestHandler {
Resp hander(req){
//查询商户信息
queryMerchantInfo();
//加签
signature();
//http请求(A商户假设走的是代理)
httpRequestbyProxy()
//验签
verify();
}
}
// 商户B处理句柄
public class CompanyBHandler implements RequestHandler {
Resp hander(Rreq){
//查询商户信息
queryMerchantInfo();
//加签
signature();
// http请求(B商户不走代理,直连)
httpRequestbyDirect();
// 验签
verify();
}
}
假设新加一个C商户接入,你需要再实现一套这样的代码。显然,这样代码就重复了,一些通用的方法,却在每一个子类都重新写了这一方法。
如何优化呢?可以使用模板方法模式。
3.2 模式的定义与特点
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下。
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
3.3 模板方法使用
- 一个抽象类,定义骨架流程(抽象方法放一起)
- 确定的共同方法步骤,放到抽象类(去除抽象方法标记)
- 不确定的步骤,给子类去差异化实现
我们继续那以上的举例的业务流程例子,来一起用 模板方法优化一下哈:
3.3.1 一个抽象类,定义骨架流程
因为一个个请求经过的流程为一下步骤:
- 查询商户信息
- 对请求报文加签
- 发送http请求出去
- 对返回的报文验签
所以我们就可以定义一个抽象类,包含请求流程的几个方法,方法首先都定义为抽象方法哈:
/**
* 抽象类定义骨架流程(查询商户信息,加签,http请求,验签)
*/
public abstract class AbstractMerchantService {
//查询商户信息
abstract queryMerchantInfo();
//加签
abstract signature();
//http 请求
abstract httpRequest();
// 验签
abstract verifySinature();
}
3.3.2 确定的共同方法步骤,放到抽象类
public abstract class AbstractMerchantService {
//模板方法流程
Resp handlerTempPlate(req){
//查询商户信息
queryMerchantInfo();
//加签
signature();
//http 请求
httpRequest();
// 验签
verifySinature();
}
// Http是否走代理(提供给子类实现)
abstract boolean isRequestByProxy();
}
3.3.3 不确定的步骤,给子类去差异化实现
因为是否走代理流程是不确定的,所以给子类去实现。
商户A的请求实现:
CompanyAServiceImpl extends AbstractMerchantService{
Resp hander(req){
return handlerTempPlate(req);
}
//走http代理的
boolean isRequestByProxy(){
return true;
}
商户B的请求实现:
CompanyBServiceImpl extends AbstractMerchantService{
Resp hander(req){
return handlerTempPlate(req);
}
//公司B是不走代理的
boolean isRequestByProxy(){
return false;
}
4. 观察者模式
4.1 业务场景
登陆注册应该是最常见的业务场景了。就拿注册来说事,我们经常会遇到类似的场景,就是用户注册成功后,我们给用户发一条消息,又或者发个邮件等等,因此经常有如下的代码:
public void register(User user){
insertRegisterUser(user);
sendIMMessage();
sendEmail();
}
这块代码会有什么问题呢? 如果产品又加需求:现在注册成功的用户,再给用户发一条短信通知。于是你又得改register方法的代码了。。。这是不是违反了开闭原则啦。
public void register(User user){
insertRegisterUser(user);
sendIMMessage();
sendMobileMessage();
sendEmail();
}
并且,如果调发短信的接口失败了,是不是又影响到用户注册了?!这时候,是不是得加个异步方法给通知消息才好。。。
实际上,我们可以使用观察者模式优化。
4.2 观察者模式定义
定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式属于行为模式,一个对象(被观察者)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。它的主要成员就是观察者和被观察者。
- 被观察者(Observerable):目标对象,状态发生变化时,将通知所有的观察者。
- 观察者(observer):接受被观察者的状态变化通知,执行预先定义的业务。
使用场景: 完成某件事情后,异步通知场景。如,登陆成功,发个IM消息等等。
4.3 观察者模式使用
观察者模式实现的话,还是比较简单的。
- 一个被观察者的类Observerable ;
- 多个观察者Observer ;
- 观察者的差异化实现
- 经典观察者模式封装:EventBus实战
4.3.1 一个被观察者的类Observerable 和 多个观察者Observer
public class Observerable {
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
notifyAllObservers();
}
//添加观察者
public void addServer(Observer observer){
observers.add(observer);
}
//移除观察者
public void removeServer(Observer observer){
observers.remove(observer);
}
//通知
public void notifyAllObservers(int state){
if(state!=1){
System.out.println(“不是通知的状态”);
return ;
}
for (Observer observer : observers) {
observer.doEvent();
}
}
}
4.3.2 观察者的差异化实现
//观察者
interface Observer {
void doEvent();
}
//Im消息
IMMessageObserver implements Observer{
void doEvent(){
System.out.println("发送IM消息");
}
}
//手机短信
MobileNoObserver implements Observer{
void doEvent(){
System.out.println("发送短信消息");
}
}
//EmailNo
EmailObserver implements Observer{
void doEvent(){
System.out.println("发送email消息");
}
}
4.3.3 EventBus实战
自己搞一套观察者模式的代码,还是有点小麻烦。实际上,Guava EventBus就封装好了,它 提供一套基于注解的事件总线,api可以灵活的使用,爽歪歪。
我们来看下EventBus的实战代码哈,首先可以声明一个EventBusCenter类,它类似于以上被观察者那种角色Observerable。
public class EventBusDemo {
private static EventBus eventBus = new EventBus();
private EventBusCenter() {
}
public static EventBus getInstance() {
return eventBus;
}
//添加观察者
public static void register(Object obj) {
eventBus.register(obj);
}
//移除观察者
public static void unregister(Object obj) {
eventBus.unregister(obj);
}
//把消息推给观察者
public static void post(Object obj) {
eventBus.post(obj);
}
}
然后再声明观察者EventListener
public class EventListener {
@Subscribe //加了订阅,这里标记这个方法是事件处理方法
public void handle(NotifyEvent notifyEvent) {
System.out.println("发送IM消息" + notifyEvent.getImNo());
System.out.println("发送短信消息" + notifyEvent.getMobileNo());
System.out.println("发送Email消息" + notifyEvent.getEmailNo());
}
}
//通知事件类
public class NotifyEvent {
private String mobileNo;
private String emailNo;
private String imNo;
public NotifyEvent(String mobileNo, String emailNo, String imNo) {
this.mobileNo = mobileNo;
this.emailNo = emailNo;
this.imNo = imNo;
}
}
public class EventBusDemoTest {
public static void main(String[] args) {
EventListener eventListener = new EventListener();
EventBusDemo.register(eventListener);
EventBusDemo.post(new NotifyEvent("158111111111", "[email protected]", "666"));
}
}
运行结果:
发送IM消息666
发送短信消息158111111111
发送Email消息[email protected]
5. 单例模式
5.1 业务场景
单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
来看一个单例模式的例子
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo (){
}
public static SingletonDemo getInstance(){
if (instance == null) {
instance = new SingletonDemo ();
}
return instance;
}
}
以上的例子,就是懒汉式的单例实现。实例在需要用到的时候,才去创建,就比较懒。如果有则返回,没有则新建,需要加下 synchronized关键字,要不然可能存在线性安全问题。
5.2 单例模式的经典写法
其实单例模式还有有好几种实现方式,如饿汉模式,双重校验锁,静态内部类,枚举等实现方式。
5.2.1 饿汉模式
public class EHanSingleton {
private static EHanSingleton instance = new EHanSingleton();
private EHanSingleton(){
}
public static EHanSingleton getInstance() {
return instance;
}
}
饿汉模式,它比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。
5.2.2 双重校验锁
public class DoubleCheckSingleton {
private static DoubleCheckSingleton instance;
private DoubleCheckSingleton() { }
public static DoubleCheckSingleton getInstance(){
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
双重校验锁实现的单例模式,综合了懒汉式和饿汉式两者的优缺点。以上代码例子中,在synchronized关键字内外都加了一层 if条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
5.2.3 静态内部类
public class InnerClassSingleton {
private static class InnerClassSingletonHolder{
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static final InnerClassSingleton getInstance(){
return InnerClassSingletonHolder.INSTANCE;
}
}
静态内部类的实现方式,效果有点类似双重校验锁。但这种方式只适用于静态域场景,双重校验锁方式可在实例域需要延迟初始化时使用。
5.2.4 枚举
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance(){
return INSTANCE;
}
}
枚举实现的单例,代码简洁清晰。并且它还自动支持序列化机制,绝对防止多次实例化。