Java开源框架中的设计模式以及应用场景

本文详细介绍了Java开源框架中常见的设计模式,包括单例模式、工厂模式、模板方法模式、构造器模式、适配器模式、责任链模式、策略模式、代理模式、装饰器模式、观察者模式和命令模式,并给出了各个模式在实际框架中的应用场景,帮助读者理解如何在实际开发中运用这些设计模式。
摘要由CSDN通过智能技术生成

前言

设计模式是软件设计中常见问题的典型解决方案,你可以通过对其进行定制来解决代码中的特定设计问题。

关于设计模式,网上有很多讲解。但大部分都是Demo示例,看完有可能还是不知道怎么用。

本文笔者将从设计模式入手,看一看在优秀的Java框架/中间件产品中,不同的设计模式应用场景在哪里。

一、单例模式

单例模式是Java中最简单的设计模式之一,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式虽然很简单,但它的花样一点都不少,我们一一来看。

1、饿汉式

饿汉式,顾名思义,就是我很饿,迫不及待。不管有没有人用,我先创建了再说。

比如在Dubbo中的这段代码,创建一个配置管理器。

public class ConfigManager {
private static final ConfigManager configManager = new ConfigManager(); 
private ConfigManager() {}
public static ConfigManager getInstance() {
    return configManager;
}
} 


又或者在RocketMQ中,创建一个MQ客户端实例的时候。

public class MQClientManager {

private static MQClientManager instance = new MQClientManager();
private MQClientManager() {}
public static MQClientManager getInstance() {
    return instance;
}
} 

 

2、懒汉式

懒汉式是对应饿汉式而言的。它旨在第一次调用才初始化,避免内存浪费。但为了线程安全和性能,一般都会使用双重检查锁的方式来创建。

我们来看Seata框架中,通过这种方式来创建一个配置类。

public class ConfigurationFactory{

private static volatile Configuration CONFIG_INSTANCE = null;
public static Configuration getInstance() {
    if (CONFIG_INSTANCE == null) {
        synchronized (Configuration.class) {
            if (CONFIG_INSTANCE == null) {
                CONFIG_INSTANCE = buildConfiguration();
            }
        }
    }
    return CONFIG_INSTANCE;
}
} 

 

3、静态内部类

可以看到,通过双重检查锁的方式来创建单例对象,还是比较复杂的。又是加锁,又是判断两次,还需要加volatile修饰的。

使用静态内部类的方式,可以达到双重检查锁相同的功效,但实现上简单了。

在Seata框架中,创建RM事件处理程序器的时候,就使用了静态内部类的方式来创建单例对象。

public class DefaultRMHandler extends AbstractRMHandler{

protected DefaultRMHandler() {
    initRMHandlers();
}
private static class SingletonHolder {
    private static AbstractRMHandler INSTANCE = new DefaultRMHandler();
}
public static AbstractRMHandler get() {
    return DefaultRMHandler.SingletonHolder.INSTANCE;
}
} 


还有可以通过枚举的方式来创建单例对象,但这种方式并没有被广泛采用,至少笔者在常见的开源框架中没见过,所以就不再列举。

有人说,饿汉式的单例模式不好,不能做到延迟加载,浪费内存。但笔者认为似乎过于吹毛求疵,事实上很多开源框架中,用的最多的就是这种方式。

如果明确希望实现懒加载效果时,可以考虑用静态内部类的方式;如果还有其他特殊的需求,比如创建对象的过程比较繁琐,可以用双重检查锁的方式。

二、工厂模式

工厂模式是Java中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

简单来说,在工厂模式中,就是代替new实例化具体类的一种模式。

1、简单工厂

简单工厂,的确比较简单,它的作用就是把对象的创建放到一个工厂类中,通过参数来创建不同的对象。

在分布式事务框架Seata中,如果发生异常,则需要进行二阶段回滚。

它的过程是,通过事务id找到undoLog记录,然后解析里面的数据生成SQL,将一阶段执行的SQL给撤销掉。

问题是SQL的种类包含了比如INSERT、UPDATE、DELETE,所以它们反解析的过程也不一样,就需要不同的执行器去解析。

在Seata中,有一个抽象的撤销执行器,可以生成一条SQL。

public abstract class AbstractUndoExecutor{
//生成撤销SQL
protected abstract String buildUndoSQL();
} 


然后有一个获取撤销执行器的工厂,根据SQL的类型,创建不同类型的执行器并返回。

public class UndoExecutorFactory {

public static AbstractUndoExecutor getUndoExecutor(String dbType, SQLUndoLog sqlUndoLog) {
    switch (sqlUndoLog.getSqlType()) {
        case INSERT:
            return new MySQLUndoInsertExecutor(sqlUndoLog);
        case UPDATE:
            return new MySQLUndoUpdateExecutor(sqlUndoLog);
        case DELETE:
            return new MySQLUndoDeleteExecutor(sqlUndoLog);
        default:
            throw new ShouldNeverHappenException();
    }
}
} 


使用的时候,直接通过工厂类获取执行器。

AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(),sqlUndoLog);
undoExecutor.executeOn(conn);


简单工厂模式的优点,想必各位都能领会,我们不再赘述。但它还有个小小的缺点:

一旦有了新的实现类,就需要修改工厂实现,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。

2、工厂方法

工厂方法模式解决了上面那个问题。它可以创建一个工厂接口和多个工厂实现类,这样如果增加新的功能,只需要添加新的工厂类就可以,不需要修改之前的代码。

另外,工厂方法模式还可以和模板方法模式结合一起,将他们共同的基础逻辑抽取到父类中,其它的交给子类去实现。

在Dubbo中,有一个关于缓存的设计完美的体现了工厂方法模式+模板方法模式。

首先,有一个缓存的接口,它提供了设置缓存和获取缓存两个方法。

public interface Cache {
void put(Object key, Object value);
Object get(Object key);
} 


然后呢,还有一个缓存工厂,它返回一个缓存的实现。

public interface CacheFactory {
Cache getCache(URL url, Invocation invocation);
} 


由于结合了模板方法模式,所以Dubbo又搞了个抽象的缓存工厂类,它实现了缓存工厂的接口。

public abstract class AbstractCacheFactory implements CacheFactory {

//具体的缓存实现类
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

@Override
public Cache getCache(URL url, Invocation invocation) {
    url = url.addParameter(Constants.METHOD_KEY, invocation.getMethodName());
    String key = url.toFullString();
    Cache cache = caches.get(key);
    if (cache == null) {
        //创建缓存实现类,交给子类实现
        caches.put(key, createCache(url));
        cache = caches.get(key);
    }
    return cache;
}
//抽象方法,交给子类实现
protected abstract Cache createCache(URL url);
} 


在这里,公共的逻辑就是通过getCahce()创建缓存实现类,那具体创建什么样的缓存实现类,就由子类去决定。

所以,每个子类都是一个个具体的缓存工厂类,比如包括:

ExpiringCacheFactory、JCacheFactory、LruCacheFactory、ThreadLocalCacheFactory。
这些工厂类,只有一个方法,就是创建具体的缓存实现类。
public class ThreadLocalCacheFactory extends AbstractCacheFactory {
@Override
protected Cache createCache(URL url) {
    return new ThreadLocalCache(url);
}
} 


这里的ThreadLocalCache就是具体的缓存实现类,比如它是通过ThreadLocal来实现缓存功能。

public class ThreadLocalCache implements Cache {

private final ThreadLocal<Map<Object, Object>> store;

public ThreadLocalCache(URL url) {
    this.store = new ThreadLocal<Map<Object, Object>>() {
        @Override
        protected Map<Object, Object> initialValue() {
            return new HashMap<Object, Object>();
        }
    };
}
@Override
public void put(Object key, Object value) {
    store.get().put(key, value);
}
@Override
public Object get(Object key) {
    return store.get().get(key);
}
} 


那在客户端使用的时候,还是通过工厂来获取缓存对象。

public static void main(String[] args) {
URL url = URL.valueOf("http://localhost:8080/cache=jacache&.cache.write.expire=1");
Invocation invocation = new RpcInvocation();
CacheFactory cacheFactory = new ThreadLocalCacheFactory();
Cache cache = cacheFactory.getCache(url, invocation);
cache.put("java","java");
System.out.println(cache.get("java"));
} 


这样做的好处有两点。

第一,如果增加新的缓存实现,只要添加一个新的缓存工厂类就可以,别的都无需改动。

第二,通过模板方法模式,封装不变部分,扩展可变部分。提取公共代码,便于维护。

另外,在Dubbo中,注册中心的获取也是通过工厂方法来实现的。

3、抽象工厂

抽象工厂模式,它能创建一系列相关的对象,而无需指定其具体类。

工厂方法模式和抽象工厂模式,它们之间最大的区别在于:

  • 工厂方法模式只有一个抽象产品类,具体工厂类只能创建一个具体产品类的实例;
  • 抽象工厂模式有多个抽象产品类,具体工厂类可以创建多个具体产品类的实例。


我们拿上面缓存的例子来继续往下说。

如果我们现在有一个数据访问程序,需要同时操作缓存和数据库,那就需要多个抽象产品和多个具体产品实现。

缓存相关的产品类都已经有了,我们接着来创建数据库相关的产品实现。

首先,有一个数据库接口,它是抽象产品类。

public interface DataBase {
void insert(Object tableName, Object record);
Object select(Object tableName);
} 


然后,我们创建两个具体产品类MysqlDataBase和OracleDataBase。

public class MysqlDataBase implements DataBase{
Map<Object,Object> mysqlDb = new HashMap<>();
@Override
public void insert(Object tableName, Object record) {
    mysqlDb.put(tableName,record);
}
@Override
public Object select(Object tableName) {
    return mysqlDb.get(tableName);
}
}

public class OracleDataBase implements DataBase {
Map<Object,Object> oracleDb = new HashMap<>();
@Override
public void insert(Object tableName, Object record) {
    oracleDb.put(tableName,record)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值