飞道的博客

2.单例模式,工厂模式,建造者模式,原型模式

314人阅读  评论(0)

单例模式

单例模式的优点:

  1. 处理资源访问冲突
  2. 表示全局唯一类

实现单例的关键:

  1. 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  2. 考虑对象创建时的线程安全问题;
  3. 考虑是否支持延迟加载;
  4. 考虑 getInstance() 性能是否高(是否加锁)。

1. 饿汉式

  • 在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。
public class IdGenerator {
   
    private AtomicLong id = new AtomicLong(0);
    private static final IdGenerator instance = new IdGenerator();
    private IdGenerator() {
   }
    public static IdGenerator getInstance() {
   
        return instance;
    }
    public long getId() {
   
        return id.incrementAndGet();
    }
}

提前初始化占用资源,但是让耗时的操作提前到启动的时候完成,解决初始化导致的性能问题

2. 懒汉式

public class IdGenerator {
   
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private IdGenerator() {
   }
    public static synchronized IdGenerator getInstance() {
   
        if (instance == null) {
   
            instance = new IdGenerator();
        }
        return instance;
    }
    public long getId() {
   
        return id.incrementAndGet();
    }
}

如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈。

3. 双重检测

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。

public class IdGenerator {
   
    private AtomicLong id = new AtomicLong(0);
    private static volatile IdGenerator instance;

    private IdGenerator() {
   
    }

    public static IdGenerator getInstance() {
   
        if (instance == null) {
   
            synchronized (IdGenerator.class) {
    // 此处为类级别的锁
                if (instance == null) {
   
                    instance = new IdGenerator();
                }
            }
        }
        return instance;
    }

    public long getId() {
   
        return id.incrementAndGet();
    }
}

 

CPU 指令重排序可能导致在 IdGenerator 类的对象被关键字 new 创建并赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了(volatile 来禁止指令重排序)。

4. 静态内部类

有点类似饿汉式,但又能做到了延迟加载

public class IdGenerator {
   
    private AtomicLong id = new AtomicLong(0);

    private IdGenerator() {
   
    }

    private static class SingletonHolder {
   
        private static final IdGenerator instance = new IdGenerator();
    }

    public static IdGenerator getInstance() {
   
        return SingletonHolder.instance;
    }

    public long getId() {
   
        return id.incrementAndGet();
    }
}

 

SingletonHolder 是一个静态内部类,当外部类 IdGenerator 被加载的时候,并不会创建
SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

5. 枚举

通过 Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum IdGenerator {
   
    INSTANCE;
    private AtomicLong id = new AtomicLong(0);
    public long getId() {
   
        return id.incrementAndGet();
    }
}

单例的缺点

  1. 单例对 OOP 特性的支持不友好(一旦你选择将某个类设计成到单例类,也就意味着放弃了继承和多态这两个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性)
  2. 单例会隐藏类之间的依赖关系(单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽。)
  3. 单例对代码的扩展性不友好(单例类只能有一个对象实例,如果需要创建两个实例或
    多个实例,那就要对代码有比较大的改动(比如 想要变成2个线程池,一个是快线程池,一个是慢线程池))
  4. 单例对代码的可测试性不友好
  5. 单例不支持有参数的构造函数

单例的替代方案

  1. 我们还可以用静态方法来实现(比单例更不灵活,不能支持懒加载)
public class IdGenerator {
   
    private static AtomicLong id = new AtomicLong(0);

    public static long getId() {
   
        return id.incrementAndGet();
    }
}
    // 使用举例
    long id = IdGenerator.getId();

2.依赖注入(可以解决单例隐藏类之间依赖关系的问题,但是对 OOP 特性、扩展性、可测性不友好等问题,还是无法解决)

public demofunction(IdGenerator idGenerator){
   
        long id=idGenerator.getId();
        }
// 外部调用demofunction()的时候,传入idGenerator
        IdGenerator idGenerator=IdGenerator.getInsance();
        demofunction(idGenerator);
        }
  1. 我们既可以通过单例模式来强制保证,也可以通过工厂模式、IOC 容器(比如 Spring IOC 容器)来保证,还可以通过程序员自己来保证(自己在编写代码的时候自己保证不要创建两个类对象)

单例类中对象的唯一性的作用范围是进程内的,在进程间是不唯一的。
如果fork一个线程出来,会在新线程也创建一个单例对象

如何实现线程唯一的单例?

  • 通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以
    做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。
  • ThreadLocal就是这个的实现

实现集群间单例

  • 把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。
  • 为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。
public class IdGenerator {
   
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略*/);
    private static DistributedLock lock = new DistributedLock();

    private IdGenerator() {
   
    }

    public synchronized static IdGenerator getInstance() {
   
        if (instance == null) {
   
            lock.lock();
            instance = storage.load(IdGenerator.class);
        }
        return instance;
    }

    public synchronized void freeInstance() {
   
        storage.save(this, IdGeneator.class);
        instance = null; //释放对象
        lock.unlock();
    }

    public long getId() {
   
        return id.incrementAndGet();
    }
}
    // IdGenerator使用举例
    IdGenerator idGeneator = IdGenerator.getInstance();
    long id = idGenerator.getId();

    idGenerator.freeInstance();

 

实现一个多例模式?

public class BackendServer {
   
    private long serverNo;
    private String serverAddress;
    private static final int SERVER_COUNT = 3;
    private static final Map<Long, BackendServer> serverInstances = new HashMap<>()

    static {
   
        serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
        serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
        serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
    }

    private BackendServer(long serverNo, String serverAddress) {
   
        this.serverNo = serverNo;
        this.serverAddress = serverAddress;
    }

    public BackendServer getInstance(long serverNo) {
   
        return serverInstances.get(serverNo);
    }

    public BackendServer getRandomInstance() {
   
        Random r = new Random();
        int no = r.nextInt(SERVER_COUNT) + 1;
        return serverInstances.get(no);
    }
}

 

多例模式创建的对象都是同一个类的对象,而工厂模式创建的是不同子类的对象

工厂模式

简单工厂(Simple Factory)

/**
* 根据不同后缀,选择不同的解析器
*/
public class RuleConfigSource {
   
    public RuleConfig load(String ruleConfigFilePath) {
   
        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
        IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFil
        if (parser == null) {
   
            throw new InvalidRuleConfigException(
                    "Rule config file format is not supported: " + ruleConfigFilePath);
        }
        String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
        RuleConfig ruleConfig = parser.parse(configText);
        return ruleConfig;
    }

    private String getFileExtension(String filePath) {
   
//...解析文件名获取扩展名,比如rule.json,返回json
        return "json";
    }
}

public class RuleConfigParserFactory {
   
    public static IRuleConfigParser createParser(String configFormat) {
   
        IRuleConfigParser parser = null;
        if ("json".equalsIgnoreCase(configFormat)) {
   
            parser = new JsonRuleConfigParser();
        } else if ("xml".equalsIgnoreCase(configFormat)) {
   
            parser = new XmlRuleConfigParser();
        } else if ("yaml".equalsIgnoreCase(configFormat)) {
   
            parser = new YamlRuleConfigParser();
        } else if ("properties".equalsIgnoreCase(configFormat)) {
   
            parser = new PropertiesRuleConfigParser();
        }
        return parser;
    }
}

 

大部分工厂类都是以“Factory”这个单词结尾的,工厂类中创建对象的方法一般都是 create 开头

我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser,所以可以将 parser 事先创建好缓存起来

public class RuleConfigParserFactory {
   
    private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<
    static {
   
        cachedParsers.put("json", new JsonRuleConfigParser());
        cachedParsers.put("xml", new XmlRuleConfigParser());
        cachedParsers.put("yaml", new YamlRuleConfigParser());
        cachedParsers.put("properties", new PropertiesRuleConfigParser());
    }
    public static IRuleConfigParser createParser(String configFormat) {
   
        if (configFormat == null || configFormat.isEmpty()) {
   
            return null;//返回null还是IllegalArgumentException全凭你自己说了算
        }
        IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
        return parser;
    }
}

 

尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权
衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也
没有太多的 parser)是没有问题的。

工厂方法(Factory Method)

可以通过多态避免多的if

public interface IRuleConfigParserFactory {
   
    IRuleConfigParser createParser();
}

public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
   
    @Override
    public IRuleConfigParser createParser() {
   
        return new JsonRuleConfigParser();
    }
}

public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
   
    @Override
    public IRuleConfigParser createParser() {
   
        return new XmlRuleConfigParser();
    }
}

public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
   
    @Override
    public IRuleConfigParser createParser() {
   

        return new YamlRuleConfigParser();
    }
}

public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactor {
   

    @Override
    public IRuleConfigParser createParser() {
   
        return new PropertiesRuleConfigParser();
    }
}

public class RuleConfigParserFactoryMap {
    //工厂的工厂
    private static final Map<String, IRuleConfigParserFactory> cachedFactories = ne

    static {
   
        cachedFactories.put("json", new JsonRuleConfigParserFactory());
        cachedFactories.put("xml", new XmlRuleConfigParserFactory());
        cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
        cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
    }

    public static IRuleConfigParserFactory getParserFactory(String type) {
   
        if (type == null || type.isEmpty()) {
   
            return null;
        }
        IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase
        return parserFactory;
    }
}


 

工厂方法模式比起简单工厂模式更加符合开闭原则。

那什么时候该用工厂方法模式,而非简单工厂模式呢?

当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂。

抽象工厂(Abstract Factory)

在简单工厂和工厂方法中,类只有一种分类方式。如果还用简单工厂或者工厂方法就会创建过多的工厂类。抽象工厂可以让一个工厂负责创建多个不同类型的对象,有效地减少工厂类的个数。

public interface IConfigParserFactory {
   
    IRuleConfigParser createRuleParser();
    ISystemConfigParser createSystemParser();
//此处可以扩展新的parser类型,比如IBizConfigParser
}
public class JsonConfigParserFactory implements IConfigParserFactory {
   
    @Override
    public IRuleConfigParser createRuleParser() {
   
        return new JsonRuleConfigParser();
    }
    @Override
    public ISystemConfigParser createSystemParser() {
   
        return new JsonSystemConfigParser();
    }
}
public class XmlConfigParserFactory implements IConfigParserFactory {
   
    @Override
    public IRuleConfigParser createRuleParser() {
   
        return new XmlRuleConfigParser();
    }
    @Override
    public ISystemConfigParser createSystemParser() {
   
        return new XmlSystemConfigParser();
    }
}

 

工厂模式和 DI 容器有何区别?

一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽象类或者接口的子类)的创建,而 DI 容器负责的是整个应用中所有类对象的创建。

DI 容器的核心功能一般有三个:配置解析、对象创建和对象生命周期管理。

  • 将需要由 DI 容器来创建的类对象和创建类对象的必要信息(使用哪个构造函数以及对应的构造函数参数都是什么等等),放到配置文件中。容器读取配置文件,根据配置文件提供的信息来创建对象。
  • 通过“反射”,它能在程序运行的过程中,动态地加载类、创建对象。
  • 多例或者单例对象,是否懒加载,配置创建和销毁时调用的方法。

建造者

当创建对象要传递很多的参数,容易传递错误的参数,并且影响可读性
使用set方法可以解决创建对象参数列表过多的参数,但是不能解决各个参数设置直接的依赖关系,或者约束条件就很难解决(比如maxIdle要大于minIdle)
如果对象在创建好之后,就不能再修改内部的属性值,不能暴露ResourcePoolConfig 的set方法
这个时候就可以使用建造者模式(把校验逻辑放置到 Builder 类中,然后在使用 build() 方法真正创建对象之前,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限,ResourcePoolConfig 没有提供任何 set() 方法,这样我们创建出来的对象就是不可变对象了)

public class ResourcePoolConfig {
   
    private String name;
    private int maxTotal;
    private int maxIdle;
    private int minIdle;
    private ResourcePoolConfig(Builder builder) {
   
        this.name = builder.name;
        this.maxTotal = builder.maxTotal;
        this.maxIdle = builder.maxIdle;
        this.minIdle = builder.minIdle;
    }
    //...省略getter方法...
//我们将Builder类设计成了ResourcePoolConfig的内部类。
//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
    public static class Builder {
   
        private static final int DEFAULT_MAX_TOTAL = 8;
        private static final int DEFAULT_MAX_IDLE = 8;
        private static final int DEFAULT_MIN_IDLE = 0;
        private String name;
        private int maxTotal = DEFAULT_MAX_TOTAL;
        private int maxIdle = DEFAULT_MAX_IDLE;
        private int minIdle = DEFAULT_MIN_IDLE;
        public ResourcePoolConfig build() {
   
        // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
            if (StringUtils.isBlank(name)) {
   
                throw new IllegalArgumentException("...");
            }

            return new ResourcePoolConfig(this);
        }
        public Builder setName(String name) {
   
            this.name = name;
            return this;
        }
        public Builder setMaxTotal(int maxTotal) {
   
            this.maxTotal = maxTotal;
            return this;
        }
        public Builder setMaxIdle(int maxIdle) {
   
            this.maxIdle = maxIdle;
            return this;
        }
        public Builder setMinIdle(int minIdle) {
   
            this.minIdle = minIdle;
            return this;
        }
    }
}
    // 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
    ResourcePoolConfig config = new ResourcePoolConfig.Builder()
            .setName("dbconnectionpool")
            .setMaxTotal(16)
            .setMaxIdle(10)
            .setMinIdle(12)
            .build();

 

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

原型模式

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。
(如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取)可以使用原型模式。

如果一次有10W条数据需要查询,使用原型模式,不断增加

public class Demo {
   
    private HashMap<String, SearchWord> currentKeywords=new HashMap<>();
    private long lastUpdateTime = -1;
    public void refresh() {
   
// 原型模式就这么简单,拷贝已有对象的数据,更新少量差值
        HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();
// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
        long maxNewUpdatedTime = lastUpdateTime;
        for (SearchWord searchWord : toBeUpdatedSearchWords) {
   
            if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
   
                maxNewUpdatedTime = searchWord.getLastUpdateTime();
            }
            if (newKeywords.containsKey(searchWord.getKeyword())) {
   
                SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());
                oldSearchWord.setCount(searchWord.getCount());
                oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());
            } else {
   
                newKeywords.put(searchWord.getKeyword(), searchWord);
            }
        }
        lastUpdateTime = maxNewUpdatedTime;
        currentKeywords = newKeywords;
    }
    private List<SearchWord> getSearchWords(long lastUpdateTime) {
   
// TODO: 从数据库中取出更新时间>lastUpdateTime的数据
        return null;
    }
}

 

深拷贝和浅拷贝

浅拷贝得到的对象跟原始对象共享数据,而深拷贝得到的是一份完完全全独立的对象

  • 在 Java 语言中,Object 类的 clone() 方法执行的就是我们刚刚说的浅拷贝。它只会拷贝对象中的基本数据类型的数据(比如,int、long),以及引用对象(SearchWord)的内存地址,不会递归地拷贝引用对象本身。(如果使用浅拷贝可能会存在数据不一致的情况)

深拷贝的两种方法

  • 递归拷贝对象、对象的引用对象以及引用对象的引用对象……直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止。
public class Demo {
   
    private HashMap<String, SearchWord> currentKeywords = new HashMap<>();
    private long lastUpdateTime = -1;

    public void refresh() {
   
// Deep copy
        HashMap<String, SearchWord> newKeywords = new HashMap<>();
        for (HashMap.Entry<String, SearchWord> e : currentKeywords.entrySet()) {
   
            SearchWord searchWord = e.getValue();
            SearchWord newSearchWord = new SearchWord(
                    searchWord.getKeyword(), searchWord.getCount(), searchWord.getLastU
                    newKeywords.put(e.getKey(), newSearchWord);
        }
// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
        long maxNewUpdatedTime = lastUpdateTime;
        for (SearchWord searchWord : toBeUpdatedSearchWords) {
   
            if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
   
                maxNewUpdatedTime = searchWord.getLastUpdateTime();
            }
            if (newKeywords.containsKey(searchWord.getKeyword())) {
   
                SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());
                oldSearchWord.setCount(searchWord.getCount());
                oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());
            } else {
   
                newKeywords.put(searchWord.getKeyword(), searchWord);
            }
        }
        lastUpdateTime = maxNewUpdatedTime;
        currentKeywords = newKeywords;
    }

    private List<SearchWord> getSearchWords(long lastUpdateTime) {
   
// TODO: 从数据库中取出更新时间>lastUpdateTime的数据
        return null;
    }
}

 
  • 先将对象序列化,然后再反序列化成新的对象。
    public Object deepCopy(Object object) {
   
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(object);
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        return oi.readObject();
    }

深拷贝都要比浅拷贝耗时、耗内存空间,我们可以先采用浅拷贝的方式创建 newKeywords。对于需要更新的 SearchWord 对象,我们再使用深度拷贝的方式创建一份新的对象,替换 newKeywords 中的老对象。

    public void refresh() {
   
        // Shallow copy
        HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();
        // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
        long maxNewUpdatedTime = lastUpdateTime;
        for (SearchWord searchWord : toBeUpdatedSearchWords) {
   
            if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
   
                maxNewUpdatedTime = searchWord.getLastUpdateTime();
            }
            if (newKeywords.containsKey(searchWord.getKeyword())) {
   
                newKeywords.remove(searchWord.getKeyword());
            }
            newKeywords.put(searchWord.getKeyword(), searchWord);
        }
        lastUpdateTime = maxNewUpdatedTime;
        currentKeywords = newKeywords;
    }

 

如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,


转载:https://blog.csdn.net/lxsxkf/article/details/128779654
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场