Java设计模式

alt

学习《设计模式-可复用面向对象软件的基础》一书,这本书用Smalltalk语言描述,这门古老的编程语言是现代面向对象语言的鼻祖,但现在生产中几乎没人使用。.类图画的很好。翻译得很一般。
建议根据开源项目源码学习设计模式,在见到***Builder、***Factory之类的源码后,再临时看相关设计模式,便于结合实际理解。如果为了找工作速通的话,本文还不够完善。
策略模式和发布订阅模式提到了一对多的消息通知问题,,用了引用,回头再看看这部分

前言

设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,而应实现一个热插拔的效果。这样使得程序易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类(对应了依赖倒转原则)。

2、里氏代换原则(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现。

里氏代换原则是面向对象设计的基本原则之一。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

不让用继承,直接秒了doge 一些规范不推荐使用继承,用组合也可以实现同样的复用效果。即使用继承,也不推荐从具体类继承,因为具体类有相关业务,不容易LSP吧

里氏代换原则:继承: B一定是A时,B extends A。

在 Java 库中有许多明显违反这一原则的地方。例如,stack 不是 vector,因此 Stack 不应该继承 Vector。类似地,property 列表不是 hash 表,因此 Properties 不应该继承 Hashtable。在这两种情况下,复合都是可取的。
——《EffectiveJava》

3、依赖倒转原则(Dependence Inversion Principle)
针对接口编程,依赖于抽象而不依赖于具体。

● 高层模块不应依赖于低层模块,两者都应该依赖于抽象。
● 抽象不应依赖于细节,细节应该依赖于抽象。
这大概就是java service层明明没有其他实现却要写一个接口的愚蠢原因。这种情况下其实 “方法”已经实现了这一层抽象,修改方法内部的具体实现是不影响他的调用方的。不过大家似乎更想要基于类来梳理依赖关系,让高层依赖于一个接口以方便更换实现类。也许还是处于想提取共有处理逻辑的想法:可能biz logic已经不需要共有逻辑了吧。框架已经做完了所有共有逻辑,我们要写的业务逻辑已经是有区别的地步了。
也许rpc框架、spring注入依赖于接口更适合解答这个话题。
这也是面向接口编程理论基础。也许我应该基于6大原则画一个图

换言之,这个原则建议我们在设计代码时使得高层模块和低层模块之间通过抽象相互通信,而不是通过具体实现相互通信。这保证了当低层模块的具体实现变化时,不会影响到高层模块的业务逻辑。

依赖倒转原则是开闭原则的基础,提高系统的灵活性和可维护性。

4、接口隔离原则(Interface Segregation Principle)

使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

尽量使用合成/聚合的方式,而不是使用继承。

  • 面向对象五大原则,, 这些都出自哪里

术语理解

复杂对象:鬼知道四人组的初版设计模式里,有没有解释复杂对象!尤其是现在写biz logic的大家大多用的是最简单的POJO。大概就是在六大类与类的关系中榜上有名的类吧,比如聚合:一个类的成员变量是另一个类。这就结构就比较复杂了。这还可以解释建造模式分步骤组建对象。
类之间的关系

创建型模式

导读:面向过程的对象创建

背景:
在当前基于《阿里Java开发手册》、POJO、贫血模型、含service层的三层架构与微服务等思想指导的Java开发体系下,许多忙于写业务逻辑(biz logic)的程序员,几乎不用考虑面向对象与领域模型,只需要编写面向过程的service方法即可。
尽管设计模式的好处之一是可以通过命名来让其他程序员明白你用了什么设计模式——如XXXbuilder.build()方法可看出使用了建造者模式来创建对象——但,正如上述背景所描述的,程序员们通常会在XXXservice下写一个名为createXXX()方法来创建对象(并调用数据层持久化——一般设计模式不会讨论持久化,他们看起来都像是针对内存的编程。当然我们也可以将持久化看作创建复杂对象时面临的复杂业务逻辑的一部分,这样创建对象的逻辑仍然是在相当于创建方法的service方法中)。
一个创建订单对象的方法,使用“建造”来描述“创建订单”这一举动,或是用订单“工厂”来生产订单,其实并没有比喻得那么贴切。
在这种业务逻辑的开发情景下,其实Service可以看作一种Builder,createXXX方法就是build方法;(进一步对应其他角色的话,抽象层Service是抽象建造者,该服务的调用者就是指挥者,创建的对象自然就是产品——虽然又是会返回数据库调用结果(如insert的行数),而不一定像设计模式中常见的那样返回可由接口指向的对象(仅在内存中。这是由于设计模式通常懒得讨论持久化问题导致的))

  • 也许我还能在《企业应用架构模式》中找到一些数据源层实现来与创建型设计模式相结合呢

工厂模式(Factory Pattern)

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个接口来指向新创建的对象。

  • 看看四人组怎么说这个
  • 什么情境下不想对二方保留创建逻辑?当然现在的创建对象一般都是属性无脑set(biz logic领域),没啥可隐瞒的。说不定人家有复杂的创建对象的场景?找找例子
    • 大概想通一点,看下文哪吒创建线程的例子可知,“创建逻辑”不值指构造函数或者无聊的getter,还包括实现接口方法之类。而抽象工厂生产抽象产品,正好就符合了实现类需要重写这一点。不过重写倒是本来就不会暴漏创建逻辑啊,,而且多半还是直接new就行了的情况。看来是new这一逻辑太复杂不想要暴漏了啊doge 如果是那种需要现场重写的,情况也太少了吧,而且还是匿名内部类出来才有这种写法。
    • 也可能是多种构造函数不用记那个参数在哪个函数的那个位置了吧,虽然现在都有编辑器提示了。
  • 第二句话,“并且是通过使用一个共同的接口来指向新创建的对象”,其实本来目的就是利用操作接口、接口指向实现类对象这类特性,在忽略实现类的基础上直接写出统一的抽象逻辑。
  • 这么看来,抽象工厂模式还需要求产品们做同一件事(只做抽象接口中的方法,因为逻辑都是提前把抽象出来、能做的 先写好了)。这不就是策略模式、状态模式那种情景嘛!

调用方传入参数,工厂类根据参数组装对象。

菜鸟教程中的例子:定义Shape接口及其工厂类,创建工厂类对象,根据传入的参数返回各种接口的实现类。

优化方法:单例工厂/静态方法/接受枚举参数

有人提出用反射来创建实例,但这样工厂就显得多余,因为既然知道了类名、类的创建方式来反射,那反而可以直接创建而不需要工厂。
结合下文哪吒线程工厂那里我所悟的,本就是为了只用一个工厂管理所有同类的创建,甚至用接口“接生”创建出来的对象,连对象是什么类的叫什么名字都不用记
而且对于.net中的一个例子:WebRequest req = WebRequest.Create("http://ccc......"),根据传入的参数的不同,创建不同协议(如http和ftp)的对象。此时反射的例子不适用这种情景,因为程序猿连具体要获得什么类的对象都不知道。(我们可以用接口接收其实现类,所以调用方法时不必知道具体要什么类,直接用别人封装的返回值就行了)

抽象工厂模式

工厂比简单工厂多一层抽象,而抽象工厂则再多一层 。
说起来,使用抽象工厂模式后只操作抽象工厂和抽象产品,有点依赖于抽象而不是具体的味道了。

模式特征
简单工厂(非23种设计模式)具体工厂,生产产品
工厂工厂类有统一接口,生产一类产品(鼠标;颜色)
抽象工厂同类工厂每个厂可以生产多个产品,或一个产品可以多种品牌
  • 其实按照分层架构思想(虽然这里是创建对象分层而不是逻辑架构分层),应该是感觉到不断重写同一逻辑了所以抽象出一层吧?
产品工厂
耳麦接口工厂接口内加入生产耳麦方法
实现戴尔耳麦、惠普耳麦实现惠普工厂、戴尔工厂,实现生产方法

FactoryProducer不变,返回工厂实例对象

  • 那为什么不给工厂用静态方法?这样就不需要工厂对象了。

1)意图
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类.
感觉也是一个“无限输出”的例子。一系列相关的对象(这软考书中虽然说是“相关”而没说是父类及其子类,但我想应该是这样,那本书中也表达了系统从简单到复杂的发展中也是一开始用继承,后面会多用组合的发展情况),可以成了个从某一父类发展的大宗族了,这么多类(理论上无限个),我程序员总有一天记不住所有的类。那么就像系统复杂了要编写管理系统一样,类太多了就可以搞个专门的抽象类管理他们的创建工作。我想,这就是为什么说想要“无需指定具体的”吧。
对于这一系列关联的类,也许不用指定创建哪个类,而是根据传入的参数,看看你有什么,来自动拆给创建合适的类,反正你都可以用父类来引用他?之前菜鸟讨论区也见大家说过,如果还需要记着每个子类叫什么名字就本末倒置了。不知道这算不算一种“反转”。
总的来说,这种一边无限拓展导致另一边用信息系统或者专门的管理层,然后更下一层每个基本管理单元(子类)只管理自己的逻辑,确实是一种消除复杂性、每个个体专注于自己逻辑,然后整体互补处理所有情况的好办法。

抽象工厂模式包含以下几个核心角色:

  • 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。可以是接口或抽象类。
  • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。
  • 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。可用于抽象工厂的返回值
  • 具体产品(Concrete Product)
  • FactoryProducer:提供工厂实例对象。

一句话概括工厂模式

  • 简单工厂:一个工厂类,一个产品抽象类。
  • 工厂方法:多个工厂类,一个产品抽象类。
  • 抽象工厂:多个工厂类,多个产品抽象类。

例子

在哪吒的《三种方式模拟两个线程抢票》中,使用抽象工厂模式的理由是“每次测试新的多线程类时新建对象太麻烦”,因此用工厂模式每次更改传入的字符串来测试不同线程方式。其他使用抽象工厂模式的地方有没有这种重复逻辑更换多种对象的情景?数据库QO?

//哪吒的代码
public class ThreadsGrabTickets {
  public static void main(String[] args) {
    TicketSystem system = CodeSandboxFactory.newInstance("Synchronized");
    new Thread(system::sellTicket, "线程1").start();
    new Thread(system::sellTicket, "线程2").start();
  }

  static class CodeSandboxFactory {
    static TicketSystem newInstance(String type) {
      switch (type) {
        case "Synchronized":
          return new TicketSystemBySynchronized();
        case "ReentrantLock":
          return new TicketSystemByReentrantLock();
        case "Semaphore":
        default:
          return new TicketSystemBySemaphore();
      }
    }
  }
 static class TicketSystemBySynchronized implements TicketSystem {
    private int tickets = 100;

    @Override
    public void sellTicket() {
      while (tickets > 0) {
        synchronized (this) {
          try {
            if (tickets > 0)
              System.out.println(Thread.currentThread().getName()
                  + "卖出一张票,剩余票数:" + --tickets);
            Thread.sleep(200);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  static class TicketSystemByReentrantLock implements TicketSystem {
    private int tickets = 100;

    private final ReentrantLock lock = new ReentrantLock(); //定义锁

    @Override
    public void sellTicket() {
      while (tickets > 0) {
        lock.lock(); //上锁
        try {
          Thread.sleep(200); //模拟售票
          if (tickets > 0)
            System.out.println(Thread.currentThread().getName()
                + "卖出一张票,剩余票数:" + --tickets);
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          lock.unlock(); //解锁
        }
      }
    }
  }

看了下这个例子,我感觉该例使用工厂模式的好处反而是“同类且固定”:三个“产品”类都是线程类,对应可抽象的抽象产品(先因为是同类,因此可抽象);因此用一个同一的“线程工厂”来创建,就像面向对象把同一领域(domain)的逻辑收到同一个Class一样,方便管理、记忆、使用;
而固定:三个类的创建没什么变化,每次新建的都是一个简单的new出来的对象。但如今业务逻辑经常使用大量getter/setter的对象,也有包含列表、嵌套对象的复杂对象,不知道工厂模式对这两类对象是否还实用。也许大量setter的对象应该是建造者模式更加适合?

  • 比如VO\DTO\QO,属于同一领域,用工厂来创建何如?
  • 对于上述同一对象,每次都有不一样的数据需要set,工厂模式又如何应对?

至于“同类”抽象,可能是由于“重写逻辑太多”感受到需要新增一层时,作为工厂模式的起源思想吧!也是dry原则的体现。至于“工厂”,哈哈,只是个无聊的比喻,人家Vue就用vue.create()也不叫工厂。不知道福特的工业化、明朝的工业化的工厂思想有没有在工厂模式里体现。

简单工厂模式: Spring的BeanFactory。
工厂方法模式: Spring的FactoryBean。通过该接口可以快速方便的实现自己的bean工厂,只要实现接口、指定泛型、重写getObject()

例子:vue3、jwt库、线程池、数据库连接池、Feign的fallbackFactory

mybatis sqlSessionFactory

曾经思考axios.get()是不是工厂模式(会返回response对象),但该方法主要是为了发送请求并接收响应,这是个行为,而不是为了创建对象

vue

import { createApp } from 'vue'
createApp({
  data() {
    return {
      count: 0
    }
  }
}).mount('#app')

SqlSessionFactory 负责获取数据源环境配置信息、构建事务工厂和创建操作SQL 的执行器,最终返回会话实现类。

NIO的Selector创建,在netty中有应用,会根据操作系统调用不同的系统调用

有丶像抽象工厂模式,不过这个应该是写死在底层jdk,不同平台不同实现

DefaultSelectorProvider.create();创建Provider,由Provider的openSelector()返回具体到操作系统的Selector对象

其中,Provider的上层有SelectorProviderImpl,定义了抽象的openSelector()方法,返回值是抽象的AbstractSelector,此处是抽象工厂模式的体现。

优点:如果直接让程序员记住三个操作系统的类名字分别是什么,是不方便的—再说万一这些类以后改名了呢?所以通过工厂类统一获取

而为了方便实现、维护工厂类,采用了抽象工厂模式,先抽象的写出流程,再由实现类根据不同操作系统环境创建具体对象

// 也可以通过实现java.nio.channels.spi.SelectorProvider.openSelector()抽象方法自定义一个Selector。
this.selector = Selector.open();

// 点进SelectorProvider.openSelector()f
// DefaultSelectorProvider会根据不同的操作系统去创建不同的SelectorProvider
provider = sun.nio.ch.DefaultSelectorProvider.create();

所以要创建一个Selector,底层完整的调用链其实是:sun.nio.ch.DefaultSelectorProvider.create().OpenSelector()

  • 如果是Windows操作系统,则创建的是WindowsSelectorProvider对象。
  • 如果是MacOS操作系统,则创建的是KQueueSelectorProvider对象。
  • 如果是Linux操作系统,则创建的是EPollSelectorProvider对象。

例如Mac系统,那么跟进create()方法时,进入如下代码:

public class DefaultSelectorProvider {
	public static SelectorProvider create() {
    	return new KQueueSelectorProvider();
	}
}

继续往下跟进KQueueSelectorProvider(),进入KQueueSelectorProvider类:

public class KQueueSelectorProvider extends SelectorProviderImpl {

	public AbstractSelector openSelector() throws IOException {
    	// KQueueSelectorImpl是具体实现类,我们继续往下跟进,去看看实现逻辑:
    	return new KQueueSelectorImpl(this);
	}
}
  • 蚂蚁金服(数字马力-郑州)笔试题
集合类的工厂方法

在Java8的年代,即便创建一个很小的集合,或者固定元素的集合都是比较麻烦的,为了简洁一些,有时我甚至会引入一些依赖。原来的写法

Set<String> set = new HashSet<>();
set.add("a");
set.add("b");

新的写法

Set<String> set = Set.of("a", "b", "c");

好吧。 起码哪吒说这是工厂

工厂方法

1)意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
在这里插入图片描述

建造者/生成器模式 Builder Pattern

1)意图
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
Builder 模式适用于:
当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时当构造过程必须允许被构造的对象有不同的表示时。

使用多个简单的对象一步一步构建成一个复杂的对象。

如根据商品信息对象和订单对象生成销售明细对象。这种复杂对象内部可能包含来自多个领域的信息。
思考:拥有上百个属性的简单POJO,是否算一个复杂对象呢?
回想类和类之间有6大关系,其中“组合”这一关系正好符合“由依赖的对象创建对象”这一概念,当然其他关系我忘了看了,欸嘿。
对于POJO,有时我们不使用聚合、继承这些特性来耦合共有的属性,而是一条条重复罗列所有的属性(类似数据库库表继承中的全表继承)。如果有太多的属性,通常set起来也非常麻烦。我们经常会习惯性的把相关属性放在一起写set。这个时候可以考虑下,这些属性是不是也近似一个单独的领域对象了呢?
我理解了。领域逻辑的划分、区分是逻辑上讲的,是无关具体代码实现方式的。尽管实现上我们可能使用继承,也可能不使用(因阿里开发规范,很多程序员心中优先级:再写一遍所有属性 > 组合 > 继承)来实现低耦合,但归根到逻辑上来讲,类之间仍然存在着关系,存在着联系。
综上,只要逻辑上(领域逻辑设计上)一个对象拥有复杂的关系,(即使其代码实现上只是一个POJO,而没有用extends等将其硬编码为具有复杂关系的类),该类仍然算一个复杂对象

优点: 1、建造者独立,易扩展。 2、便于控制细节风险。

缺点: 1、产品必须有共同点,范围有限制。 2、如果内部变化复杂,会有很多的建造类,对应不同建造方式

使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

  • 依赖我理解,什么是相互依赖?结合下面顺序,难道是内部聚合的对象之间?比如现有订单再有流水,先有采购再有销售,因此一个个set控制顺序麻烦,所以先弄一个再弄一个?

与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
比如我们可以先创建Builder对象,再链式调用,不断的setXXX().setXX().setX(),最后再build()。

StringBulider, SqlSessionFactoryBuilder

从设计上,可以只用builder,可以进一步设置Director(对应土木项目中的项目主管(尽管实际土木项目中会有多种主管)),

*上面这张图,,一个具体Builder负责创建所有的部位!这,同复用那本书中的一个Builder创建一个部位不同!但是,也无妨!毕竟最终都是director负责编排这些建造方法!
而且,在书中后文建造迷宫的案例中,也是同一个builder的不同方法来建造不同部件了!只有文字converter的那个案例不同罢了!
好吧,,呃,,我想,还是统一的一个builder是buidler接口,然后可以用不同的部件builder实现?
经常见到建造者模式建造工厂,,,

另外适用于快速失败,在 build 时可以做校验,如果不满足必要条件,则可以直接抛出创建异常,在 OkHttp3 中的 Request.Builder 中就是这样用的。

public Request build() {  
    if (url == null) throw new IllegalStateException("url == null");  
    return new Request(this);
}

场景

使用生成器模式可避免 “重叠构造函数 (telescoping constructor)” 的出现。

class Pizza {
  Pizza(int size) { …… }
  Pizza(int size, boolean cheese) { …… }
  Pizza(int size, boolean cheese, boolean pepperoni) { …… }
 // ……

假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。

设计模式-可复用面向对象软件的基础——笔记

该书讲述了一个富文本转换的案例。使用Director来调用Builder,每个Builder生产一个部件

  • 客户创建D i r e c t o r对象,并用它所想要的B u i l d e r对象进行配置。
  • 一旦产品部件被生成,导向器就会通知Builder。
  • Builder处理导向器的请求,并将部件添加到该产品中。
  • 客户从生成器中检索产品。

看了上面的“用Builder来配置Director”,以及“Builder处理Director请求,愈发明白为什么类是依赖于他的成员变量的了。”

适用性在以下情况使用B u i l d e r模式
• 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
• 当构造过程必须允许被构造的对象有不同的表示时。
好处
部件的随意组合与复用!创建不同形式的迷宫!虽然blyat’他的具体实现连我都给隐藏了!

这里分离的好处是不是通用的?我看似乎有些老生常谈的问题。什么“每个C o n c r e t e B u i l d e r……代码只需要写一次;然后不同的D i r e c t o r可以复用它以在相同部件集合的基础上构作不同的P r o d u c t”,(虽然之前没想到过服用这些代码hhh)不过既然分离了,自然会有这样的特性!可惜我的打标签笔记还没有完工,不然的话,给这个概念打上“分离”属性,以后看到了自然会发现有这个特性!但是标签是用来区分的还是什么的?有这个自动带有的功能吗?在OOP里叫继承吧?可能标签是为了区分、识别、检索客观物质世界,分析庞大系统用的?不是用来自动带有某种特性用的?

  • 不同get set的阿里pojo,还用这么麻烦建造?可能某些情况下某些属性需要先决条件,这时候也写道service里了吧!现在流行写service里。可能某种情况下的pojo,不是vo一样普通set的,而是有特殊条件的(但根据DDD,可能被拆到service)这种是什么?实体?

  • 此处提到的“逐步”,主要指能分步骤吧,一定要求先后顺序吗?一定吗?一定吗?

"但有时你可能需要访问前面已经构造了的产品部件"这说明之前的例子就是不需要访问之前build的部件的。可能之前的是关联实体(甚至不是有他的引用的这种关联)?只是需要关联创建它,但是甚至没必要指向他?(如果按照没必要访问之前的部件理解的话)
那这样的情况岂不是也不要求顺序了(笑)带入限时生产的话,如果房屋是要求顺序的,而其他零件可能顺序只有微小的的区别。

同组合的关系(组合好像是OOP关系吧,这里也是一种设计模式) Co m p o s i t e通常是用B u i l d e r生成的

重构大师笔记

呐呐呐~你怎么一直再提“假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便”呐?是不是不知到贫血模型呐?唔嘻嘻~雜魚雜魚~

问题
假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。

我认为主要问题在于后半句。生成器模式一来解决了曾经把构造逻辑集中在构造函数,导致构造逻辑过于难懂的问题(相信很多开发者都有过调用服务、接口之前由于不清楚其中具体逻辑而担心接口是否合适的情况。尤其是接口文档的描述难免与实际代码有所偏差); 二来,bean的一些属性,即使用贫血模型set方式,也难以避免这种困境:“单个属性的取值逻辑太过复杂,且分散在各个地方(以我的经验来看是,会分散在多个service中。平常我的处理方式是提取分散在各个位置的共有逻辑,提取成方法)”
可以认为,在对象结构复杂(如组合其他对象、属性的取值需要复杂的关联或判断)的情况下,以整个对象为单位构造实在太复杂; 而用贫血模型,又会导致构造逻辑散布在各个不同地方。builder相当于:对于构造逻辑,在“对象”和“属性”粒度之间封装了一层,控制起来更加方便。
也许这也是随着复杂度增加,中央集中管理(构造方法)和就近区域自治(各属性自己管理逻辑,或是service方法中根据实地情况整理)之间的取舍呢。加一层就是builder方法更具体的集中或单个管理属性。

进一步:主管 Director

复用,封装,隐藏。话说一般封装和隐藏都语义化的好处吧。
你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现。
严格来说, 你的程序中并不一定需要主管类。 客户端代码可直接以特定顺序调用创建步骤。 不过, 主管类中非常适合放入各种例行构造流程, 以便在程序中反复使用。

此外, 对于客户端代码来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个生成器与主管类关联, 然后使用主管类来构造产品, 就能从生成器处获得构造结果了。

进一步:结合面向接口编程:每个部件builder可拓展多种实现

很容易理解,说一下权当复习前面内容了:每个builder的方法可以建造一个“部件”,如果调用构造步骤的客户端代码可以通过通用接口与建造者进行交互,就可以提供不同实现。

案例: MybatisPlus Wrapper

    /**
     * 手动set条件
     * 更新 user表name字段和address表address字段
     */
    @Test
    void update() {
        UpdateJoinWrapper<UserDO> update = JoinWrappers.update(UserDO.class)
                .set(UserDO::getName, "aaaaaa")
                .set(AddressDO::getAddress, "bbbbb")
                .leftJoin(AddressDO.class, AddressDO::getUserId, UserDO::getId)
                .eq(UserDO::getId, 1);
        int i = userMapper.updateJoin(null, update);
    }

案例:Mybatis源码

sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取数据库的会话,创建出数据库连接的会话对象(事务工厂,事务对象,执行器,如果有插件的话会进行插件的解析)
SqlSession sqlSession = sqlSessionFactory.openSession();

👆😀看,要创建sqlSessionFactory对象的参数又多又复杂,难道要用构造函数一个个传参吗?Spring系框架又要剥离配置与代码,所以给Builder传入配置文件流inputStream就好,
👇😀在build(InputStream )方法内部,将配置文件解析成Configuration 这配置的包装类

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    // 准备阶段:将配置文件加载到内存中并生成document对象,然后创建初始化Configuration对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  // 解析document对象并生成 SqlSessionFactory
    return build(parser.parse());
}

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
  • Config类疑似有些不POJO了

XMLConfigBuilder:用于创建configuration,解析MyBatis配置文件中 configuration 节点的配置信息并填充到 Configuration 对象中(返回Configuration)

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
   this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
   // 调用父类初始化configuration
   super(new Configuration());
   ErrorContext.instance().resource("SQL Mapper Configuration");    
   // 将Properties全部设置到configuration里面去
   this.configuration.setVariables(props);
   this.parsed = false;
   this.environment = environment;
  // 绑定 XPathParser
   this.parser = parser;
}

建造者模式在MyBatis 中使用了大量的XxxxBuilder,将XML 文件解析到各类对象的封装中,使用建造者及建造者助手完成对象的封装。它的核心目的是不希望把过多的关于对象的属性设置写到其他业务流程中,而是用建造者方式提供最佳的边界隔离。

案例:线程工厂

    // thread factory       线程工厂
    ThreadFactory threadFactory = new ThreadFactoryBuilder()
            .setUncaughtExceptionHandler((thread, throwable) -> {
                log.error("workerExecutor has uncaughtException.");
                log.error(throwable.getMessage(), throwable);
            })
            .setDaemon(true)
            .setNameFormat("collect-worker-%d")
            .build();
  • can can need 源码
  • 开平仓这种,虽然可以看做聚合了开仓与平仓,可以看作他们的父类,也可以就看做是一个类,可以build其中的部分吗?
  • 什么业务需要建造这么复杂的对象?
    • SqlSessionFactory,工厂类比较复杂;
    • sqlSession有这么复杂,还需要工厂(现在更是要mapper)?我jdbc写几句不就好了?同理线程?
    • POJO还复杂吗?业务上有什么类似管理会话的场景?

案例讨论:一百个属性的类

在工作中碰到一个VO类,足足100个属性。原来是从采购-销售一条龙的交易明细展示,结合了采购明细和销售明细两个大类的属性,而不是一个单独的实体。
在controller中拼接这个VO需要足足上百行的Set方法,于是考虑用builder构建。
https://www.coder.work/article/853978

  • 建造者模式与链式调用与顺序?
  • 在pogo\分层架构这种应用中,Builder属于?实体?

思考:

贫血模型已经限制属性可以单独set了,就是一个个set麻烦些,多个属性组合看作一个对象来builder,也不是很麻烦。so,Builder是不是有考虑某些方法必须设置了某些属性的情况下才能用?同时又觉得放构造或者一个个set太麻烦?但是她有没有像枚举一样限制构造方法私有。红豆泥亚萨西呐,builder模式。

单例模式(Singleton Pattern)

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。(创建与销毁是有成本的,创建不必要的也会浪费资源)

关键代码:构造函数是私有的;在类内部创建一个自己的静态实例。

缺点

没有接口,不能继承,与单一职责原则(?)冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

饿汉式

定义静态成员变量时就创建对象

是否 Lazy 初始化:否

是否多线程安全 :是

实现难度:易

描述 : 这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,实例在类装载时就实例化,虽然导致类装载的原因有很多种,这时候初始化 instance 显然没有达到 lazy loading 的效果。

在getInstance()方法中不用再考虑实例是否在为空时需要创建,直接返回即可。

懒汉式

等到调用getInstance()方法的时候再创建对象。

由于创建对象时已进入临界区,如果不加Synchronized锁的话不能线程安全。

双检锁/双重校验锁(DCL, double-checked locking)

why双锁模式下还有高性能?

1.使用volatile修饰我们的对象引用
2.外部if判断对象是否为null,为null往下执行,不为null直接返回对象
3.使用syn同步进入代码块,使用if判断对象是否为null,为null就创建对象
4.使用volatile修饰成员变量的原因就是防止重排序的问题—>变量还未完全初始化就被线程B返回了

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

原型模式

3)适用性
Prototype 模式适用于:
当一个系统应该独立于它的产品创建、构成和表示时。
当要实例化的类是在运行时刻指定时,例如,通过动态装载。
为了避免创建一个与产品类层次平行的工厂类层次时。
当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。
第一条算是继承于创建型模式了;最后一条是在cue枚举么hhh

结构型模式

结构型设计模式涉及如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果这个类包含了所有父类的性质。这一模式尤其有助于多个独立开发的类库协同工作。

适配器模式 Adapter pattern

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。
它结合了两个独立接口的功能。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

类适配器使用多重继承对一个接口与另一个接口进行匹配。
对象适配器依赖于对象组合。

优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

**注意事项:**适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

复用已经写好的接口,给他们装个适配器被其他接口调用

  • 招个刚毕业ava程序员

  • 给他看公司文档(针对该公司项目的适配器)

  • 变成专门为某个项目工作的程序员

  • 要调到其他项目,在针对性的让该程序员适应差异(不同适配器)

重点是提高复用

实现例子

菜鸟论坛:看起来只是在一个实现类里调用Adapter类就可以了,,

案例

计网中的适配器

2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。

桥接

1)意图
将抽象部分与其实现部分分离,使它们都可以独立地变化
2)结构
在这里插入图片描述
其中:
Abstraction 定义抽象类的接口,维护一个指向 Implementor 类型对象的指针。
RefinedAbstraction 扩充由Abstraction 定义的接口。
Implementor 定义实现类的接口,该接口不一定要与 Abstraction 的接口完全一致;事实上这两个接口可以完全不同。一般来说,mplementor 接口仅提供基本操作,而Abstraction 定义了基于这些基本操作的较高层次的操作。
Concretelmplementor 实现 Implementor 接口并定义它的具体实现。
3)适用性

  • 不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如,这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
    不知道状态转换的时候啊什么的会不会出现这种情况。不过我感觉curd也可以实现。不好说有没有用。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这是 Bridge 模式使得开发者可以 对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户代码不必重新编译。
  • (C++)想对客户完全隐藏抽象的实现部分。
  • 有许多类要生成的类层次结构。
  • 想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。

组合模式

可以将对象组合成树形结构以表示“部分—整体” 的层次结构。

**场景介绍:**在MyBatis XML 动态的SQL 配置中,共提供了9 种标签(trim、where、set、foreach、if、choose、when、otherwise 和bind),使用者可以组合出各类场景的SQL 语句。而SqlNode 接口的实现就是每个组合结构中的规则节点,通过规则节点的组装,完成规则树组合模式的使用。

**同类场景:**主要体现在对各类SQL 标签的解析上,以实现SqlNode 接口的各个子类为主。

在这里插入图片描述

装饰器模式 Decorator

1)意图
动态地给一个对象添加一些额外的职责。就增加功能而言,Decorator 模式比生成子类更加灵活。

将对象放入包含行为的特殊封装对象中, 为元对象绑定新的行为。

  • 允许向一个现有的对象添加新的功能,同时又不改变其结构
  • 允许在运行时动态地添加或修改对象的功能。
    在这里插入图片描述
    感觉说是动态添加职责。是提前在装饰器写好可以添加的各种方法(接口定义可添加的方法,各种实现允许不同的这种方法),然后动态的使用它们。

包装器类的缺点很少。一个需要注意的点是:包装器类不适合在回调框架中使用,在回调框架中,对象为后续调用(「回调」)将自定义传递给其他对象。因为包装对象不知道它对应的包装器,所以它传递一个对它自己的引用(this),回调避开包装器。这就是所谓的「自用」问题。

这说的是不是类似builder那种?不过返回自己,设置自己属性的话,不影响装饰器调用它在调用吧。除非外部的set方法也要包装

二级缓存装饰器的实现结构如图8所示。

图片

场景介绍

  1. 《阿里java开发手册》:谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现
    组合也是一种装饰器模式。
  2. MyBatis 的所有SQL 操作都是经过SqlSession 调用SimpleExecutor 完成的, 而一级缓存的操作也是在简单执行器中处理的。这里的二级缓存因为是基于一级缓存刷新的,所以在实现上,通过创建一个缓存执行器,包装简单执行器的处理逻辑,实现二级缓存操作。这里用到的就是装饰器模式,也叫俄罗斯套娃模式。

过滤器模式

过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。

外观模式

在这里插入图片描述

享元模式 Flyweight

在这里插入图片描述

代理模式

在这里插入图片描述

模板方法模式

定义一个操作中的算法的骨架流程,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

定义一个抽象类,在其中实现算法骨架的方法;该方法的一些“步骤”为调用抽象方法,然后在子类中实现这些抽象方法

public abstract  AbstractGroupLabelJudgeTemplate implements IGroupLabelStrategyService{
         //模板骨架
         public boolean processBiz(Parameter dto){
              if(isSwitchClose){
                 return false;
              }
             if(dto.reqNum==1){
                 return singleRemote(dto);
             }else if(dto.reqNum>1){
                 return batchRemote(dto);
            }
         }
       //开关由子类控制
        abstract boolean isSwitchClose();
        //单笔远程调用,由子类控制
        astract boolean singleRemote(dto);
        //批量远程调用,由子类控制
        astract boolean batchRemote(dto);
}

这个例子类似泛型,子类处理不同类型、类似操作的方法

思考

在菜鸟教程入门的设计模式,每期评论区一堆“秀儿”说自己优化代码,优化着优化着跑其他设计模式去了,还有的一会儿被后面人打脸的;
私以为可能是为某一特定设计模式找的例子,其实很难找到不适合其他所有设计模式的例子;且如何设计、在某一阶段牵扯到的改动多大,很多时候是那种不能全赢的博弈,是一种抉择。因此就从所谓“假设的业务”层面,总会有似乎可以优化的点子。因此,主要的关注点应该在该设计模式的思想的学习,以及这个所谓的“例子”是否真的适合学习这种设计模式。优化可以作为从业务角度的思考,但优化是无穷无尽的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值