设计模式

Posted by 盈盈冲哥 on March 5, 2020

设计模式

  • Spring和JDK设计中用到的设计模式

    https://blog.csdn.net/TK_lTlei/article/details/101599074

    https://blog.csdn.net/wenjieyatou/article/details/80630685

    Spring中用到了那些设计模式

    • 简单工厂模式
    • 工厂方法
    • 单例模式
    • 代理模式
    • 观察者模式

    JDK中的设计模式

    • Singleton(单例) Runtime(https://www.cnblogs.com/fflower/p/10578437.html)
    • Factory(静态工厂) Class.forName
    • Factory Method(工厂方法) Collection.iterator
    • Abstract Factory(抽象工厂) java.sql
    • Prototype(原型) Object.clone
    • Adapter(适配器) java.io.InputStreamReader(InputStream)
    • Proxy(代理) 动态代理
    • Iterator(迭代器) Iterator
    • Observer(观察者) Swing中的Listener
    • Command(命令) Runnable
  • Spring 框架中用到了哪些设计模式?

    • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
    • 代理设计模式 : Spring AOP 功能的实现。
    • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
    • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
    • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
    • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
    • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
    • ……
  • 单例模式

    https://blog.csdn.net/qq_34337272/article/details/80455972

    • 单例模式简介

      为什么要用单例模式呢?

      在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。

      简单来说使用单例模式可以带来下面几个好处:

      对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销; 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

      为什么不使用全局变量确保一个类只有一个实例呢?

      我们知道全局变量分为静态变量和实例变量,静态变量也可以保证该类的实例只存在一个。

      只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。

      但是,如果说这个对象非常消耗资源,而且程序某次的执行中一直没用,这样就造成了资源的浪费。利用单例模式的话,我们就可以实现在需要使用时才创建对象,这样就避免了不必要的资源浪费。 不仅仅是因为这个原因,在程序中我们要尽量避免全局变量的使用,大量使用全局变量给程序的调试、维护等带来困难。

    • 单例的模式的实现

      通常单例模式在Java语言中,有两种构建方式:

      • 饿汉方式。指全局的单例实例在类装载时构建。
      • 懒汉方式。指全局的单例实例在第一次被使用时构建。

      不管是那种创建方式,它们通常都存在下面几点相似处:

      • 单例类必须要有一个 private 访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化;
      • uniqueInstance 成员变量和 getInstance 方法必须是 static 的。

      饿汉方式(线程安全)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      public class Singleton {
          //在静态初始化器中创建单例实例,这段代码保证了线程安全
          private  Singleton uniqueInstance = new Singleton();
          //Singleton类只有一个构造方法并且是被private修饰的,所以用户无法通过new方法创建该对象实例
          private Singleton(){}
          public static Singleton getInstance(){
              return uniqueInstance;
          }
      }
      

      所谓 “饿汉方式” 就是说JVM在加载这个类时就马上创建此唯一的单例实例,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间。

      懒汉式(非线程安全和synchronized关键字线程安全版本 )

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
      public class Singleton {  
          private static Singleton uniqueInstance;  
          private Singleton (){}   
          //没有加入synchronized关键字的版本是线程不安全的
          public static Singleton getInstance() {
              //判断当前单例是否已经存在,若存在则返回,不存在则再建立单例
              if (uniqueInstance == null) {  
                  uniqueInstance = new Singleton();  
              }  
              return uniqueInstance;  
          }  
      }
      

      所谓 “ 懒汉式” 就是说单例实例在第一次被使用时构建,而不是在JVM在加载这个类时就马上创建此唯一的单例实例。

      但是上面这种方式很明显是线程不安全的,如果多个线程同时访问getInstance()方法时就会出现问题。如果想要保证线程安全,一种比较常见的方式就是在getInstance() 方法前加上synchronized关键字,如下:

      1
      2
      3
      4
      5
      6
      
      public static synchronized Singleton getInstance() {  
          if (instance == null) {  
              uniqueInstance = new Singleton();  
          }  
          return uniqueInstance;  
      } 
      

      我们知道synchronized关键字偏重量级锁。虽然在JavaSE1.6之后synchronized关键字进行了主要包括:为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升。

      但是在程序中每次使用getInstance() 都要经过synchronized加锁这一层,这难免会增加getInstance()的方法的时间消费,而且还可能会发生阻塞。我们下面介绍到的 双重检查加锁版本 就是为了解决这个问题而存在的。

      懒汉式(双重检查加锁版本)

      利用双重检查加锁(double-checked locking),首先检查是否实例已经创建,如果尚未创建,“才”进行同步。这样以来,只有一次同步,这正是我们想要的效果。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      public class Singleton {
          //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
          private volatile static Singleton uniqueInstance;
          private Singleton() {}
          public static Singleton getInstance() {
              //检查实例,如果不存在,就进入同步代码块
              if (uniqueInstance == null) {
                  //只有第一次才彻底执行这里的代码
                  synchronized(Singleton.class) {
                      //进入同步代码块后,再检查一次,如果仍是null,才创建实例
                      if (uniqueInstance == null) {
                          uniqueInstance = new Singleton();
                      }
                  }
              }
              return uniqueInstance;
          }
      }
      

      很明显,这种方式相比于使用synchronized关键字的方法,可以大大减少getInstance() 的时间消费。

      我们上面使用到了volatile关键字来保证数据的可见性。

      懒汉式(登记式/静态内部类方式)

      静态内部实现的单例是懒加载的且线程安全。

      只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      public class Singleton {  
          private static class SingletonHolder {  
              private static final Singleton INSTANCE = new Singleton();  
          }  
          private Singleton (){}  
          public static final Singleton getInstance() {  
              return SingletonHolder.INSTANCE;  
          }  
      }
      

      饿汉式(枚举方式)

      这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象,关于单例与序列化的问题可以查看这一篇文章《单例与序列化的那些事儿》),同时这种方式也是《Effective Java 》以及《Java与模式》的作者推荐的方式。

      1
      2
      3
      4
      5
      6
      7
      8
      
      public enum Singleton {
          //定义一个枚举的元素,它就是 Singleton 的一个实例
          INSTANCE;  
              
          public void doSomeThing() {  
            System.out.println("枚举方法实现单例");
          }  
      }
      

      使用方法:

      1
      2
      3
      4
      5
      6
      
      public class ESTest {
          public static void main(String[] args) {
              Singleton singleton = Singleton.INSTANCE;
              singleton.doSomeThing();//output:枚举方法实现单例
          }
      }
      

      这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 —-《Effective Java 中文版 第二版》

  • 工厂模式

    https://blog.csdn.net/qq_34337272/article/details/80472071

    • 工厂模式介绍

      工厂模式的定义

      在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。

      工厂模式的分类

      (1)简单工厂(Simple Factory)模式,又称静态工厂方法模式(Static Factory Method Pattern)。

      (2)工厂方法(Factory Method)模式,又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式;

      (3)抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式。

      工厂模式的例子

      (1) Spring中通过getBean(“xxx”)获取Bean;

      (2) Java消息服务JMS中(下面以消息队列ActiveMQ为例子)

      1
      
      ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.155:61616");
      

      为什么要用工厂模式

      (1) 解耦 :把对象的创建和使用的过程分开

      (2) 降低代码重复: 如果创建某个对象的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。

      (3) 降低维护成本 :由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建对象的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。

    • 简单工厂模式

      严格的说,简单工厂模式并不是23种常用的设计模式之一,它只算工厂模式的一个特殊实现。简单工厂模式在实际中的应用相对于其他2个工厂模式用的还是相对少得多,因为它只适应很多简单的情况。

      最重要的是它违背了我们在概述中说的 开放-封闭原则 (虽然可以通过反射的机制来避免,后面我们会介绍到) 。因为每次你要新添加一个功能,都需要在生switch-case 语句(或者if-else 语句)中去修改代码,添加分支条件。

    • 工厂方法模式

      工厂方法模式应该是在工厂模式家族中是用的最多模式,一般项目中存在最多的就是这个模式。

      工厂方法模式是简单工厂的进一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。 也就是说 每个对象都有一个与之对应的工厂 。

    • 抽象工厂模式

      在工厂方法模式中,其实我们有一个潜在意识的意识。那就是我们生产的都是同一类产品。抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一种产品,而是可以创建一组产品。

      抽象工厂是生产一整套有产品的(至少要生产两个产品),这些产品必须相互是有关系或有依赖的,而工厂方法中的工厂是生产单一产品的工厂。

  • 代理模式

    https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html

  • 观察者模式

    https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html

六边形架构(端口与适配器)

https://insights.thoughtworks.cn/from-sandwich-to-hexagon/

https://www.infoq.cn/article/7QgXyp4Jh3-5Pk6LydWw

https://www.jianshu.com/p/a775836c7e25

六边形每条不同的边代表了不同类型的端口,通过“端口”跟外部进行交互。通过适配器调用应用程序和领域模型。

DDD(Domain-driven Design - 领域驱动设计)

设计思想–开发模式

  • 《浅谈我对DDD领域驱动设计的理解》

    • 概念:DDD 主要对传统软件开发流程(分析-设计-编码)中各阶段的割裂问题而提出,避免由于一开始分析不明或在软件开发过程中的信息流转不一致而造成软件无法交付(和需求方设想不一致)的问题。DDD 强调一切以领域(Domain)为中心,强调领域专家(Domain Expert)的作用,强调先定义好领域模型之后在进行开发,并且领域模型可以指导开发(所谓的驱动)。
    • 过程:理解领域、拆分领域、细化领域,模型的准确性取决于模型的理解深度。
    • 设计:DDD 中提出了建模工具,比如聚合、实体、值对象、工厂、仓储、领域服务、领域事件来帮助领域建模。
  • 《领域驱动设计的基础知识总结》

    • 领域(Doamin)本质上就是问题域,比如一个电商系统,一个论坛系统等。
    • 界限上下文(Bounded Context):阐述子域之间的关系,可以简单理解成一个子系统或组件模块。
    • 领域模型(Domain Model):DDD的核心是建立(用通用描述语言、工具—领域通用语言)正确的领域模型;反应业务需求的- 本质,包括实体和过程;其贯穿软件分析、设计、开发 的整个过程;常用表达领域模型的方式:图、代码或文字;
    • 领域通用语言:领域专家、开发设计人员都能立即的语言或工具。
    • 经典分层架构:用户界面/展示层、应用层、领域层、基础设施层,是四层架构模式。
    • 使用的模式:
      • 关联尽量少,尽量单项,尽量降低整体复杂度。
      • 实体(Entity):领域中的唯一标示,一个实体的属性尽量少,少则清晰。
      • 值对象(Value Object):没有唯一标识,且属性值不可变,小二简单的对象,比如Date。
      • 领域服务(Domain Service): 协调多个领域对象,只有方法没有状态(不存数据);可以分为应用层服务,领域层服务、基础层服务。
      • 聚合及聚合根(Aggregate,Aggregate Root):聚合定义了一组具有内聚关系的相关对象的集合;聚合根是对聚合引用的唯一元素;当修改一个聚合时,必须在事务级别;大部分领域模型中,有70%的聚合通常只有一个实体,30%只有2~3个实体;如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,那么我们可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互;
      • 工厂(Factory):类似于设计模式中的工厂模式。
      • 仓储(Repository):持久化到DB,管理对象,且只对聚合设计仓储。
  • 《领域驱动设计(DDD)实现之路》

    • 聚合:比如一辆汽车(Car)包含了引擎(Engine)、车轮(Wheel)和油箱(Tank)等组件,缺一不可。
  • 《领域驱动设计系列(2)浅析VO、DTO、DO、PO的概念、区别和用处》

    VO、DTO、DO、PO

    领域模型中的实体类可细分为4种类型:VO、DTO、DO、PO。

    • VO(View Ob-ject):视图对象,用于展示层视图状态对应的对象
    • DTO(Data Transfer Object):数据传输对象,原来的目的是为EJB的分布式应用提供粗粒度的数据实体,以降低分布式调用的次数,提高分布式调用的性能,后来一般泛指用于展示层与服务层之间的数据传输对象,因此可以将DTO看成一个组合版的DO
    • DO(Domain Object):领域对象,即业务实体对象
    • PO(Persistent Object):持久化对象,表示持久层的数据结构(如数据库表)

    从分层角度来说,PO、DO/DTO、VO分别属于持久层、服务层和展现层。

    对于简单模块来说,有时PO、DO和VO并没有什么区别,这时就没有必要分别定义DO和VO了,直接复用PO即可。