栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

常见设计模式的原理、应用场景总结——创建型

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

常见设计模式的原理、应用场景总结——创建型


经典设计模式共分为 3 种类型,分别是创建型、结构型和行为型。今天,我们先看下创建型设计模式的原理、实现、设计意图和应用场景。

创建型设计模式

创建型设计模式包括:单例模式、工厂模式、建造者模式、原型模式。它主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。

1. 单例模式

单例模式用来创建全局唯一的对象。一个类只允许创建一个对象(或者叫实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。单例有几种经典的实现方式,它们分别 是:饿汉式、懒汉式、双重检测、静态内部类、枚举。

尽管单例是一个很常用的设计模式,在实际的开发中,我们也确实经常用到它,但是,有些 人认为单例是一种反模式(anti-pattern),并不推荐使用,主要的理由有以下几点:

  • 单例对 OOP 特性的支持不友好
  • 单例会隐藏类之间的依赖关系
  • 单例对代码的扩展性不友好
  • 单例对代码的可测试性不友好
  • 单例不支持有参数的构造函数

那有什么替代单例的解决方案呢?如果要完全解决这些问题,我们可能要从根上寻找其他方 式来实现全局唯一类。比如,通过工厂模式、IOC 容器来保证全局唯一性。有人把单例当作反模式,主张杜绝在项目中使用。我个人觉得这有点极端。模式本身没有对 错,关键看你怎么用。如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。对于一些全局类,我们在其他地方 new 的话,还要在类之间传来传去,不如直接做成单例类,使用起来简洁方便。
除此之外,单例有进程唯一单例、线程唯一单例、集群唯一单例、多例等扩展,这一部分在实际的开发中并不会被用到,但是可以扩展你的思路、锻炼你的逻辑思维。

示例

//双重检测
//没加volatile 是因为高版本的 Java 已经在 JDK 内部实现中解决了这个问题 
//(解决的方法很简单,只要把对象 new 操作和初始化操作 设计为原子操作,就自然能禁止重排序)
public class IdGenerator {
    private static IdGenerator instance;
    private AtomicLong id = new AtomicLong(0);

    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();
    }
}
//静态内部类
 public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);

    private IdGenerator() {
    }

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

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

2. 工厂模式

工厂模式包括简单工厂、工厂方法、抽象工厂这 3 种细分模式。其中,简单工厂和工厂方
法比较常用,抽象工厂的应用场景比较特殊,所以很少用到,不是我们学习的重点。
工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。实际上,如果创建对象的逻辑并不复杂,那我们直接通 过 new 来创建对象就可以了,不需要使用工厂模式。当创建逻辑比较复杂,是一个“大工程”的时候,我们就考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。
当每个对象的创建逻辑都比较简单的时候,我推荐使用简单工厂模式,将多个对象的创建逻 辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的工厂类,我们推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。详细点说,工厂模式的作用有下面 4 个,这也是判断要不要使用工厂模式最本质的参考标准。

  • 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
  • 代码复用:创建代码抽离到独立的工厂类之后可以复用。
  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

除此之外,工厂模式一个非常经典的应用场景:依赖注入框架,比如 Spring IOC、Google Guice,它用来集中创建、组装、管理对象,跟具体业务代码解耦,让程序员聚焦在业务代码的开发上。

示例

//简单工厂
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;
    }
}

//工厂方法
public interface IRuleConfigParserFactory {
    IRuleConfigParser createParser();
}


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


public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
    @Override
    public IRuleConfigParser createParser() {
        return new YamlRuleConfigParser();
    }
}
3. 建造者模式

建造者模式用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。建造者模式的原理和实现比较简单,重点是掌握应用场景,避免过度使用。如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用 性,我们可以通过构造函数配合 set() 方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。

  • 我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有 很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的 问题。如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻 辑就无处安放了。
  • 如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方 法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。
  • 如果我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属 性值,要实现这个功能,我们就不能在类中暴露 set() 方法。构造函数配合 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;
    }

    //我们将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("...");
            }

            if (maxIdle > maxTotal) {
                throw new IllegalArgumentException("...");
            }

            if ((minIdle > maxTotal) || (minIdle > maxIdle)) {
                throw new IllegalArgumentException("...");
            }

            return new ResourcePoolConfig(this);
        }

        public Builder setName(String name) {
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }

            this.name = name;

            return this;
        }

        public Builder setMaxTotal(int maxTotal) {
            if (maxTotal <= 0) {
                throw new IllegalArgumentException("...");
            }

            this.maxTotal = maxTotal;

            return this;
        }

        public Builder setMaxIdle(int maxIdle) {
            if (maxIdle < 0) {
                throw new IllegalArgumentException("...");
            }

            this.maxIdle = maxIdle;

            return this;
        }

        public Builder setMinIdle(int minIdle) {
            if (minIdle < 0) {
                throw new IllegalArgumentException("...");
            }

            this.minIdle = minIdle;

            return this;
        }
    }
}
4. 原型模式

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同), 在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型模式。
原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝只会复制对象中基本数据类型数据和引 用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象…而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。
如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了。除非操作非常耗时,比较推荐使用浅拷贝,否则,没有充分的理由,不要为了一点点的性能提升而使用浅拷贝。

示例

public class Demo {
 private HashMap currentKeywords=new HashMap<>();
 private long lastUpdateTime = -1;
 public void refresh() {
 // Shallow copy
 HashMap newKeywords = currentKeywords;
 // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中 
 List 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; 
}
 private List getSearchWords(long lastUpdateTime) {
  return null;
  }
}

好了,常见的创建型设计模式就总结道这里了,希望对你有所帮组。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/328103.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号