Java各类技能知识点学习链接大全:十一、设计模式

以下内容大多是学习链接,他人整理,个人收藏以便复习,同时归纳分享出来(如有不妥,原作者可随时联系本人删除,感谢!)

十一、设计模式

(一)、几大原则:

1、单一职责原则:Single Responsibility Principle,简称是SRP;

 一个类只负责一个功能领域中的相应职责。(就一个类而言,应该只有一个引起它变化的原因);一个类,或者一个模块,或者一个方法,承担的职责越多,被复用的可能性就越小。而且,一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响到其它职责。因此要讲这些职责进行分离,将不同的职责封装在不同的类中。如果多个职责总是同时发生改变,则可以将它们封装在同一个类中。

    单一职责原则是实现高内聚低耦合的指导方针,是最简单但又最难运用的原则,需要开发者发现类的不同职责并将其分离。

2、里氏替换原则:程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。

子类可以扩展父类的功能,但不能改变父类原有的功能;

3、依赖倒置原则:是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

4、接口隔离原则:客户端不应该依赖它不需要的接口。 一个类对另一个类的依赖应该建立在最小的接口上。 简介 使用多个专门的接口比使用单一的总接口要好;

5、迪米特原则:如果一个系统满足迪米特法则,那么当其中一个软件实体发生变化时,就会尽量少的影响其他软件实体,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。

迪米特法则可以降低系统的耦合度,使类与类之间保持松耦合状态。

6、开闭原则:在面向对象编程领域中,规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。

(二)具体设计模式:

创建型设计模式:

1、建造者(Builder)模式

Java 大白话讲解设计模式之 -- 建造者(Builder)模式 - 简书   

秒懂设计模式之建造者模式(Builder pattern) - 知乎 (zhihu.com)

(jdk源码中建造者模式,,HttpClientBuilder,还有HystrixCommand的构造方法,都是这种设计模式)

StringBuffer 和 StringBuiler 是建造者模式吗?

 

“ StringBuilder”是Builder设计模式的应用程序吗? (qastack.cn)

2、单例模式:

(1)、为什么推荐用枚举作为单例模式:(java中源码的Runtime、Calendar都是单例模式)

为什么要用枚举实现单例模式(避免反射、序列化问题) - 李子沫 - 博客园

为什么用枚举类来实现单例模式越来越流行? - 知乎

枚举实现单例模式 - 简书

(2)、枚举方式实现单例:

枚举实现单例模式 - 简书

(3)、单例模式应用场景:

a、网站的计数器,一般也是采用单例模式实现,否则难以同步。 

b、应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 

c、 Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 

d、数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。 

e、多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。  

f、操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。 

(4)、单例模式在jdk源码中的体现:

Calendar日期类,Calendar.getInstance();

Runtime类(饿汗单例模式,获取cpu数、系统环境变量、java版本等等系统信息):
 

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
}

3、工厂方法模式:(spring就是用工厂方法模式来创建bean)

使用场景:比如不同风控结果评分的维度,我们推荐选择不同的消费贷产品,我们可以使用工厂模式生产出不同的策略,同时使用策略模式来进行不同的策略执行;

设计模式在外卖营销业务中的实践 - 美团技术团队 (meituan.com)

java工厂模式三种详解(部分转载)_llussize的博客-优快云博客_java工厂模式

4、原型模式:

在java中我们知道通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),在我们需要大量对象的情况下,原型模式就是我们可以考虑实现的方式。
原型模式我们也称为克隆模式,即一个某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。

实现原型模式:实现cloneable接口以及用object的clone方法;

原型模式的克隆方式有两种:浅克隆和深度克隆
浅克隆:只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝;
深度克隆 :深复制把要复制的对象所引用的对象都复制了一遍;

jdk源码中克隆模式:java.lang.Cloneable

Spring 源码中的原型模式(Prototype 模式),区别于单利模式:

参考:原型模式(Prototype Pattern)_深情以改的博客-优快云博客_原型模式

(1)、原型模式的优点
a、性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。

b、逃避构造函数的约束
这既是它的优点也是它的缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。

(2)、原型模式的使用场景
a、资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

b、性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

c、一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为一体,大家可以随手拿来使用。

结构型设计模式:

5、Adapter适配器模式(Wrapper包装模式):把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。

Carson带你学设计模式:适配器模式(Adapter Pattern) - 简书

业务场景:如何在业务开发中使用适配器模式? - 掘金 (juejin.cn)

java源码中用到的适配器,FutureTask类中,两个构造方法:

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}
     */
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

换成 Callable 接口,调用call方法,其实就是调用了Runnable的 run方法:

    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

6、Decorator  Pattern装饰者模式 ( *Wrapper.java ):

我们知道面向对象设计最基本的原则之一就是对扩展开放,对修改关闭,如果仅仅使用继承那么必然不会有很好的扩展性,尤其是我们想给对象增加功能时,装饰者模式也就因此而出现了
动态的给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活;

在已有功能的基础之上,动态地添加更多 功能的一种方式,这些新加的代码装饰了原有类的 核心职责或主要行为;

装饰者模式-JDK中InputStream和HttpServletRequsetWrapper源码分析 - 无可奈何SOS - 博客园 (cnblogs.com)

装饰者模式和代理模式的区别:

对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。他们之间的边界确实比较模糊,两者都是对类的方法进行扩展,具体区别如下:

(1)、装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

(2)、装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;

(3)、装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;

7、Facade Pattern  门面模式(外观模式):

门面模式又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。让子系统更容易使用,属于结构式模式。

引入一个中间类,把各个纷繁的功能组合成一个整体,只对外暴露一个统一的接口。

​目的:为了用户使用方便,把过度拆分的分散功能,组合成要给整体,对外提供一个统一的接口。

门面模式的优缺点
优点:
(1)、简化了调用过程,无需深入了解子系统,以防给子系统带来风险

(2)、减少系统依赖,松散耦合,不用关系子系统的调用顺序

(3)、更好地划分访问层次,提高了安全性

(4)、遵循迪米特法则,即最少知道原则

缺点:
(1)、当增加子系统和扩展子系统行为时,可能容易带来未知风险

(2)、不符合开闭原则

(3)、某些情况下可能违背单一职责

Java设计模式之(九)——门面模式 - 腾讯云开发者社区-腾讯云 (tencent.com)

JAVA设计模式之门面模式(外观模式)_一个本科小生的奋斗史-优快云博客

Spring JDBC中的JdbcUtils对原生的JDBC进行封装,让调用者统一访问。

public abstract class JdbcUtils {

    //公共关闭链接
    public static void closeConnection(Connection con) {
        if (con != null) {
            try {
                con.close();
            }
            catch (SQLException ex) {
            }
            catch (Throwable ex) {
            }
        }
    }

    //获取结果集的某一条数据
    public static Object getResultSetValue(ResultSet rs, int index) throws SQLException {
		Object obj = rs.getObject(index);
		String className = null;
		if (obj != null) {
			className = obj.getClass().getName();
		}
		if (obj instanceof Blob) {
			obj = rs.getBytes(index);
		}
		else if (obj instanceof Clob) {
			obj = rs.getString(index);
		}
		else if (className != null &&
				("oracle.sql.TIMESTAMP".equals(className) ||
				"oracle.sql.TIMESTAMPTZ".equals(className))) {
			obj = rs.getTimestamp(index);
		}
		else if (className != null && className.startsWith("oracle.sql.DATE")) {
			
		}
		else if (obj != null && obj instanceof java.sql.Date) {
			if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {
				obj = rs.getTimestamp(index);
			}
		}
		return obj;
	}
   //省略...
}

门面模式和代理模式的区别:

代理模式侧重于对原对象的访问控制;

代理模式会实现原对象的接口;

代理模式只代理一个接口;

门面模式更侧重于整合子系统资源;

门面模式和子系统对象具有不同的接口抽象;

门面模式代理的是一系列接口。

8、享元模式:

享元(Flyweight)的核心思想很简单:如果一个对象实例一经创建就不可变,那么反复创建相同的实例就没有必要,直接向调用方返回一个共享的实例就行,这样即节省内存,又可以减少创建对象的过程,提高运行速度。在实际应用中,享元模式主要应用于缓存,即客户端如果重复请求某些对象,不必每次查询数据库或者读取文件,而是直接返回内存中缓存的数据。

享元 - 廖雪峰的官方网站

jdk源码中的享元模式:Integer类valueOf这个方法中它会先判断传进去的值是否在IntegerCache中,如果不在就创建新的对象,在就直接返回缓存池里的对象。这个valueOf方法就用到享元模式。它将-128到127的Integer对象先在缓存池里创建好,等我们需要的时候直接返回即可。所以在-128到127中的数值我们用valueOf创建会比new更快。

大幅度地降低内存中对象的数量,减缓堆内存压力 (下面  return new Integer(i)中,也体现了静态工厂方法模式)

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

String的常量池即用到了享元模式,String.intern():扩充常量池,native方法。调用时查看常量池中是否有相同的字符串常量,如果有返回常量池中的字符串的引用;如果没有,在常量池中增加一个相等的字符串,并返回常量池中字符串的引用

public native String intern();

 9、组合模式:

设计模式(十)——组合模式(HashMap源码解析) - linzm14 - 博客园 (cnblogs.com)

行为型设计模式:

10、模板方法模式(Template Method)

我们先定义一套固定的规则标准,然后让不确定的方法设置为抽象的,等到具体的实现时候,让子类再去实现。先设置好一套规则,定义好一套业务流程,再去具体实现。这就是模板方法;

父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。

最大的好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好了。

模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。子类实现算法的某些细节,有助于算法的扩展。

通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。

Java设计模式之模板方法模式(Template Method)_一个本科小生的奋斗史-优快云博客_java 模板方法

模板方法模式jdk源码中实现:AbstractList、AQS(抽象队列同步器);

HashMap实现的模板方法模式:

在HashMap中很多方法都会调用afterNodeAccess、afterNodeInsertion、afterNodeRemoval方法,而这些方法HashMap本身并没有实现,对于这些函数HashMap的子类,LinkedHashMap实现了这些方法;

模板方法模式:结合JDK源码深入理解(TemplateMethod)_goodluckwj的博客-优快云博客

JdbcTemplate 也采用了模板方法模式:

模板模式之spring的jdbcTemplate_lzf的博客-优快云博客_jdbctemplate模板模式

11、策略模式 Strategy:
当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能;

如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题;(if  else 问题)

如果继续增加if/else,会显得该类特别庞大,难以维护;

处理逻辑全部放在一个类中,会导致整个类特别庞大;

业务场景1:房贷产品,根据不同的产品,选择不同的支付路由通道(把if else 硬编码,改变成一个个匹配的类,)

业务场景2:

假设我们要处理一个office文件,分为三种类型 docx、xlsx、xls(2003)、ppt,分别表示Word文件、Excel文件、PPT等文件,根据文件后缀分别解析

策略模式优点:

(1)、策略模式符合开闭原则。(

                开闭原则定义:
                开闭原则规定"软件中的对象(类,模块,函数等等)应该对于扩展是开放的,

                但是对于修改是封闭的";

                软件设计本身所追求的目标就是封装变化、降低耦合,

                而开放封闭原则正是对这一目标的最直接体现;
                其他的设计原则,很多时候是为实现这一目标服务的;
                做到不对原有系统修改的前提下, 实现灵活的扩展. 这就是开闭原则要实现的。

                )

(2)、避免使用多重条件转移语句,如if...else...语句、switch 语句

(3)、使用策略模式可以提高算法的保密性和安全性。

(4)扩展性良好、增加一个策略,只需要实现一个接口即可。

策略模式缺点:

(1)、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。

(2)、代码中会产生非常多策略类,增加维护难度。
 

12、责任链模式:

责任链责任链,主要是体现在一个链字上面。也就是前后之间将要形成一条链;

这个概念很抽象,估计大家看了也很难理解,其实不用想的那么复杂。相信大家都有做过表单校验的工作,假如此时需要做一个登陆校验(用户名、密码、验证码),首先肯定是校验用户名,校验通过则进入下一步校验密码,否则提示用户,校验密码用过,则校验验证码,否则提示用户,表单全部校验通过,才开始提交到后台。其实这也是一个典型的责任链设计模式的运用。

实战:设计模式之责任链设计模式深度解析 - 知乎 (zhihu.com)

策略模式和责任链模式区别,两者之间的区别:
职责链模式
解决更多的是条件属于模糊的判断,执行不同的逻辑,将判断条件内置于函数中,
条件不符合需要链式传递继续执行函数,有上下责任链执行顺序;

策略模式
有确定的判断条件,比如if/else 或者 switch...case直接根据确定的判断条件调用

策略模式jdk源码模式: ThreadPoolExecutor中的四种拒绝策略就是使用策略模式:

https://matthung0807.blogspot.com/2018/02/java-strategy-pattern.html

策略模式在JDK以及Spring源码中的应用_蒙奇D灬小武的博客-优快云博客

策略模式在JDK源码中的应用

设计模式 | 策略模式及典型应用 - 腾讯云开发者社区-腾讯云 (tencent.com)

其他:

设计模式在外卖营销业务中的实践:

设计模式在外卖营销业务中的实践 - 美团技术团队 (meituan.com)

 JDK中其他设计模式

JDK中设计模式_星星的技术专栏-优快云博客_jdk设计模式

生产者消费者模式(不属于GOF 二十三种设计模式里面的设计模式)

什么是生产者-消费者模式:
比如有两个进程A和B,它们共享一个固定大小的缓冲区,A进程产生数据放入缓冲区,B进程从缓冲区中取出数据进行计算或者消费,那么这里其实就是一个生产者和消费者的模式,A相当于生产者,B相当于消费者;

为什么要使用生产者消费者模式?
在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完了数据才能够继续生产数据,因为生产那么多也没有地方放啊;同理如果消费者的速度大于生产者那么消费者就会经常处理等待状态,所以为了达到生产者和消费者生产数据和消费数据之间的平衡,那么就需要一个缓冲区用来存储生产者生产的数据,所以就引入了生产者-消费者模式

简单来说这里的缓冲区的作用就是为了平衡生产者和消费者的处理能力,起到一个数据缓存的作用,同时也达到了一个解耦的作用;

jdk中生产者消费者模式源码应用体现:线程池中的阻塞队列;

参考链接:经典并发同步模式:生产者-消费者设计模式 - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值