Java设计模式

Java设计模式_第1张图片

学习《设计模式-可复用面向对象软件的基础》一书,这本书用Smalltalk语言描述,有些不明觉厉。不过类图画的很好。翻译的也一般。
建议根据开源项目源码学习设计模式,在见到***Builder、***Factory之类的源码后,再临时看相关设计模式,便于结合实际理解。如果为了学习设计模式而死记硬背各种定义,那就如同薯片里的空气—鼓鼓囊囊又啥也没有。
策略模式和发布订阅模式提到了一对多的消息通知问题,,用了引用,回头再看看这部分

设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,而应实现一个热插拔的效果。这样使得程序易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

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

3、依赖倒转原则(Dependence Inversion Principle)

依赖倒转原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

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

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

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

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

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

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

创建型模式

工厂模式(Factory Pattern)

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

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

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

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

有人提出用反射来创建实例,但这样工厂就显得多余,因为既然知道了类名、类的创建方式来反射,本可以直接创建而不需要工厂。

而且对于.net中的一个例子:WebRequest req = WebRequest.Create("http://ccc......"),根据传入的参数的不同,创建不同协议(如http和ftp)的对象。此时反射的例子不适用这种情景,因为程序猿连具体要获得什么类的对象都不知道。(我们可以用接口接收其实现类,所以调用方法时不必知道具体要什么类,直接用别人封装的返回值就行了)

抽象工厂模式

工厂比简单工厂多一层抽象,而抽象工厂再多一层

模式 特征
简单工厂(非23种设计模式) 具体工厂,生产产品
工厂 工厂类有统一接口,生产一类产品(鼠标;颜色)
抽象工厂 同类工厂每个厂可以生产多个产品,或一个产品可以多种品牌

总之就是遇事不决多加一层

产品 工厂
耳麦接口 工厂接口内加入生产耳麦方法
实现戴尔耳麦、惠普耳麦 实现惠普工厂、戴尔工厂,实现生产方法

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

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

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

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

一、一句话概括工厂模式

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

二、生活中的工厂模式

  • 简单工厂类:一个麦当劳店,可以生产多种汉堡。
  • 工厂方法类:一个麦当劳店,可以生产多种汉堡。一个肯德基店,也可以生产多种汉堡。
  • 抽象工厂类:百胜餐饮集团下有肯德基和百事公司,肯德基生产汉堡,百事公司生成百事可乐。

例子

例子:vue3、java jwt、spring beanFactory、线程池、数据库连接池、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);
	}
}

建造者/生成器模式 Builder Pattern

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

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

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

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

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

StringBulider, SqlSessionFactoryBuilder

Java设计模式_第2张图片

经常见到建造者模式建造工厂,,,

案例:Mybatis源码

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

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 源码

单例模式(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;  
    }  
}

行为型模式

观察者模式 发布/订阅模式

观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被完成业务的更新

一个对象(被观察者)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。它的主要成员就是观察者和被观察者

通知由被观察者发出,观察者在接收到通知之后,执行提前定义好的业务

登陆注册应该是最常见的业务场景了

假如把注册后的通知、关联业务都放在注册的方法内。那么将来增加业务(如发优惠券)需要修改这里的代码,违反开闭原则

如果相关业务太多,可以改成异步的方式,通过消息队列实现发布、订阅

生产者消费者也是一种发布、订阅。只是理解的角度不同

监听器Listener也算是一种

mq那个解耦的叫发布订阅模式,和观察者有区别的。观察者是穷举一个集合遍历的

可以用spring实现

同步阻塞方式的观察者模式

直接可以通过springApplicationContextAware,初始化观察者列表,然后用户注册成功,通知观察者即可。

虽然解决了开闭原则的问题,但同步阻塞的话,一步异常可能全部失败。

异步非阻塞的观察者模式

开个线程、消息队列

策略模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

可以解决if-else太多的问题

策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。

策略模式针对一组算法,将每一个算法封装到实现共同接口的不同独立的类中,从而使得它们可以相互替换。

  • 一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法)
  • 不同策略的差异化实现(就是说,不同策略的实现类)

**关键代码:**实现同一个接口。

**注意事项:**如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

多类型处理器策略模式的结构如图所示。

Java设计模式_第3张图片

**策略模式:**是一种行为型模式,能定义一系列算法,并将每种算法分别放入独立的类中,从而使算法的对象能够互相替换。

**场景介绍:**在MyBatis 处理JDBC 执行后返回的结果时,需要按照不同的类型获取对应的值,这样就可以避免大量的if 判断。所以,这里基于TypeHandler 接口对每个参数类型分别做了自己的策略实现。

**同类场景:**PooledDataSource、UnpooledDataSource、BatchExecutor、ResuseExecutor、SimpleExector、CachingExecutor、LongTypeHandler、StringTypeHandler 和DateTypeHandler。

责任链模式

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

**意图:**避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

**何时使用:**在处理消息时有许多处理步骤

**如何解决:**拦截的类都实现统一接口。然后根据这一接口拦截?

**关键代码:**Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

  • 这个聚合自己是什么意思?怎么实现?

应用实例:

已有应用:

2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,jsp servlet 的 Filter;

可以应用的场景:

不同级别的日志记录;参数校验

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

缺点: 1、不能保证请求一定被接收。?什么意思,,,怎么就不能接收了, 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。因为拆解了步骤所以性能有损耗么,, 3、可能不容易观察运行时的特征,有碍于除错。

使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

比如审批流程,有一系列处理步骤,而且每一步都有可能打回;

责任链模式与if…else…相比,他的耦合性要低一些,因为它把条件判定都分散到了各个处理类中,并且这些处理类的优先处理顺序可以随意设定。责任链模式也有缺点,这与if…else…语句的缺点是一样的,那就是在找到正确的处理类之前,所有的判定条件都要被执行一遍,当责任链比较长时,性能问题比较严重。

  • 看来还有能有索引一样直接找到条件的模式,,不知道那种模式能不能改造if-else

实现案例

一个抽象类,几个实现类。

抽象类结构类似链表,有next指向下一个,setNext()方法设置下一项;

demo里都是一个一个setNext,hhh,,

另一个demo

    ├── impl
    │  ├── Level1AuthLink.java
    │  ├── Level2AuthLink.java
    │  └── Level3AuthLink.java
    ├── AuthInfo.java
    └── AuthLink.java

其他技巧

抽象类可以使用泛型

模板模式

SQL 执行模板模式如图9所示。

Java设计模式_第4张图片

图9

**模板模式:**是一种行为型模式,在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。场景介绍:存在一系列可被标准定义的流程,并且流程的步骤大部分采用通用逻辑,只有一小部分是需要子类实现的,通常采用模板模式来定义这个标准的流程。就像MyBatis 的BaseExecutor 就是一个用于定义模板模式的抽象类,在这个类中把查询、修改的操作都定义为一套标准的流程。

**同类场景:**BaseExecutor、SimpleExecutor 和BaseTypeHandler。

迭代器模式

拆解字段解析实现的结构如图11所示。

Java设计模式_第5张图片

图11

**迭代器模式:**是一种行为型模式,能在不暴露集合底层表现形式的情况下遍历集合中的所有元素。

**场景介绍:**PropertyTokenizer 用于MyBatis 的MetaObject 反射工具包下,用来解析对象关系的迭代操作。这个类在MyBatis 中使用得非常频繁,包括解析数据源配置信息并填充到数据源类上,同时参数的解析、对象的设置都会使用这个类。

**同类场景:**PropertyTokenizer。

结构型模式

适配器模式 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。

组合模式

**组合模式:**是一种结构型模式,可以将对象组合成树形结构以表示“部分—整体” 的层次结构。

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

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

装饰器模式

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

Java设计模式_第6张图片

**装饰器模式:**是一种结构型设计模式,允许将对象放入包含行为的特殊封装对象中, 为元对象绑定新的行为。

**场景介绍:**MyBatis 的所有SQL 操作都是经过SqlSession 调用SimpleExecutor 完成的, 而一级缓存的操作也是在简单执行器中处理的。这里的二级缓存因为是基于一级缓存刷新的,所以在实现上,通过创建一个缓存执行器,包装简单执行器的处理逻辑,实现二级缓存操作。这里用到的就是装饰器模式,也叫俄罗斯套娃模式。

过滤器模式

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

模板方法模式

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

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

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);
}

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

你可能感兴趣的:(java,设计模式,开发语言)