java设计模式应用场景常见哪些?

TheDisguiser 2020-08-21 17:24:00 java常见问答 11491

每个设计模式都有它适合的应用场景,小伙伴们知道有哪些吗?下面就跟着小编一起来看看吧。

单例模式

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

虽说单例模式很简单,但它的各种花样却是不比其他模式少的,我们一一来看。

1、饿汉式

饿汉式,看名字相信小伙伴们就懂了,就是我很饿,非常急。不管有没有人用,我先创建了再说。

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

public class ConfigManager
{
    private static final ConfigManager configManager = new ConfigManager;
    private ConfigManager
    {}
    public static ConfigManager getInstance
    {
        returnconfigManager;
    }
}
又或者在RocketMQ中, 创建一个MQ客户端实例的时候。
public class MQClientManager
{
    private static MQClientManager instance = new MQClientManager;
    private MQClientManager
    {}
    public static MQClientManager getInstance
    {
        returninstance;
    }
}

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;
                }
            }
        }
        returnCONFIG_INSTANCE;
    }
}

3、静态内部类

使用静态内部类的方式,我们也能够达到双重检查锁相同的功效。

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

public class DefaultRMHandler extends AbstractRMHandler
{
    protected DefaultRMHandler
    {
        initRMHandlers;
    }
    private static class SingletonHolder
    {
        private static AbstractRMHandler INSTANCE = new DefaultRMHandler;
    }
    public static AbstractRMHandler get
    {
        returnDefaultRMHandler.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)
        {
            caseINSERT:
                returnnew MySQLUndoInsertExecutor(sqlUndoLog);
            caseUPDATE:
                returnnew MySQLUndoUpdateExecutor(sqlUndoLog);
            caseDELETE:
                returnnew 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 ConcurrentMapcaches = new
    ConcurrentHashMap;
    @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);
        }
        returncache;
    }
    //抽象方法,交给子类实现
    protected abstract Cache createCache(URL url);
}
在这里, 公共的逻辑就是通过getCahce创建缓存实现类, 那具体创建什么样的缓存实现类, 就由子类去决定。
所以, 每个子类都是一个个具体的缓存工厂类, 比如包括:
ExpiringCacheFactory、 JCacheFactory、 LruCacheFactory、 ThreadLocalCacheFactory。
这些工厂类, 只有一个方法, 就是创建具体的缓存实现类。
public class ThreadLocalCacheFactory extends AbstractCacheFactory
{
    @Override
    protected Cache createCache(URL url)
    {
        returnnew ThreadLocalCache(url);
    }
}
这里的ThreadLocalCache就是具体的缓存实现类, 比如它是通过ThreadLocal来实现缓存功能。
public class ThreadLocalCache implements Cache
{
    private final ThreadLocal < map > store;
    public ThreadLocalCache(URL url)
    {
        this.store = new ThreadLocal < map >
        {
            @Override
            protected MapinitialValue
            {
                returnnew HashMap;
            }
        };
    }
    @Override
    public void put(Object key, Object value)
    {
        store.get.put(key, value);
    }
    @Override
    public Object get(Object key)
    {
        returnstore.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
{
    MapmysqlDb = new HashMap < > ;
    @Override
    public void insert(Object tableName, Object record)
    {
        mysqlDb.put(tableName, record);
    }
    @Override
    public Object select(Object tableName)
    {
        returnmysqlDb.get(tableName);
    }
}
public class OracleDataBase implements DataBase
{
    MaporacleDb = new HashMap < > ;
    @Override
    public void insert(Object tableName, Object record)
    {
        oracleDb.put(tableName, record);
    }
    @Override
    public Object select(Object tableName)
    {
        returnoracleDb.get(tableName);
    }
}
其次, 创建抽象的工厂类, 它可以返回一个缓存对象和数据库对象。
public interface DataAccessFactory
{
    Cache getCache(URL url);
    DataBase getDb;
}
最后是具体的工厂类, 可以根据实际的需求, 任意组合每一个具体的产品。
比如我们需要一个基于ThreadLocal的缓存实现和基于MySQL的数据库对象。
public class DataAccessFactory1 implements DataAccessFactory
{
    @Override
    public Cache getCache(URL url)
    {
        returnnew ThreadLocalCache(url);
    }
    @Override
    public DataBase getDb
    {
        returnnew MysqlDataBase;
    }
}
如果需要一个基于Lru的缓存实现和基于Oracle的数据库对象。
public class DataAccessFactory2 implements DataAccessFactory
{
    @Override
    public Cache getCache(URL url)
    {
        returnnew LruCache(url);
    }
    @Override
    public DataBase getDb
    {
        returnnew OracleDataBase;
    }
}

我们能够看到,抽象工厂模式是隔离了具体类的生成的,使得客户并不需要知道什么被创建。因为这种隔离,更换一个具体工厂就变得相对较容易点,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。

以上就是今天的所有内容,还有其他java常见问题需要解决的小伙伴可以关注我们寻找答案。

推荐阅读:

MVC设计模式是什么?MVC框架是什么?

微服务架构设计模式有哪些?有什么好处?

java多线程设计模式怎么实现?