常见设计模式

(Liskov Substitution Principle,LSP)

是继承复用的基石,说白了就是继承与派生的规则

里氏替换原则核心:在软件系统中,一个可以接受父类对象的地方必然可以接受子类对象

里氏替换原则实例:某系统需要实现画直线功能,现在有DrawLineA和DrawLineB两种画图方式,在操作类中提供了这两种画图方法选择画直线

现在我需要改变或添加一种画直线方式来画直线,比如原先使用DrawLineA方式进行画直线现在更换为DrawLineB方式来画直线,如果直接修改操作类的代码就违背了开闭原则。现在使用里氏替换原则重构代码, 既可以方便了系统扩展,又遵循了开闭原则

重构后的方案图如下图所示

所以说里氏替换原则是实现开闭原则的重要方法之一

三、依赖倒置原则

(Dependence Inversion Principle)

依赖倒置也叫依赖注入、依赖倒转

要针对抽象层编程,不要针对具体类编程

依赖倒置原则核心:要依赖于抽象,不要依赖于具体的实现。

分开来说:(注:抽象:接口或抽象类;细节:具体实现;如果把模块层次关系比作基础关系的话:高层模块和底层模块对应于父类和子类)

一、高层模块不应该依赖底层模块,这两者应该依赖与其抽象

二、抽想不应该依赖细节

三、细节应依赖抽象

依赖倒置实例:

比如某系统可以从本地获取和服务器获取数据,后将数据可以为转化配置成XML文件和XLS文件

假设我们增加了数据源或者有新的转化格式,需要修改操作类里面的源代码,这样就违背了开闭原则

现在使用依赖倒置原则对其进行重构

重构方案图如下图所示

四、接口隔离原则

(Interface Segregation Principle, ISP)

使用多个专门接口来取代一个统一的接口,

一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中。

接口隔离原则核心:不应该强迫客户端程序依赖他们 不需要的使用方法

接口隔离原则实例:

比如一个系统中有一个大的接口,这个接口包含了GameobjectA、GameobjecB、GameobjectC三个对象的接口

但是这三个对象中有些接口未必需要实现,现在使用接口隔离原则将接口隔离,这样又满足单一职责原则

重新构造的方案如下图所示

五、合成/聚合复用原则

(Composite/Aggregate Reuse Principle,CARP)

在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过组合或通过继承。

继承复用:实现简单,便于扩展。但是破坏系统的封装性。

组合复用:耦合性相对较低,选择性的调用成员对象的操作。可以再运行时动态运。.

合成复用核心:尽量使用对象组合而不是继承达到复用的目的

六、迪米特法则

(Law of Demeter LoD)

又叫做最少知识原则,一个软件实体对其他实体的引用越少越好,或者说如果两个类不必直接通信,那么这两个类就不应当发生直接的相互作用,而是通过一个第三者发生间接性的交互

迪米特原则核心:高内聚,低耦合

低迷特原理实例:

比如某一系统有多个系统和多个数据源,他们之间的联系图如下:

这样比较杂乱,耦合性较高,使用迪米特原则后的构造方案如下图所示:

七、单一职责原则

(Simple responsibility pinciple SRP)

  • 一个类只负责一个领域的内容,简而言之就是自己的类负责自己的事情,与别的类互不干涉

如果一个类具有多个职责,应该把这多个职责分离出去,再分别创建一些类去一一完成这些职责

换句话说就是一个类的职责要单一,不能将太多的职责放在一个类中

单一职责核心:高内聚、低耦合

如下图是一个类画线的实现:

但是功能太过集中,严重违背了单一职责原则,重构后如下图所示

单例模式(常用)

  • 单例模式使⽤场景:

  • 业务系统全局只需要⼀个对象实例,⽐如发号器、 redis 连接对象等

  • Spring IOC容器中的 bean 默认就是单例

  • spring boot 中的controller、service、dao层中通过 @autowire的依赖注⼊对象默认都是单例的

  • 单例模式分类:

  • 懒汉:就是所谓的懒加载,延迟创建对象,需要用的时候再创建对象

  • 饿汉:与懒汉相反,提前创建对象

  • 单例模式实现步骤:

  • 私有化构造函数

  • 提供获取单例的⽅法

懒汉式

懒汉式有以下⼏种实现⽅式:

/**

  • @Description: 单例设计模式-懒汉式

*/

public class SingletonLazy {

// 当需要用到该实例的时候再创建实例对象

private static SingletonLazy instance;

/**

  • 构造函数私有化

  • 不能通过 new SingletonLazy() 的方式创建实例

  • 当需要用到该实例的时候在加载

  • 只能通过 SingletonLazy.getInstance() 这种方式获取实例

*/

private SingletonLazy() {

}

/**

  • 单例对象的方法

*/

public void process() {

System.out.println(“方法实例化成功!”);

}

/**

  • 方式一:

  • 对外暴露一个方法获取该类的对象

  • 缺点:线程不安全,多线程下存在安全问题

  • @return

*/

public static SingletonLazy getInstance() {

if (instance == null) {// 实例为null时候才创建

/**

  • 线程安全问题:

  • 当某一时刻,两个或多个线程同时判断到instance == null成立的时候

  • 这些线程同时进入该if判断内部执行实例化

  • 则会新建出不止一个SingletonLazy实例

*/

instance = new SingletonLazy();// 当需要的时候再进行实例化对象

}

return instance;

}

/**

  • 方式二:

  • 通过加synchronized锁 保证线程安全

  • 采用synchronized 对方法加锁有很大的性能开销

  • 因为当getInstance2()内部逻辑比较复杂的时候,在高并发条件下

  • 没获取到加锁方法执行权的线程,都得等到这个方法内的复杂逻辑执行完后才能执行,等待浪费时间,效率比较低

  • @return

*/

public static synchronized SingletonLazy getInstance2() {

if (instance == null) {// 实例为null时候才创建

// 方法上加synchronized锁后可以保证线程安全

instance = new SingletonLazy();// 当需要的时候再进行实例化对象

}

return instance;

}

/**

  • 方式三:

  • 在getInstance3()方法内,针对局部需要加锁的代码块加锁,而不是给整个方法加锁

  • 也存在缺陷:

  • @return

*/

public static SingletonLazy getInstance3() {

if (instance == null) {// 实例为null时候才创建

// 局部加锁后可以保证线程安全,效率较高

// 缺陷:假设线程A和线程B

synchronized (SingletonLazy.class){

// 当线程A获得锁的执行权的时候B等待 A执行new SingletonLazy();实例化

// 当A线程执行完毕后,B再获得执行权,这时候还是可以实例化该对象

instance = new SingletonLazy();// 当需要的时候再进行实例化对象

}

}

return instance;

}

}

懒汉实现+双重检查锁定+内存模型

对于上面方式三存在的缺陷,我们可以使用双重检查锁定的方式对其进行改进:

/**

  • 方式三改进版本:

  • 在getInstance3()方法内,针对局部需要加锁的代码块加锁,而不是给整个方法加锁

  • DCL 双重检查锁定 (Double-Checked-Locking) 在多线程情况下保持高性能

  • 这是否安全? instance = new SingletonLazy(); 并不是原子性操作

  • jvm中 instance实例化内存模型流程如下:

  • 1.分配空间给对象

  • 2.在空间内创建对象

  • 3.将对象赋值给instance引用

  • 假如出现如下顺序错乱的情况:

  • 线程的执行顺序为:1 -> 3 -> 2, 那么这时候会把值写回主内存

  • 则,其他线程就会读取到instance的最新值,但是这个是不完全的对象

  • (指令重排现象)

  • @return

*/

public static SingletonLazy getInstance3plus() {

if (instance == null) {// 实例为null时候才创建

// 局部加锁后可以保证线程安全,效率较高

// 假设线程A和线程B

synchronized (SingletonLazy.class){// 第一重检查

// 当线程A获得锁的执行权的时候B等待 A执行new SingletonLazy();实例化

// 当A线程执行完毕后,B再获得执行权,这时候再判断instance == null是否成立

// 如果不成立,B线程无法 实例化SingletonLazy

if (instance == null){// 第二重检查

instance = new SingletonLazy();// 当需要的时候再进行实例化对象

}

}

}

return instance;

}

再次升级方式三,来解决内存模型中的指令重排问题:

// 添加volatile 关键字,禁止实例化对象时,内存模型中出现指令重排现象

private static volatile SingletonLazy instance;

/**

  • 方式三再次升级版本:

  • 在getInstance3()方法内,针对局部需要加锁的代码块加锁,而不是给整个方法加锁

  • DCL 双重检查锁定 (Double-Checked-Locking) 在多线程情况下保持高性能

  • 解决指令重排问题——禁止指令重排

  • @return

*/

public static SingletonLazy getInstance3plusplus() {

if (instance == null) {// 实例为null时候才创建

// 局部加锁后可以保证线程安全,效率较高

// 假设线程A和线程B

synchronized (SingletonLazy.class){// 第一重检查

// 当线程A获得锁的执行权的时候B等待 A执行new SingletonLazy();实例化

// 当A线程执行完毕后,B再获得执行权,这时候再判断instance == null是否成立

// 如果不成立,B线程无法 实例化SingletonLazy

if (instance == null){// 第二重检查

instance = new SingletonLazy();// 当需要的时候再进行实例化对象

}

}

}

return instance;

}

懒汉式调用:

@Test

public void testSingletonLazy(){

SingletonLazy.getInstance().process();

}

什么是指令重排 ? 可以参考 https://blog.csdn.net/qq_51998352/article/details/117451728

饿汉式

/**

  • @Description: 单例设计模式-饿汉式

*/

public class SingletonHungry {

// 当类加载的时候就直接实例化对象

private static SingletonHungry instance = new SingletonHungry();

private SingletonHungry(){}

/**

  • 单例对象的方法

*/

public void process() {

System.out.println(“方法实例化成功!”);

}

public static SingletonHungry getInstance(){

return instance;// 当类加载的时候就直接实例化对象

}

}

饿汉式调用:

@Test

public void testSingletonHungry(){

SingletonHungry.getInstance().process();

}

  • 饿汉式单例模式,当类加载的时候就直接实例化对象,因此不需要考虑线程安全问题。

  • 优点:实现简单,不需要考虑线程安全问题

  • 缺点:不管有没有使用该对象实例,instance对象一直占用着这段内存

  • 懒汉与饿汉式如何选择?

  • 如果对象内存占用不大,且创建不复杂,直接使用饿汉的方式即可

其他情况均采用懒汉方式(优选)

工厂模式(常用)

  • ⼯⼚模式介绍:

  • 它提供了⼀种创建对象的最佳⽅式,我们在创建对象时 不会对客户端暴露创建逻辑,并且是通过使⽤⼀个共同 的接⼝来指向新创建的对象。

  • 例⼦:

  • ⼯⼚⽣产电脑,除了A品牌、还可以⽣产B、C、D品牌 电脑;

  • 业务开发中,⽀付很常⻅,⾥⾯有统⼀下单和⽀付接 ⼝,具体的⽀付实现可以微信、⽀付宝、银⾏卡等;

  • ⼯⼚模式有 3 种不同的实现⽅式:

  • 简单工厂模式:通过传⼊相关的类型来返回相应的类,这 种⽅式⽐较单 ⼀,可扩展性相对较差;

  • 工厂方法模式:通过实现类实现相应的⽅法来决定相应 的返回结果,这种⽅式的可扩展性⽐较强;

  • 抽象工厂模式:基于上述两种模式的拓展,且⽀持细化 产品;

  • 应⽤场景:

  • 解耦:分离职责,把复杂对象的创建和使⽤的过程分开

  • 复⽤代码、降低维护成本:

  • 如果对象创建复杂且多处需⽤到,如果每处都进⾏编写,则很多重复代码,如果业务逻辑发⽣了改 变,需⽤四处修改;

  • 使⽤⼯⼚模式统⼀创建,则只要修改⼯⼚类即可, 降低成本;

简单工厂模式
  • 简单⼯⼚模式(静态工厂)

  • ⼜称静态⼯⼚⽅法, 可以根据参数的不同返回不同类的实例,专⻔定义⼀个类来负责创建其他类的实例,被创建的实例通常都具有共同的⽗类;

  • 由于⼯⼚⽅法是静态⽅法,可通过类名直接调⽤,⽽且只需要传⼊简单的参数即可;

  • 核⼼组成

  • Factory:⼯⼚类,简单⼯⼚模式的核⼼,它负责实现 创建所有实例的内部逻辑

  • IProduct:抽象产品类,简单⼯⼚模式所创建的所有对象的⽗类,描述所有实例所共有的公共接⼝

  • Product:具体产品类,是简单⼯⼚模式的创建⽬标

  • 实现步骤

  • 创建抽象产品类,⾥⾯有产品的抽象⽅法,由具体的产 品类去实现

  • 创建具体产品类,继承了他们的⽗类,并实现具体⽅法

  • 创建工厂类,提供了⼀个静态⽅法createXXX()⽤来⽣产产品,只需要传⼊你想产品名称

  • 优点:

  • 将对象的创建和对象本身业务处理分离可以降低系统的 耦合度,使得两者修改起来都相对容易。

  • 缺点:

  • ⼯⼚类的职责相对过重,增加新的产品需要修改⼯⼚类的判断逻辑,这⼀点与开闭原则是相违背

  • 开闭原则(Open Close Principle):对扩展开放,对 修改关闭,程序需要进⾏拓展的时候,不能去修改原有 的代码,实现⼀个热拔插的效果

  • 将会增加系统中类的个数,在⼀定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不⽤模式

代码实现

创建IProduct 抽象产品接口——IPay

public interface IPay {

/**

  • 统一下单

*/

void unifiedOrder();

}

创建Product具体产品类——AliPay/WeChatPay

//AliPay.java

//@Description: 支付宝支付具体实现类

public class AliPay implements IPay{

@Override

public void unifiedOrder() {

System.out.println(“支付宝支付统一下单…”);

}

}

//WeChatPay.java

//@Description: 微信支付具体实现类

public class WeChatPay implements IPay{

@Override

public void unifiedOrder() {

System.out.println(“微信支付统一下单…”);

}

}

创建Factory工厂类——SimplePayFactory

//@Description: 简单支付工厂类(静态工厂类)

public class SimplePayFactory {

/**

  • 工厂创建方法:

  • 根据参数返回对应的支付对象

*/

public static IPay createPay(String payType) {

if (payType == null) {

return null;

} else if (payType.equalsIgnoreCase(“WECHAT_PAY”)) {

return new WeChatPay();

} else if (payType.equalsIgnoreCase(“ALI_PAY”)) {

return new AliPay();

}

// 如果需要扩展,可以编写更多

return null;

}

}

测试使用简单支付工厂

@Test

public void testSimplePayFactory(){

IPay wechat_pay = SimplePayFactory.createPay(“WECHAT_PAY”);

IPay ali_pay = SimplePayFactory.createPay(“ALI_PAY”);

wechat_pay.unifiedOrder();

ali_pay.unifiedOrder();

}

// 输出结果:

// 微信支付统一下单…

// 支付宝支付统一下单…

上述就是工厂设计模式——简单工场(静态工厂的一个简单使用例子),那么我们来分析下其缺点与不足之处:

需求

  • 如果我需要额外再添加一个A银行的银行卡支付,那么就需要在SimplePayFactory 类中添加响应的判断逻辑,比如再加一个if判断,添加一个A银行支付的逻辑

  • 而如果再需要一个B银行的银行卡支付,那么还需要再添加一个if判断 添加一个B银行支付的逻辑,依次加下去…

  • 那么这就违背了⼯⼚类要遵循的开闭原则(Open Close Principle)(对扩展开放,对修改关闭,程序需要进⾏拓展的时候,不能去修改原有的代码,实现⼀个热插拔的效果),这样就导致,每次扩展功能的时候都需要添加新的逻辑,并且需要对工厂类进行修改,如果是真实复杂的业务,这就增加了成本。

下面我们来看一下工厂方法模式是如何解决简单工厂模式的这一缺点

工厂方法模式

⼯⼚⽅法模式

  • ⼜称⼯⼚模式,是对简单⼯⼚模式的进⼀步抽象化,其 好处是可以使系统在不修改原来代码的情况下引进新的 产品,即满⾜开闭原则

  • 通过⼯⼚⽗类定义负责创建产品的公共接⼝,通过⼦类 来确定所需要创建的类型

  • 相⽐简单⼯⼚⽽⾔,此种⽅法具有更多的可扩展性复用性,同时也增强了代码的可读性

  • 将类的实例化(具体产品的创建)延迟到⼯⼚类的⼦类 (具体⼯⼚)中完成,即由子类来决定应该实例化哪⼀ 个类

核⼼组成

  • IProduct:抽象产品接口,描述所有实例所共有的公共接⼝

  • Product:具体产品类,实现抽象产品类的接⼝,⼯⼚ 类创建对象,如果有多个需要定义多个

  • IFactory:抽象⼯⼚接口,描述具体⼯⼚的公共接⼝

  • Factory:具体⼯⼚类,实现创建产品类对象,实现抽 象⼯⼚类的接⼝,如果有多个需要定义多个

要实现工厂方法模式,只需要在原来的简单工厂模式基础上,做出改进,而之前我们创建的IPay抽象产品接口和AliPay WeChatPay 两个具体产品类不需要改动

首先创建IPayFactory抽象⼯⼚接口:

//@Description: 抽象⼯⼚接口

public interface IPayFactory {

IPay getPay();

}

然后创建AliPayFactoryWeChatFactory 两个具体⼯⼚类:

//@Description: 具体工厂类 AliPayFactory

public class AliPayFactory implements IPayFactory{

@Override

public IPay getPay() {

return new AliPay();

}

}

//@Description: 具体工厂类 WeChatFactory

public class WeChatFactory implements IPayFactory{

@Override

public IPay getPay() {

return new WeChatPay();

}

}

进行测试:

@Test

public void testMethodPayFactory(){

AliPayFactory aliPayFactory = new AliPayFactory();

IPay ali_pay = aliPayFactory.getPay();

ali_pay.unifiedOrder();// 输出:支付宝支付统一下单…

WeChatFactory weChatFactory = new WeChatFactory();

IPay wechat_pay = weChatFactory.getPay();

wechat_pay.unifiedOrder();// 输出:微信支付统一下单…

}

工厂方法模式思路如下图:

工厂方法模式优点

  • 符合开闭原则,增加⼀个产品类,只需要实现其他具体的产品类和具体的⼯⼚类;

  • 符合单⼀职责原则,每个⼯⼚只负责⽣产对应的产品;

  • 使⽤者只需要知道产品的抽象类,⽆须关⼼其他实现 类,满⾜迪⽶特法则、依赖倒置原则和⾥⽒替换原则

  • 迪⽶特法则 :最少知道原则,实体应当尽量少地与 其他实体之间发⽣相互作⽤;

  • 依赖倒置原则:针对接⼝编程,依赖于抽象⽽不依 赖于具体;

  • ⾥⽒替换原则:俗称LSP, 任何基类可以出现的地 ⽅,⼦类⼀定可以出现, 对实现抽象化的具体步骤的 规范;

抽象工厂方法模式

抽象工厂方法模式是简单工厂模式 和工厂方法模式的整合升级版

⼯⼚模式有 3 种不同的实现⽅式:

  • 简单⼯⼚模式:通过传⼊相关的类型来返回相应的类,这 种⽅式⽐较单 ⼀,可扩展性相对较差;

  • ⼯⼚⽅法模式:通过实现类实现相应的⽅法来决定相应 的返回结果,这种⽅式的可扩展性⽐较强;

  • 抽象⼯⼚模式:基于上述两种模式的拓展,是⼯⼚⽅法 模式的升级版,当需要创建的产品有多个产品线时使⽤ 抽象⼯⼚模式是⽐较好的选择

  • 抽象⼯⼚模式在 Spring 中应⽤得最为⼴泛的⼀种设计模式

背景:

  • ⼯⼚⽅法模式引⼊⼯⼚等级结构,解决了简单⼯⼚模式 中⼯⼚类职责过重的问题

  • 但⼯⼚⽅法模式中每个⼯⼚只创建⼀类具体类的对象, 后续发展可能会导致工厂类过多,因此将⼀些相关的具 体类组成⼀个“具体类族”,由同⼀个⼯⼚来统⼀⽣产, 强调的是⼀系列相关的产品对象!!!

实现步骤:

  • 1、定义两个接⼝ IPay(支付)、IRefund(退款)

  • 2、创建具体的Pay产品、创建具体的Refund产品

  • 3、创建抽象⼯⼚ IOrderFactory 接⼝ ⾥⾯两个⽅法 createPay/createRefund

  • 4、创建⽀付宝产品族AliOderFactory,实现OrderFactory 抽象⼯⼚

  • 5、创建微信⽀付产品族WechatOderFactory,实现 OrderFactory抽象⼯⼚

  • 6、定义⼀个超级⼯⼚创造器FactoryProducer,通过传递参数获取对应的⼯⼚

接下来我们就按照步骤使用一下抽象工厂方法模式:

1、定义两个接⼝ IPay(支付)、IRefund(退款)

//@Description: 支付抽象接口

public interface IPay {

/**

  • 统一下单

*/

void unifiedOrder();

}

// @Description: 退款抽象接口

public interface IReFund {

/**

  • 退款

*/

void refund();

}

2、创建具体的Pay产品、创建具体的Refund产品:

AliPay/WeChatPay:支付宝支付和微信支付

// @Description: 支付宝支付具体实现类

public class AliPay implements IPay {

@Override

public void unifiedOrder() {

System.out.println(“支付宝支付 统一下单接口…”);

}

}

// @Description: 微信支付具体实现类

public class WeChatPay implements IPay {

@Override

public void unifiedOrder() {

System.out.println(“微信支付统一下单…”);

}

}

AliRefund/WeChatFund:支付宝退款和微信退款

public class AliRefund implements IReFund {

@Override

public void refund() {

System.out.println(“支付宝退款…”);

}

}

public class WeChatRefund implements IReFund {

@Override

public void refund() {

System.out.println(“微信支付退款…”);

}

}

3、创建抽象⼯⼚ IOrderFactory 接⼝ ⾥⾯两个⽅法 createPay/createRefund:

// @Description: 订单抽象工厂,一个超级工厂可以创建其他工厂(又被称为其他工厂的工厂)

public interface IOrderFactory {

IPay createPay();

IReFund createRefund();

}

4、创建⽀付宝产品族AliOderFactory,实现OrderFactory 抽象⼯⼚:

public class AliOrderFactory implements IOrderFactory {

@Override

public IPay createPay() {

return new AliPay();

}

@Override

public IReFund createRefund() {

return new AliRefund();

}

}

5、创建微信⽀付产品族WechatOderFactory,实现 OrderFactory抽象⼯⼚

public class WeChatOrderFactory implements IOrderFactory {

@Override

public IPay createPay() {

return new WeChatPay();

}

@Override

public IReFund createRefund() {

return new WeChatRefund();

}

}

6、定义⼀个超级⼯⼚创造器FactoryProducer,通过传递参数获取对应的⼯⼚

public class FactoryProducer {

public static IOrderFactory getFactory(String type){

if (type.equalsIgnoreCase(“WECHAT”)){

return new WeChatOrderFactory();

}else if (type.equalsIgnoreCase(“ALI”)){

return new AliOrderFactory();

}

return null;

}

}

最后我们来进行测试:

@Test

public void testAbstractMethodPayFactory(){

IOrderFactory wechatPayFactory = FactoryProducer.getFactory(“WECHAT”);

wechatPayFactory.createPay().unifiedOrder();

wechatPayFactory.createRefund().refund();

IOrderFactory aliPayFactory = FactoryProducer.getFactory(“ALI”);

aliPayFactory.createPay().unifiedOrder();

aliPayFactory.createRefund().refund();

}

结果如下:

微信支付统一下单…

微信支付退款…

支付宝支付 统一下单接口…

支付宝退款…

  • ⼯⼚⽅法模式和抽象⼯⼚⽅法模式

  • 当抽象⼯⼚模式中每⼀个具体⼯⼚类只创建⼀个产品对 象,抽象⼯⼚模式退化成⼯⼚⽅法模式

  • 优点

  • 当⼀个产品族中的多个对象被设计成⼀起⼯作时,它能 保证使⽤⽅始终只使⽤同⼀个产品族中的对象

  • 产品等级结构扩展容易,如果需要增加多⼀个产品等 级,只需要增加新的⼯⼚类和产品类即可, ⽐如增加银 ⾏⽀付、退款

  • 缺点

  • 产品族扩展困难,要增加⼀个系列的某⼀产品,既要在 抽象的⼯⼚和抽象产品⾥修改代码,不是很符合开闭原 则

  • 增加了系统的抽象性和理解难度

建造者模式(常用)

建造者模式简介

  • 建造者模式(Builder Pattern)

  • 使用多个简单的对象一步一步构建成一个复杂的对象,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

  • 允许用户只通过指定复杂对象的类型和内容就可以构建它们,不需要知道内部的具体构建细节

  • 场景举例

  • KFC创建套餐:套餐是一个复杂对象,它一般包含主食如汉堡、烤翅等和饮料 如果汁、 可乐等组成部分,不同的套餐有不同的组合,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐

  • 电脑有低配、高配,组装需要CPU、内存、电源、硬盘、主板等

  • 核心组成

  • Builder:抽象建造者,定义多个通用方法和构建方法

  • ConcreteBuilder:具体建造者,可以有多个

  • Director:指挥者,控制整个组合过程,将需求交给建造者,由建造者去创建对象

  • Product:产品角色

通过图再来理解下:

建造者模式案例

首先来创建产品类Computer (电脑)

// @Description: 产品类 Computer

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Computer {

private String cpu;

private String memory;

private String mainboard;

private String disk;

private String power;

接下来创建抽象建造者接口Builder

/**

  • @Description: 抽象建造者接口

  • 声明建造者的公共方法

*/

public interface Builder {

/**

  • 接口方法

*/

void buildCpu();

void buildMainBoard();

void buildDisk();

void buildPower();

void buildMemory();

Computer createComputer();

}

然后是创建具体建造者类A (高配电脑建造者)HighComputerBuilder 和 具体建造者类B(低配电脑建造者) LowComputerBuilder

/**

  • @Description: 具体建造者类 A :高配电脑建造者

*/

public class HighComputerBuilder implements Builder {

private Computer computer = new Computer();

@Override

public void buildCpu() {

computer.setCpu(“高配 CPU”);

}

@Override

public void buildMainBoard() {

computer.setMainboard(“高配 主板”);

}

@Override

public void buildDisk() {

computer.setDisk(“高配 磁盘”);

}

@Override

public void buildPower() {

computer.setPower(“高配 电源”);

}

@Override

public void buildMemory() {

computer.setMemory(“高配 内存”);

}

@Override

public Computer createComputer() {

return computer;// 返回computer对象

}

}

/**

  • @Description: 具体建造者类 B :低配电脑建造者

*/

public class LowComputerBuilder implements Builder {

private Computer computer = new Computer();

@Override

public void buildCpu() {

computer.setCpu(“低配 CPU”);

}

@Override

public void buildMainBoard() {

computer.setMainboard(“低配 主板”);

}

@Override

public void buildDisk() {

computer.setDisk(“低配 磁盘”);

}

@Override

public void buildPower() {

computer.setPower(“低配 电源”);

}

@Override

public void buildMemory() {

computer.setMemory(“低配 内存”);

}

@Override

public Computer createComputer() {

return computer;// 返回computer对象

}

}

最后是指挥者类**Director ** 控制产品生产过程:

/**

  • @Description: 指挥者类:控制整个组合过程,将需求交给建造者,由建造者去创建对象

  • 将产品和创建过程进行解耦,使用相同的创建过程创建不同的产品,控制产品生产过程

  • Director 是全程指导组装过程,具体细节还是由builder 去操作

*/

public class Director {

public Computer create(Builder builder){

builder.buildCpu();

builder.buildDisk();

builder.buildMainBoard();

builder.buildMemory();

builder.buildPower();

return builder.createComputer();

}

}

完成后我们来进行测试:

@Test

public void testBuilder(){

Director director = new Director();// 声明指挥者

// 指挥者指挥构建低配电脑

Computer low_computer = director.create(new LowComputerBuilder());

// 指挥者指挥构建高配电脑

Computer high_computer = director.create(new HighComputerBuilder());

System.out.println(low_computer);

System.out.println(high_computer);

}

输出结果:

Computer{cpu=‘低配 CPU’, memory=‘低配 内存’, mainboard=‘低配 主板’, disk=‘低配 磁盘’, power=‘低配 电源’}

Computer{cpu=‘高配 CPU’, memory=‘高配 内存’, mainboard=‘高配 主板’, disk=‘高配 磁盘’, power=‘高配 电源’}

小结

  • 建造者模式优点

  • 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦

  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,更加精细地控制产品的创建过程

  • 增加新的具体建造者无须修改原有类库的代码,符合开闭原则

  • 建造者模式结合链式编程来使用,代码上更加美观

  • 建造者模式缺点

  • 建造者模式所创建的产品一般具有较多的共同点,如果产品差异大则不建议使用

  • JDK里面的应用

  • tcp传输协议 protobuf 生成的api、java中的StringBuilder(不完全一样,思想一样)

  • 建造者模式与抽象工厂模式的比较:

  • 建造者模式返回一个组装好的完整产品 , 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族

  • 建造者模式是把对象的创建分散开来,每个抽象方法负责其中的一部分抽象工厂是每个方法负责一个产品族

  • 建造者模式所有函数加到一起才能生成一个对象。抽象工厂一个函数生成一个对象。

适配器模式(常用)

  • 适配器模式(Adapter Pattern

  • 见名知意,是作为两个不兼容的接口之间的桥梁,属于结构型模式

  • 适配器模式使得原本由于接⼝不兼容⽽不能⼀起⼯作的那些类可以⼀起⼯作

  • 常见的⼏类适配器

  • 类的适配器模式

想将⼀个类转换成满⾜另⼀个新接⼝的类时,可以使⽤类的适配器模式,创建⼀个新类,继承原 有的类,实现新的接⼝即可

  • 对象的适配器模式

想将⼀个对象转换成满⾜另⼀个新接⼝的对象时,可以创建⼀个适配器类,持有原类的⼀个实例,在适配器类的⽅法中,调⽤实例的⽅法就⾏

  • 接⼝的适配器模式

不想实现⼀个接⼝中所有的方法时,可以创建⼀ 个Adapter,实现所有⽅法,在写别的类的时 候,继承Adapter类即可

  • 应⽤场景

  • 电脑需要读取内存卡的数据,读卡器就是适配器

  • ⽇常使⽤的转换头,如电源转换头,电压转换头

  • 系统需要使⽤现有的类,⽽这些类的接⼝不符合系统的 需要

  • JDK中InputStreamReader就是适配器

  • JDBC就是我们⽤的最多的适配器模式

JDBC给出⼀个客户端通⽤的抽象接⼝,每⼀个具体数据库⼚商

如 SQL Server、Oracle、MySQL等,就会开发JDBC驱动,

就是⼀个介于JDBC接⼝和数据库引擎接⼝之间的适配器软件

接口的适配器案例

  • 接口的适配器

  • 有些接⼝中有多个抽象⽅法,当我们写该接⼝的实现类时,必须实现该接⼝的所有⽅法, 这明显有时⽐较浪费,因为并不是所有的⽅法都是我们需要的,有时只需要实现部分接⼝就可以了

代码实现

自定义一个支付网关接口PayGateWay

/**

  • @Description: 支付网关接口

*/

public interface PayGateWay {

/**

  • 下单

*/

void unifiedOrder();

/**

  • 退款

*/

void refund();

/**

  • 查询支付状态

*/

void query();

/**

  • 发红包

*/

void sendRedPack();

}

再来一个视频产品订单类ProductVideoOrder 去实现 PayGateWay 接口

/**

  • @Description: 视频产品订单

*/

public class ProductVideoOrder implements PayGateWay{

}

当我们实现该接口的时候,默认就需要实现接口中的所有抽象方法。而实际上如果我们只需要在ProductVideoOrder

实现下单unifiedOrder() 和refund() 退款功能,而并不需要多余的查询支付状态 query(); 和 发红包sendRedPack();功能,那么如何解决呢?

这就需要用到一个适配器,适配器去实现PayGateWay 接口的所有方法,ProductVideoOrder 只需要继承 该适配器,就可以按需重写相关方法即可:

/**

  • @Description: 支付网关接口的适配器

*/

public class PayGateWayAdapter implements PayGateWay{

@Override

public void unifiedOrder() {

}

@Override

public void refund() {

}

@Override

public void query() {

}

@Override

public void sendRedPack() {

}

}

然后在ProductVideoOrderProductVipOrder 按需重写响应的方法:

/**

  • @Description: 超级会员用户产品订单

*/

public class ProductVipOrder extends PayGateWayAdapter{

@Override

public void unifiedOrder() {

super.unifiedOrder();

}

@Override

public void refund() {

super.refund();

}

@Override

public void sendRedPack() {

super.sendRedPack();

}

/**

  • @Description: 视频产品订单

*/

public class ProductVideoOrder extends PayGateWayAdapter{

@Override

public void unifiedOrder() {

System.out.println(“产品下单…”);

}

@Override

public void refund() {

System.out.println(“产品退款…”);

}

}

类的适配器实战

  • 类的适配器模式

  • 想将⼀个类转换成满足另⼀个新接⼝的类时,可以使用类的适配器模式,创建⼀个新类,继承原有的类,实现 新的接口即可

首先,假设我们在升级或者优化一个项目时,需要将老的模块中的相关功能进行升级扩展,这个功能目前是由模块中的某个类OldModule进行实现。

/**

  • @Description:

*/

public class OldModule {

public void methodA(){

System.out.println(“OldModule 中的 methodA…”);

}

}

那么我们如何在不改动老模块中OldModule类的前提下,对其所负责的业务功能进行扩展升级呢?

接下来,我们先将要扩展升级的相关功能方法,包括现有功能的方法在目标接口TargetModule中进行定义

/**

  • @Description:

*/

public interface TargetModule {

/**

  • 和需要适配的类的方法名一样

*/

public void methodA();

/**

  • 新的方法,如果有多个新的方法直接编写即可

*/

public void methodB();

public void methodC();

}

然后通过类的适配器,进行适配OldModuleTargetModule,这样就可以做到将⼀个类转换成满⾜另⼀个新接⼝的类,且不改动原有类

/**

  • @Description: 类的适配器

*/

public class Adapter extends OldModule implements TargetModule{

/**

  • 注意: 已经在OldModule中实现的 methodA()是不用再实现的

*/

/**

  • 新的方法B

*/

@Override

public void methodB() {

System.out.println(“Adapter 中的 methodB…”);

}

/**

  • 新的方法C

*/

@Override

public void methodC() {

System.out.println(“Adapter 中的 methodC…”);

}

}

测试一下效果:

@Test

public void testClassAdapater(){

TargetModule targetModule = new Adapter();

targetModule.methodA();

targetModule.methodB();

targetModule.methodC();

}

输出结果:

OldModule 中的 methodA…

Adapter 中的 methodB…

Adapter 中的 methodC…

这样就做到了 在不改动原来OldModule 类以及其中相关功能方法的前提下,对其进行扩展实现TargetModule 接口中新增加的业务功能!

代理模式(常用)

1、静态代理

静态代理角色分析:

  • 抽象角色 :一般使用接口或者抽象类来实现。

  • 真实角色 :被代理的角色。

  • 代理角色: 代理真实角色 , 代理真实角色后 ,一般会做一些附属的操作。

  • 调用方:使用代理角色来进行一些操作。

我们以租客租客租房子为例,涉及到的对象有:租客、中介、房东。(房东即为被代理对象,中介即为代理对象)

租客通过中介之手租住房东的房子,代理对象中介需要寻找租客租房,并从中获取中介费用

代码实现

Rent.java 即抽象角色

// 抽象角色:租房

public interface Rent {

public void rent();

}

Host.java 即真实角色

// 真实角色: 房东,房东要出租房子

public class Host implements Rent{

@Override

public void rent() {

System.out.println(“房屋出租”);

}

}

Proxy.java 即代理角色

//代理角色:中介

public class Proxy implements Rent {

private Host host;

public Proxy() { }

public Proxy(Host host) {

this.host = host;

}

// 租房

@Override

public void rent(){

seeHouse();

host.rent();

fare();

}

// 看房

public void seeHouse(){

System.out.println(“带房客看房”);

}

// 收中介费

public void fare(){

System.out.println(“收中介费”);

}

}

Client.java 调用方,即客户

// 客户类,一般客户都会去找代理!

public class Client {

public static void main(String[] args) {

// 房东要租房

Host host = new Host();

// 中介帮助房东

Proxy proxy = new Proxy(host);

// 你去找中介!

proxy.rent();

}

}

静态代理的缺点:

  • 需要手动创建代理类,如果需要代理的对象多了,那么代理类也越来越多。

为了解决,这个问题,就有了动态代理 !

2、动态代理

说到动态代理,面试的时候肯定会问动态代理的两种实现方式:

先来看公共的 UserService 接口,和 UserServiceImpl 实现类:

public interface UserService {

/**

  • 登录

*/

void login();

/**

  • 登出

*/

void logout();

}

public class UserServiceImpl implements UserService{

@Override

public void login() {

System.out.println(“用户登录…”);

}

@Override

public void logout() {

System.out.println(“用户推出登录…”);

}

}

JDK 动态代理

public class JDKProxyFactory implements InvocationHandler {

// 目标对象(被代理对象)

private Object target;

public JDKProxyFactory(Object target) {

super();

this.target = target;

}

/**

  • 创建代理对象

  • @return

*/

public Object createProxy() {

// 1.得到目标对象的类加载器

ClassLoader classLoader = target.getClass().getClassLoader();

// 2.得到目标对象的实现接口

Class<?>[] interfaces = target.getClass().getInterfaces();

// 3.第三个参数需要一个实现invocationHandler接口的对象

Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);

return newProxyInstance;

}

/**

  • 真正执行代理增强的方法

  • @param proxy 代理对象.一般不使用

  • @param method 需要增强的方法

  • @param args 方法中的参数

  • @return

*/

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println(“JDK 动态代理:登录/登出前逻辑校验…”);

Object invoke = method.invoke(target, args);

System.out.println(“JDK 动态代理:登录/登出后日志打印…”);

return invoke;

}

public static void main(String[] args) {

// 1.创建对象

UserServiceImpl userService = new UserServiceImpl();

// 2.创建代理对象

JDKProxyFactory jdkProxyFactory = new JDKProxyFactory(userService);

// 3.调用代理对象的增强方法,得到增强后的对象

UserService userServiceProxy = (UserService) jdkProxyFactory.createProxy();

userServiceProxy.login();

System.out.println(“==================================”);

userServiceProxy.logout();

}

}

输出结果:

JDK 动态代理:登录/登出前逻辑校验…

用户登录…

JDK 动态代理:登录/登出后日志打印…

==================================

JDK 动态代理:登录/登出前逻辑校验…

用户推出登录…

JDK 动态代理:登录/登出后日志打印…

CGLIB 动态代理

public class CglibProxyFactory implements MethodInterceptor {

// 目标对象(被代理对象)

private Object target;

// 使用构造方法传递目标对象

public CglibProxyFactory(Object target) {

super();

this.target = target;

}

/**

  • 创建代理对象

  • @return

*/

public Object createProxy() {

// 1.创建Enhancer

Enhancer enhancer = new Enhancer();

// 2.传递目标对象的class

enhancer.setSuperclass(target.getClass());

// 3.设置回调操作

enhancer.setCallback(this);

return enhancer.create();

}

/**

  • 真正执行代理增强的方法

  • @param o 代理对象

  • @param method 要增强的方法

  • @param objects 要增强方法的参数

  • @param methodProxy 要增强的方法的代理

  • @return

  • @throws Throwable

*/

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println(“cglib 动态代理:登录/登出前逻辑校验…”);

Object invoke = method.invoke(target, objects);

System.out.println(“cglib 动态代理:登录/登出后日志打印…”);

return invoke;

}

public static void main(String[] args) {

// 1.创建对象

UserServiceImpl userService = new UserServiceImpl();

// 2.创建代理对象

CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(userService);

// 3.调用代理对象的增强方法,得到增强后的对象

UserService userServiceProxy = (UserService) cglibProxyFactory.createProxy();

userServiceProxy.login();

System.out.println(“==================================”);

userServiceProxy.logout();

}

}

输出结果:

cglib 动态代理:登录/登出前逻辑校验…

用户登录…

cglib 动态代理:登录/登出后日志打印…

==================================

cglib 动态代理:登录/登出前逻辑校验…

用户推出登录…

cglib 动态代理:登录/登出后日志打印…

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。

《Java高级面试》

《Java高级架构知识》

《算法知识》

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
xyFactory.createProxy();

userServiceProxy.login();

System.out.println(“==================================”);

userServiceProxy.logout();

}

}

输出结果:

JDK 动态代理:登录/登出前逻辑校验…

用户登录…

JDK 动态代理:登录/登出后日志打印…

==================================

JDK 动态代理:登录/登出前逻辑校验…

用户推出登录…

JDK 动态代理:登录/登出后日志打印…

CGLIB 动态代理

public class CglibProxyFactory implements MethodInterceptor {

// 目标对象(被代理对象)

private Object target;

// 使用构造方法传递目标对象

public CglibProxyFactory(Object target) {

super();

this.target = target;

}

/**

  • 创建代理对象

  • @return

*/

public Object createProxy() {

// 1.创建Enhancer

Enhancer enhancer = new Enhancer();

// 2.传递目标对象的class

enhancer.setSuperclass(target.getClass());

// 3.设置回调操作

enhancer.setCallback(this);

return enhancer.create();

}

/**

  • 真正执行代理增强的方法

  • @param o 代理对象

  • @param method 要增强的方法

  • @param objects 要增强方法的参数

  • @param methodProxy 要增强的方法的代理

  • @return

  • @throws Throwable

*/

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println(“cglib 动态代理:登录/登出前逻辑校验…”);

Object invoke = method.invoke(target, objects);

System.out.println(“cglib 动态代理:登录/登出后日志打印…”);

return invoke;

}

public static void main(String[] args) {

// 1.创建对象

UserServiceImpl userService = new UserServiceImpl();

// 2.创建代理对象

CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(userService);

// 3.调用代理对象的增强方法,得到增强后的对象

UserService userServiceProxy = (UserService) cglibProxyFactory.createProxy();

userServiceProxy.login();

System.out.println(“==================================”);

userServiceProxy.logout();

}

}

输出结果:

cglib 动态代理:登录/登出前逻辑校验…

用户登录…

cglib 动态代理:登录/登出后日志打印…

==================================

cglib 动态代理:登录/登出前逻辑校验…

用户推出登录…

cglib 动态代理:登录/登出后日志打印…

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-37BE9YL5-1713665036995)]

[外链图片转存中…(img-U9VfzGRW-1713665036996)]

[外链图片转存中…(img-wsSij3UR-1713665036996)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。

《Java高级面试》

[外链图片转存中…(img-RzrhL69r-1713665036996)]

《Java高级架构知识》

[外链图片转存中…(img-4TF178SF-1713665036997)]

《算法知识》

[外链图片转存中…(img-wUWvbEQ7-1713665036997)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值