设计模式(包括Spring)、贯穿项目梳理与源码知识点

目标:高复用性,高内聚,低耦合

目的:高可读性,重用性,可靠性

类的六种关系

  • 依赖,类中用到了对方,没有对方连编译都通不过,如下的几种关系全部是依赖关系
  • 泛化/继承,is-a关系,子类基础了父类代表它本身就是和父类是同一种类型,只不过在此基础上做了更详细的功能扩展,例如玻璃杯、塑料杯的父类是杯子,玻璃杯、塑料杯也可以被看做是杯子
  • 实现关系,like-a关系,代表一种规范、一种能力,实现类实现这个接口即意味着需要具备某种功能,具代表例如空调继承与制冷器,同时它也实现了加热接口通过重写相关方法具备了加热功能
  • 关联关系,分为聚合与组合
  • 聚合:has-a关系:一个类A持有类B类型的属性,即A has B,但非必须持有,例如车和轮胎,计算机和鼠标键盘外设的关系
  • 组合:has-a关系,类似于聚合,但区别是组合关系的类直接依赖程度高于聚合,例如人和他的头是不可分的

=,如果项目中出现级联删除,就是删除person的时候同时删除card对象,那么他们的依赖关系就介于聚合和组合之间,被算作组合关系

依赖,关联,聚合,组合
这四种关系,都表示类与类之间的关联关系,说明两个类之间有某种联系。而依据与联系的紧密程度,由弱到强,依次成为 依赖<关联<聚合<组合

其中标准的java bean对象都是聚合关系,也就是说面向对象的封装特征中类和它的引用类型属性的关系为聚合关系,继承关系要慎用,通常情况下都是用组合关系代替,除非是is a的关系,has a的关系必须用聚合or组合关系

七大原则

七大原则

单一职责原则:抽象好的类只负责一种功能事,否则耦合度高,做修改时可能会影响另一个功能
接口隔离原则:一个类A对另一个类B的依赖(a中通过接口b引用来让它的接口实现类B来干活)应该建立在最小接口之上,类B在类A中被用到几个方法,接口b中就应该有多少对应的,否则b应该被拆分成对应的b1接口;

单例模式

系统中有些特殊的对象我们只允许它存在一个,不能被重复创建,防止资源浪费,更为了避免两个对象间状态不一致产生的问题,比如Windows系统始终只能弹出一个任务管理器窗口,因为如果弹出多个首先会造成资源浪费,最严重的是如果多个任务管理器显示状态不一致问题会给用户带来很大困扰

单例模式三要素

  • 保证一个单例类仅有一个实例,构造器是private着禁止在其它地方实例化这个类,且这个唯一的单实例对象由单例类内部维护(饿汉是组合关系,懒汉是聚合关系)
  • 提供一个全局访问点 => 提供一个对外公开的全局静态单例方法,对外提供这个单实例对象

饿汉式:不够节约资源,但可以避免一切线程安全问题

懒汉式:更加节约资源,但高并发下容易产生一些列线程安全问题,导致系统中出现多个单例对象,造成问题,解决方法

  • 线程同步方法
  • double check双重判断
  • 静态内部类
  • 枚举方法

注意区分,单例模式和Spring的singleton

工厂模式

简单工厂

核心组件

  • 普通工厂类SimpleFactory,内部定义了一个工厂方法SimpleFactoryMethod(静态方法),只需要传入一个正确的参数type,就可以创建并返回你所需要的对象
  • 抽象产品 Prodouct:作为SimpleFactoryMethod方法返回类型
  • 具体产品 ConcreteProduct,由参数type来决定创建并返回哪一个具体产品

问题:工厂方法内部还是有大量的if-else语句,不符合开闭原则,需要进一步消灭里面的if-else

案例:Spring中的BeanFactory用到了简单工厂模式,工厂方法是getBean,传入唯一标识(beanName,beanClass)可以获取到相应的bean,符合简单工厂模式特征

工厂模式

重要组件

  • Factory,这些具体的工厂类共同基础一个接口,里面有一个接口方法factoryMethod
  • ConcreteFactory,具体的产工厂,实现工厂方法,一个工厂对应一个具体产品的创建
  • 抽象产品 Prodouct
  • 具体产品 ConcreteProduct,和ConcreteFactory一一对应

针对简单工厂的升级版,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品类型提供不同的普通工厂类,如此依赖工厂方法模式让一个产品的创建过程延迟到其子类工厂中,也就是先确定具体工厂,确定后再由该工厂负责他所创建的对象,用同数量的工厂类消灭简单工厂中工厂方法中的if-else

新问题:由于工厂方法模式中的每个子工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销,即便是符合开闭原则,但增加新功能很容易导致类爆炸

案例
Spring factoryBean

抽象工厂模式

我们希望一个工厂可以提供多个产品对象,而不是单一的产品对象,如一个电器工厂,它可以生产电视机、电冰箱、空调等多种电器,而不是只生产某一种电器,可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产

  • 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。

  • 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。
    设计模式(包括Spring)、贯穿项目梳理与源码知识点_第1张图片
    重要组件

  • 超级工厂IProductFactoty:里面有多个接口方法factorymethod,这些接口方法对应着一个产品等级结构的创建,例如我们用三个接口方法,分别代表着生产电视、洗衣机、空调这三种产品

  • ConcreteFactory,实现IProductFactoty中的多个接口方法factorymethod,生产具体的产品ConcreteProduct,ConcreteFactory代表是一个产品族,比如美的和海尔这两个具体工厂

  • Prodouct抽象产品 ,对应产品等级结构,比如这里有电视、洗衣机、空调三种产品

  • ConcreteProduct具体产品,真正的产品,这里有六个,海尔和美的的电视、洗衣机、空调

工厂模式总结:

  • 工厂模式将对象的创建与使用拆分开,解耦合,两个类A和B之间的关系应该仅仅是A创建B或是A使用B,而不能两种关系都有。将对象的创建和使用分离,也使得系统更加符合“单一职责原则
  • 有利于对功能的复用和系统的维护,减少代码重复,可以将有关创建的流程统一到工厂类中,防止用来实例化一个类的数据和代码在项目中重复出现

工厂模式的核心原则是解耦,提供创建对象代码的重用性,为了符合开闭原则①客户端层面(前端js代表做选择),选择好了相关的子类工厂,接口层面来创建相关的对象工厂,创建的过程可以结合配置文件,到时候业务发生变化只需要改配置文件即可,更好的符合开闭原则

原型模式

原型模式(Prototype Pattern)是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。即用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。

浅克隆:将原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。即用基类Object的clone()方法或序列化。
深克隆:序列化完成深克隆,或者在clone的过程中手动克隆下相关的对象属性

建造者模式(Builder Pattern)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。

建造者模式
设计模式(包括Spring)、贯穿项目梳理与源码知识点_第2张图片
● Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,builder中组合了一个product产品对象,在该接口中一般声明两类方法,一类是抽象方法方法是buildPartX(),它们用于创建复杂对象的各个部件,细节由各个子类来实现;另一类方法是getResult(),它们用于返回已创建完毕复杂对象。
● ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
● Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。
● Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。

在有些情况下,为了简化系统结构,可以将Director和抽象建造者Builder进行合并,合并方式就是,在Builder内部设置一个核心方法construct,Builder自己调用自己的建造方法来完成一系列角色创建

客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

源码应用

  • Appendable 是抽象建造者
  • AbstractStringBuilder可以看做是具体的建造者,重写了append方法,只是不能实例化
  • StringBuilder即是指挥者(通过继承AbstractStringBuilder的方式和建造者建立关系,然后在重写的方法中调用了AbstractStringBuilder的append)又是具体的建造者,同时也充当了具体产品角色,最终返回的就是它自己本身

个人应用@Builder

  • 为该类生成的内部类是真正建造者
  • 这个类就变成了指挥者,同时也作为具体产品对象

产品的创建与生产过程解耦,掩盖了真实的产品创建过程

意义:每一个具体建造者都相对独立,可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合“开闭原则”

模板方法模式

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

AbstractClass(抽象类):在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method)

模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法

ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。

模板方法,代表是一个由多个小步骤组成的一个复杂流程,其中这些小步骤很多是固定套路,只有个别方法是不固定的

固定套路我们自己在抽象类或其他类中定义具体方法,在模板方法里直接拿来用,大大增强了这种方法的重用性,不固定的流程我们定义为抽象方法或者是空方法,具体的实现过程肯定是延迟到子类中来做实现,最后直接由子类调用模板方法完成这个具体的复杂流程

应用案例

  • sobot项目:导入导出lhj模式,转人工链路wzh模式
  • Spring中:AbstractApplication源码中的refresh方法

适配器模式

参考文章
我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器或变压器,有了这个电源适配器,生活用电和笔记本电脑即可兼容
设计模式(包括Spring)、贯穿项目梳理与源码知识点_第3张图片
适配器模式有两种实现方式:类适配器和对象适配器。类适配器使用继承关系来实现,对象适配器使用组合关系来实现。

在对象适配器模式结构图中包含如下几个角色:
● Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

● Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

● Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码

经典模式,对象适配器

class Adapter extends Target {
	private Adaptee adaptee; //维持一个对适配者对象的引用
	
	public Adapter(Adaptee adaptee) {
		this.adaptee=adaptee;
	}
	
	@override
	public void request() {
		adaptee.specificRequest(); //转发调用
	}
}

类适配器

public interface AmerciaInterface{
  void powerCharge();
}

public class AmericaCharge implements AmerciaInterface{
    @Override
    public void powerCharge() {
        System.out.println("美国插口");
    }
}

public interface GBSocketInterface {
  void powerWithThreeFlat();
}

public class GBCharge implements GBSocketInterface{
    @Override
    public void powerCharge() {
        System.out.println("国标插口");
    }
}

public class AmerciaHotel {
 
	//旅馆中有一个德标的插口
	private AmerciaInterface dbSocket;

	public void setSocket (AmerciaInterface dbSocket){
		this.dbSocket = dbSocket;
	}
 
	//旅馆中有一个充电的功能
	public void charge(){
		//使用美国插口充电
		dbSocket.powerWithTwoRound();
	}
}

public class GBAdatpter implements AmerciaInterface{

    private GBSocketInterface gbSocketInterface;

    public GBAdatpter(GBSocketInterface gbSocketInterface) {
        this.gbSocketInterface = gbSocketInterface;
    }

    @Override
    public void powerCharge() {
        System.out.println("适配器适配:Amercia => GB");
        gbSocketInterface.powerCharge();
    }
}

public class Test {
    public static void main(String[] args) {
        // 我去美国旅游,带去的充电器gbCharge是国标的
        GBCharge gbCharge = new GBCharge();
        //来到美国,我找了一家宾馆住下,宾馆充电器仅支持美国充电器
        AmerciaHotel amerciaHotel = new AmerciaHotel();
        //  由于没法充电,我拿出随身带去的适配器,并且将我带来的充电器插在适配器的上端插孔中。这个上端插孔是符合国标的,我的充电器完全可以插进去
        GBAdatpter gbAdatpter = new GBAdatpter(gbCharge);
        //再将适配器的下端插入宾馆里的插座上
        amerciaHotel.setCharge(gbAdatpter);
        //开始充电
        amerciaHotel.charge();
    }
}

适配器模式在不做任何改变的前提下将一个类的接口转换成客户期望的另一个接口,让原本不兼容的接口可以合作无间。

适配器模式经典案例org.springframework.web.servlet.DispatcherServlet#doDispatch

  • DispatcherServlet,这里提供一个选择方法getHandlerAdapter,根据不同的Handler类型选择出合适的适配者,内部是通过for选择外加HandlerAdapter的support方法来实现的
  • Handler是作为被被适配者adptee
  • HandlerAdapter接口作为适配者adapter,其下面有四个具体的适配者类,handle是作为核心的处理方法,是真正的处理逻辑,supprot用于判断hander是否作为该适配器的适配者,完成多个adptee和adpter间的一对一匹配

这里也可以这样改版:HandlerAdapter接口用一个list容器handlerAdapter代替,这是一个类容器,用于管理这四种适配器类,也就是接口一定程度上也可以看做是一系列类的管理器(容器)

这里为每个不同的handler方法都对应一个HandlerAdapter,适配器的作用就是适配(support方法)和处理(handler),真正的请求处理逻辑是HandlerAdapter做的,这在一定程度上可以大大减少if-else的使用,优化代码结构

策略模式

设计模式(包括Spring)、贯穿项目梳理与源码知识点_第4张图片

  • Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义并获取所采用的具体策略。
  • Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口、map集合、匿名函数实现类、Spring容器环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
  • ConcreteStrategy(具体策略类):可以是一个具体类,甚至一个lamda表达式,它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

应用案例

  • sobot项目:导入导出lhj模式,转人工链路wzh模式
  • jdk方法排序源码:匿名函数实现类Comparator的引用作为抽象策略类,他实现的匿名函数体本质是作为具体策略类,Arrays.sort方法则是充当Context环境类,使用我们传入的comparator实现体来完成定制的排序功能

观察者模式

而绿灯亮起,汽车可以继续前行。在这个过程中,交通信号灯是汽车(更准确地说应该是汽车驾驶员)的观察目标,而汽车是观察者。随着交通信号灯的变化,汽车的行为也将随之而变化,一盏交通信号灯可以指挥多辆汽车

核心组件
设计模式(包括Spring)、贯穿项目梳理与源码知识点_第5张图片

  • Subject(目标):目标又称为主题,它是指被观察的对象,提供三种抽象方法,注册registerObservers到观察者集合,delteObservers从观察者集合移除,notifyObservers集体通知,通过遍历集合中所有观察者,调用Observer的update方法来实现
  • Event(事件):通过由目标对象产生
  • ConcreateSubject:具体主题,Subject实现类,里面维护着一个观察者集合List< Observer >,并实现上述三种抽象方法,观察者集合
  • Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
  • ConcreateObserver(观察者):实现Observer中的update方法。如果需要用到ConcreateSubject中的属性,可以把ConcreateSubject作为属性
  • Clinent,相当于事件的发布者,发布时间通知全部观察者做出相应动作

观察者模式描述了如何建立对象与对象之间的依赖关系,以及如何构造满足这种需求的系统。观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监视观察目标的状态以使其状态与目标状态同步,这种交互也称为发布-订阅(Publish-Subscribe)。观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。

实际案例

  • 事件模型ApplicationEvent(类似于Subject),此类为Spring事件中用于扩展的事件对象,构造方法中Object类似的的source是具体的事件,所有需要使用事件驱动模型发布的事件(类似于ConcreateSubject) 都可以继承此类,例如

    • ContextStartedEvent:Spring Application容器启动后触发的事件。
    • ContextStopedEvent:Spring Application容器停止后触发的事件。
    • ContextClosedEvent:Spring Application容器关闭后触发的事件。
    • ContextRefreshedEvent:Spring Application容器初始化或者刷新完成后触发的事件。
  • 事件发布者ApplicationEve ntPublisher(Client中的关键组件),实现该接口则具备了事件发布能力,最典型的就是AbstractApplicationContext,通过publishEvent方法来进行事件发布,①对象转化,object类似转化为ApplicationEvent类型②获取到ApplicationEventMulticaster③判断spring容器是否具有父容器,有的话会在事件发布的时候通知到父容器,主要是通过事件发布器来完成的,事件发布者往往作为一个实例属性聚合到真正的时间发布者

  • 真正的事件发布者:RealApplicationEventPublisher(类似于Client),持有事件发布者ApplicationEventPublisher,调用publishEvent方法传参事件对象event。即作为ApplicationEvent的核心属性,事件源source

  • 广播事件发布器ApplicationEventMulticaster(类似于ObserverManagement),实现后具有事件发布的能力(类似于),在refresh的13行方法中完成初始化SimpleApplicationEventMulticaster或其它,内部有一个Map集合retrieverCache统一对全部的监听器进行管理,对管理器进行添加、移除和集体通知(multicastEvent方法)

  • ApplicationListener事件监听器(类似于Observer观察者),核心就是一个时间相应方法onApplicationEvent,被各类监听器实现,比如AbstractTypeAwareSupport(类似于ConcreateObserver观察者))

装饰者模式

装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,在现实生活中,这种情况也到处存在,例如一张照片,我们可以不改变照片本身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。

装饰模式是一种用于替代继承的技术,核心使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
设计模式(包括Spring)、贯穿项目梳理与源码知识点_第6张图片
● Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

● ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。

● Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。

● ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

不用改动原有代码,靠增加装饰者类完成,更符合开闭原则,继承会伴随着类爆炸问题,可完美避免
,例如Spring中Wrapper或Decorator结尾的类

外观模式

去茶馆喝茶,最简单的方式就是跟茶馆服务员说想要一杯什么样的茶,是铁观音、碧螺春还是西湖龙井?正因为茶馆有服务员,顾客无须直接和茶叶、茶具、开水等交互,整个泡茶过程由服务员来完成,顾客只需与服务员交互即可,整个过程非常简单省事, 在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及到的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)。如果没有外观类,那么每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大
设计模式(包括Spring)、贯穿项目梳理与源码知识点_第7张图片

享元模式

https://www.bilibili.com/video/BV1G4411c7N4?p=89&vd_source=c2a7fc0879fca3ca35e6d65757407476

内存属于计算机的“稀缺资源”,不应该用来“随便浪费”

当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。例如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用较多的内存空间,那么我们如何去避免系统中出现大量相同或相似的对象,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作?

享元模式通过共享技术实现相同或相似对象的重用,在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)。我们可以针对每一个不同的字符创建一个享元对象,将其放在享元池中,需要时再从享元池取出

享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Intrinsic State)和外部状态(Extrinsic State)

  • 旗子对象的本身的颜色是旗子的内部状态
  • 旗子在棋盘中所处的位置是旗子的外部状态

享元模式通常和工厂模式&单例模式搭配使用
设计模式(包括Spring)、贯穿项目梳理与源码知识点_第8张图片
Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类提供抽象方法供具体享元类设置自己的属性(内部状态,例如旗子颜色),同时也提供公用方法来设置外部数据(外部状态UnsharedConcreteFlyweight => Coordinate旗子的坐标信息)。

ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态(旗子颜色)提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。

UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。例如 Coordinate旗子的坐标信息

FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,内部维护一个集合类型的享元池用于存储具体的享元类,可以结合工厂方法模式进行设计;如果对象存在,则直接从享元池获取,如果对象不存在,先创建一个新的对象添加到享元池中,然后返回

职责链模式

一个软件系统中可以处理某个请求的对象不止一个,例如SCM系统中的采购单审批,主任、副董事长、董事长和董事会都可以处理采购单,他们可以构成一条处理采购单的链式结构,采购单沿着这条链进行传递,这条链就称为职责链。职责链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求。链上的每一个对象都是请求处理者,职责链模式可以将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理,客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,实现请求发送者和请求处理者解耦
设计模式(包括Spring)、贯穿项目梳理与源码知识点_第9张图片

  • Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在抽象处理者中定义了一个抽象处理者类型的对象(如结构图中的successor),作为其对下家的引用。定义审批处理方法HandleRequest(Request request)通过该引用,处理者可以连成一条链
  • ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。
abstract class Handler {
	//维持对下家的引用
    protected Handler successor;
	
	public void setSuccessor(Handler successor) {
		this.successor=successor;
	}
	
	public abstract void handleRequest(String request);
}


class ConcreteHandler extends Handler {
	public void handleRequest(String request) {
		if (请求满足条件) {
			//处理请求
		}
		else {
			this.successor.handleRequest(request);  //转发请求
		}
	}
}

具体处理者是抽象处理者的子类,它具有两大作用:第一是处理请求,不同的具体处理者以不同的形式实现抽象请求处理方法handleRequest();第二是转发请求,如果该请求具体处理者类的典型代码如下:

lhj模式1、统计数据导出(策略模式+模板方法)

消灭大量重复接口、重复流程代码,统一风格,高可读性,高扩展性,符合开闭原则

应用案例:新版统计数据导出

1、大一统接口:task,全部的导出功能共用的接口

2、service组件状态标识常量管理类ExportTaskType,22个组件代表22个service功能组件,意味着消灭了21个重复接口

3、前端公用查询基类StatisticsBasisVo,相当于对请求参数的公用提取

4、容器管理类:ProducerExportTaskContext

  • 主体结构PRODUCER_EXPORT_STRATEGY_MAP:ConcurrentHashMap类型的Service组件管理器,k是ExportTaskType常量标识,value代表真实的Service层的类(Class类型)
  • 注册方法registerProvider:再各个Service的impl组件的static方法中调用,意味着服务器启动时这些class类型的service组件就会被注册到PRODUCER_EXPORT_STRATEGY_MAP中
  • 分发方法distribute(reqVo):接口task中调用,根据reqVo中的ExportTaskType常量标识从PRODUCER_EXPORT_STRATEGY_MAP取出相应的service组件类,然后从Spring IOC容器中取出干活的service组件,然后执行其抽象父类的execute方法,来完成它自己的导出任务

5、ProducerExportStrategy导出生产者接口模型

6、ConsumerExportStrategy 导出消费者接口模型

7、泛型抽象类AbstractProducerExportTaskHandler,导出生产者端流程的抽象类,相当于这22处导出公共部分的抽取,

  • 核心execute方法:该方法集合了各个不共有的、需要子类各种实现的抽象方法和各种通用的方法组装成了一个核心通用的导出流程,该方法流程是1、时间校验checkDate;2、getCreateTime生成当前时间;3、获取创建文件的路径请求 4、生成文件上传地址;5、校验是否已有导出相同任务;6、获取导出数据大小;7、校验导出数据大小合法性,需要或者子类的相关信息;7.1是否需要限制7.2现在的最大量是多少;8、封装最终的请求参数;9、设置锁;10、向kafka发送请求消息; 11、设置返回信息;
  • 实现了ProducerExportStrategy接口,相当于该接口作为22个ConcreateProducerService的父接口,其意义在于注册到Spring IOC容器中统一使用该接口完成对这些组件的注入和管理
  • 泛型T继承自StatisticsBasisVo,22个service组件各种有对应的StatisticsBasisVo子类参数

8、导出公用逻辑服务者端接口ConsumerExportTaskService

9、导出公用逻辑消费者端接口ConsumerExportTaskService

10,AbstractConsumerExportTaskHandler: 导出消费者端流程抽象,同6

11 ConcreateProducerService:真正干活的导出生产者service组件,有22个负责不同业务模块的导出,基础自AbstractProducerExportTaskHandler,内部公其父类的execute方法,每个子类负责实现自己各种,大方面分按生产者和消费者为2类,消费者基础6,10,生产者继承5,7

12 ExportTaskListener监听kafka发送过来的消息,做消费,也就是数据处理

刘虹杰导出模式总结

  • 生产者 - 中间件 - 消费者模式
    • 把导出功能分开为两个主体,生成者
    • kafka充当中间件①解耦合,
  • 模板方法:消灭大量面向重复操作的方法,提高复用性,可读性
  • 分发者模式:消灭大量无意义接口,接口task作为分发者,内部调用distribute,通过前端传参标识,通过IOC容器技术结合自定义的service类管理容器从IOC容器取出相关的组件,实现一个controller管理全部业务的service而不是传统的 1对1或者1对多的关系;泛型及前端基类前来统一管理和前端传参

lhj模式2、无侵入转人工AMP系统(策略模式+模板方法、AOP动态代理)

应用设计模式:代理模式、AOP

背景:转人工逻辑

实战心得 && 组合技 => 面试有话说

转人工链路wzh

核心架构AOP通用服务代码和核心业务代码解耦合、更适合团队开发、提高可重用新),用作,代码结构采用策略模式+ 模板方法+建造者模式(模板方法是),小功能上使用了适配器,aop切面配置切点,拦截满足切点条件的方法,拦截方法后将其作为任务分发,任务分发依据是读取类中注解信息,注解信息中存储着service名称,从方法注解中取出service类名,再去Spring容器中取出相应的service类,service类本质是由之前一个个if-else判断拆分开来的,拆分后大量公用代码提前为公共方法,对应模板类的功能方法,非通用方法用抽象方法代替,表示具体实现流程需要延迟到子类去实现,然后将这一系列流程组装成为模板方法,作为一个个service业务类的模板,业务类本质上都是基于模板类的,业务类和模板类都是继承关系,即父子类关系,每一个service业务类都可以看做是基于父类模板方法基本算法框架,把需要补全的内容进行补全

  • AOP切面,面向切面编程,核心思想就是将业务逻辑与通用服务(日志、校验、权限认证、数据采集)完成解耦,实现方式是通过jdk动态代理(针对有接口的类,或者是织入,对应没有代理类)
    • 切面类,一个关注点的模块化,这个关注点可能会横切多个对象
      • Pointcut: 切点/切入点表达式,属于过滤器的一种实现,匹配过滤哪些类哪些方法需要被切面处理,通常使用注解的形式定义切点,用注解修饰的方法或者接口,所在类都会被生成代理对象存储到Spring中,执行这个方法时时间执行的就是代理类的代理方法
      • 横切关注点:对应四类通知方法,前置通知@Before,返回通知@AfterReturning,异常通知@AfterThrowing,后置通知@After,占用代理方法的不同位置
      • 连接点JointPoint:程序执行期间的某一点,每一个通知方法的每一个位置都是连接点
      • Joinpoint 连接点 :程序执行过程中的某一行为
      • Advice,切面对于某个“连接点”所产生的动作,四个通知方法
      • AOP Proxy,AOP代理:在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理,对满足切点过量条件的目标类,即被注解所修饰的目标类生成代理类,而后通过把满足切点条件的业务方法封装为代理方法,以jdk动态代理实现方式为例,代理方法其实是对核心业务方法进行包装,核心业务方法通过反射来调用,method.invoke来执行核心业务逻辑,在核心方法的四个位置,对应四个通知方法的业务插入到四个位置,这四个通知方法用来做通用服务
  • AOP服务拦截了几十个接口中的方法,针对方法的判断需要用到大量if-else或者switch-case,将n个if语句才分开m个类(n>=m),这类似于就是工厂模式针对简单工厂模式做出的升级,满足开闭原则
  • 我们往往定义一个接口(被各个类实现,然后用这个接口管理这一些列的类)/容器(本质是java集合,可以自定义容器,或者用Spring现成的容器+扫描注册的形式),本案例中用的是容器存储Service组件,Service组件则是对应具体的策略,业务方法中针对不同的情况(请求、入参),拿到对应的策略去处理,这就是策略模式,升级版的策略模式其实是自定义一个map容器,而后在impl实体bean组件类中,让组件类去实现InitializingBean,而后重写afterPropertiesSet方法,在该方法中将该类或其它相关类主键进行注册,获取时,直接在类中再定义一个相关的map组件容器,k是String类型代码beanName,value则是对应bean的类型,直接通过依赖注入的形式把容器中的策略bean统统注入,例如
   @Autowired
    private Map userInfoSessionFillServiceMap;

但每个serviceImpl中单独写方法去注册,请求可能是来自哪里呢?web浏览器最常见,但也可能是aop动态代理静态代理,中拿到的方法入参出参封装出来的请求,Spring代理模式是基于cglib或者jdk动态代理的,常见应用

  • 被拆分的类,很多公共属性和公共流程(方法),我们提取公共流程为一个公用父类的公用方法属性,比如入参的合法性验证,非公用流程父类中使用抽象方法,实现步骤需要推迟到子类具体问题具体分析,这样子类可以在不改变算法架构的情况下重新定义该算法的某些特征即可,这就是模板方法模式,其中,为了保证模板方法的通用性与扩展性,模板方法中的入参和出参都要选用高层的基类或抽象类,也可以使用泛型类,模板方法模式抽象中的模板类(模板方法)通常可以用作我们某个项目的基本架构

    • 梳理结构,参考抽象工厂模式,为了防止类爆炸,引入两个概念,工作族、产品等级结构,我们这个代码架构流程就是填充一个个ChatconnectBasic对象,这个看做是最终产品,每个Service则是对其中的属性进行填充,经过一些列Service的填充最终生成一个完整的对象,Service则是流水线上的填充机器,重复性的机器可以合而为一,减少类爆炸,流水线上会有很多很多Service来填充复杂产品,我们又把具有公共属性的类(OnlyRecordCreateChatTypeLink)提取到一起
  • 建造者模式

    • AbstractLinkLogHandler作为抽象建造者,里面的模板方法&通用方法均是构建(linkMsg)流程,各个Service子类作为具体构建者,这些子类共同完成linkMsg的构建,ChooseLinkLogAspect起到指挥者的角色,通过策略类管理器管理这一系列builder,然后根据不同的情况指挥对应的builder一步步完成构建工作,ChooseLinkLogAspect是负责和客户端直接交互的
    • @builder,本质上是生成了一个静态内部类XxxBuidler,这个是建造者角色,其中构建属性的方法均是User.UserBuilder,build()是返回最终构造好的对象(方法类型),形成链式编程,主要是从代码美观角度设计的,其中静态内部类XxxBuidler是不会再代码交互层面被调用的,是真正的类Xxx则是指挥者,相当于调用建造者XxxBuilder的这些api构建对象,类内部内部提供方法builder()是返回的是建造者对象,且XxxBuilder对客户端而言是不可见的,Xxx类与客户端进线交互
  • 关于CreateTypeFailResult的优化,此前针对每一种失败原因都对应个map属性,不美观,显得很多余,而且每初始化一个map类型属性会耗费一定资源,所以我们把这些个map都统一为1个,增加一个字段failType来标识识别类型,还有就行如果把时间看做资源,我们通过是从redis缓存中取数据,缓存中没数据才去mysql中去取,通过redis覆盖mysql的操作,大大节省了时间资源,这些都是蝇量模式的思想

  • 观察者模式升级版(监听者模式)

    • 最后关于linkMsgNode数据的推送问题,通过kafka作为中间件,生产者在发送数据时会以主动推送的形式退给消费者端,消费者收到数据时,会根据topicName取出对应的topic,这个topic便是主题(被观察者),然后给其注册观察者对象,这里是监听器的形式,并且在消费者中维护一个观察者管理器listenerMap;key是topic主题,value是绑定的监听器listener观察者提供,其中监听器提供数据入库操作方法,相当于是核心业务逻辑方法,主题则是有触发观察者的任务,这里不是触发,而是被中间件kafka推送消息的时候触发
    • Spring事务通知:由以下组件①事件对象,比如容器的启动、刷新、关闭…也可以自定义事件作为扩展②事件的发布者,通常是Spring容器担任事件发布者角色,时间发布后会被监听者获取并处理③事件监听者,即监听器,可以自定义监听器,并设置Spring容器中
  • Spring中bean中的scope取值Singleton,可以理解为工厂范围内的单例模式,也就是说一个beanName只能对应一个单实例对象,和从类的角度来看,可以存在多个类型一样的单例bean,只要是beanName不重复即可(一级缓存决定的),这是和传统单例模式最大区别,好处是

    • 相对于原型模式的bean,减少了初始化和实例化对象的开销
    • 减少了GC的压力
    • 利用单例模式的唯一性完成某些功能,比如配置事务管理器,事务管理器和jdbcTemplete对象需要装配完全相同的dataSource, 那么这种情况下dataSource的必须配置为单例对象

文本类指标导出lhj

项目架构核心技术Kafka(生产者消费者中间件模式)

  • 解耦:完成项目中业务功能的解耦进而完成代码结构上的,也就按预期功将项目划分为生产者和消费者模块,功能上生产者负责接收前端传参并拼接导出数据的筛选条件,校验导出数据的合法性,拼接excel文件的上传地址,消费者端拿到请求
  • 削峰:
  • 异步:接口执行完毕后就会给前端返回结果,前端那边给用户一个文件下载提示,剩下的就是等待消费者端的数据处理流程,具体时间最终是由当前消费者端的服务数目和当前导出大小决定的

生产者端

  • 十几个统计指标模块,每个模块正常情况下是对应一个接口的,但这十几个接口本身是同一类型重复性出现的,完全可以将其去重,去重步骤核心技术架构是策略模式+模板方法
    • 我们这里自定义一个容器作为策略集合,容器底层其实就是一个concurrentHashMap,k存储标识符,value存储Class类型的对象
    • 使用一个统一的接口,接口接收前端入参,接口用json格式接收入参,从入参中取出标识符,再去策略集合中取出策略类的class对象,在从Spring容器中取出service组件对象,去完成相关的业务处理
    • 导出业务的公共流程提取出来,比如校验接口请求参数的合法性、非通用流程作为抽象方法延迟到子类去具体实现、比如拼接文件上传地址

消费者端
本质是一个kafka的监听器,监听到生产者端的请求,获取请求然后根据请求去收集导出数据,消费者端的代码流程和生产者端是完全一致的,也是拆分成了很多service,这里的service都是泛型类,我们之前有对这些入参的公共参数提取作为统一的参数基类,这里泛型限定为基类的子类,service中对父类抽象方法的实现都是用的泛型类,service类声明中已经直接完成,通过泛型代替了类型转换相关操作,消费者端的核心逻辑就是①拿到查询条件从es中取出目标数据,然后用来做拼接

状态锁

源码应用设计模式应用补充

动态代理

  • 事务:Spring面试题
  • @Configuration:Spring首先判断配置类是否被@Configuration修饰,如果是被它修饰,就会生成这个配置类的代理对象,jdbcTemplete和transactionManager中都需要装配同一个数据源,也就都调用配置类中的datasource的配置方法,执行datasource配置方法的时候其实是通过代理对象去执行的,代理对象执行过程就是把方法名作为beanName去容器去取,本质也就是一级缓存中取,而且默认是单例对象,如果把@Configuration换成@Component方法,那么两者装配时调用datasource方法就是个普通方法
  • mybatis,通过mybatis的xml解析器或者java配置类解析器,解析mybatis的业务类,生成MapperProxyFactory相关对象存储起来,通过getMapper方法时底层会调用jdk动态代理生成相关mapper的代理类对象,后续会通过代理类对象去执行mapper方法,代理方法底层其实会调用执行器去执行相关sql方法

你可能感兴趣的:(设计模式,spring,java)