Java设计模式
解决普遍存在的问题,反复出现的各种问题,所提出的解决方案。
设计模式七大原则
- 设计模式七大原则:
- 单一职责
- 接口隔离
- 依赖倒转
- 里氏替换
- 开闭原则
- 迪米特法则
- 合成复用原则
面向对象 => 功能模块[设计模式+算法] => 框架[调用多种模式] => 架构[服务器集群]
单一职责原则
- 降低类的复杂度,一个类只负责一项职责
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下,应当遵守单一职责原则,保有逻辑足够简单,才可以在代码级违法单一职责原则,只有类中方法数量足够少,可以在方法级别保持单一职责原则
接口隔离原则
将一个接口的功能进行拆分,让其成模块化的。这里的例子就是,一个接口有五个方法,如果两个类继承了,此接口,则需要实现十次。有些方法是用不到的。所以需要将折口拆分成几个接口。
![](https://i-blog.csdnimg.cn/blog_migrate/21eca1491c337603386cee5d86e82c4c.png)
这样的使用的和维护的时候,更有针对性,而且耦合度更低。当然,其缺点就是可能会造成模块粒度不可控。
依赖倒转原则
将一个类型抽象为接口,这类型都是相同的属性,具有不同的细节特性。
如果动物 下 有 狗和猫,其都有走路和吃食的 属性(方法),但吃的不一样,所以需要不同的实现。
但是整体业务是一样的,只需要更换不同的实现。面向接口编程就如同面向规范编程,不同产商同一标准,生产相同的零件,可能是颜色不一样,Logo不一样。
下面只是 依赖关系 传递的一种操作。
- 接口传递
- 构造方法传递
- setter传递
总结:
- 底层最好是抽象类或接口,程序稳定性更好
- 变量的声明类型尽量是抽象类和接口,这样在使用时,就是可以利用多态的特性,而不是直接把接口写死,利于后期代码的更新和维护。
- 继承时遵循晨氏替换原则
里氏替换原则
这个我的总结就是不应该破坏原有的封装。在添加新的功能时,应当考虑原有代码的这个封装和可能存在的误导性。新的类或者方法需要与之前的模块进行一定的区分和隔离。
这个隔离,就是把原有的继承关系,变成依赖关系。即可以重写方法,又可以使用相关性非常高的原有的继承关系。
开闭原则
这原则的核心。
很像是依整倒转原则,不过基类变成了抽象类。
主要是提供使用接口,让其不需要为每个使用到的类而重写一个功能方法,直接使用抽象类的方法就可以了。
迪米特法则
又称最少知道原则。
简单理解为,如果不依赖,就不要在本类创建其任何相关的局部变量。
不依赖的意思就是不是直接朋友,不是直接朋友就是不依赖。
老师内,有直接依赖学生,所以老师可以在局部创建学生的变量,而学生并不依赖老师,所以在使用的时候,不能直接创建老师的局部变量。
所以这种操作,可以转为 其他的直接朋友进行操作,就是一个主抽象类,即依赖了老师,又依赖了学生,那么这种局部的操作,可以转给此类进行操作。
- 迪米特法则的核心是降低类之间的耦合
- 由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间耦合关系,并不是要求公开赛没有依赖关系。
合成复用原则
原则是尽量使用全成/聚合的方式,而不是使用继承。
就是说,新手可能在使用一个类的时候会直接使用继承,这样显然是主动耦合了,在维护上会出现很大的关联性的问题。一般在使用一个类的时候,一般使用 依赖,合成 和 聚合。
总结
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 为了交互对象之间的松耦合设计而努力。
设计模式
类型
- 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
- 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)
创建型模式
单例模式
懒汉式(线程安全,同步方法)
- 解决了线程不安全问题,但效率太低了,也不推荐,但我记录下。
双重检查
- DoubleCheck概念是多线程使用的
- 再次判断,线程安全,延迟加载,效率较高
实际开发中,非常推荐
静态内部类
使用了JVM的装载机制,静态内部类在父类装载时并不被实例化,而是在需要实例化时,调用 GetInstance方法,才会装载SIngletonInstance类。
优点:使用JVM的机制实现了懒加载,并且是线程安全的。
推荐使用
单例枚举
能避免多线程同步的总理 ,还能防止反序列化重新创建新的对象
《effective Java》作者推荐
推荐使用
总结
单例模式注意事项和细节说明:
-
单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
-
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
-
单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多,但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。
Runtime 使用的是 饿汉式 单列模式的编写方法。
每次使用的时候都需要考虑使用场景。
工厂模式
通过一个工厂类,获取不通的解决方案。
工厂可以是类也可以是方法。
最好的例子,就是造包子,不同的馅料。
工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类。
![](https://i-blog.csdnimg.cn/blog_migrate/380496dab4d2d25c42b7c668ce366303.png)
抽象工厂方法
![](https://i-blog.csdnimg.cn/blog_migrate/8ae53f1ffcab7133610c6e06d7922af9.png)
虽然这个抽象方法也就是一个接口,但其是用了几个工厂类的实现。
就是进一步的将工厂进行抽象了,先是基于工厂为基础进行开发。而抽象工厂,则是直接将这个抽象工厂作为一个中心。(这个图中的UML是错的)
静态工厂
在一个里有一个静态方法用来将输入的数字yyyyMMdd转成LocalDate
抽象工厂
这里的抽象工厂的作用就是将使用的工厂和接口都抽象出来,然后每种类型都需要不同的实现。
测试 方法
工厂类接口和工厂实现类
Document接口和实现类
工厂模式小结
- 工厂模式的意义
将实例化的对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,从而提高项目的扩展和维护性 - 三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
设计模式的依赖抽象原则:
- 创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中,并返回。有的书推荐,变量不要直接持有具体的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现interface。
- 不要覆盖蕨类中已经实现的方法
原型模式-基本介绍
基本介绍
- 原型模式(Prototype)是指:用原型实例指定创建对象的各类,并且通过挂账这些原型,创建新的对象。
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过 请求原型对象拷贝它们自己来实施创建,即 对象.clone()
- 需要实现Cloneable接口,不然会报错
Spring的原型的应用
Spring配置Bean时可以选择使用原型还是单例。如果选择原型,则每次创建的对象都不同。
- 分析Spring的bean创建的代码
深拷贝
浅拷贝时,对象内的成员属性如果是引用类型,则直接复制其引用的地址,而不是重新拷贝一份。这样的问题就是所有浅拷贝的对象,都是同一个引用成员属性。
![](https://i-blog.csdnimg.cn/blog_migrate/6499b8fa4fd51ca220cf9a36cb3808b5.png)
clone方法里手动拷贝
- 引用对象越多越不方便
![](https://i-blog.csdnimg.cn/blog_migrate/e878a784f64bb4096c45813f153a8140.png)
使用序列化来实现拷贝(推荐)
原理: 将对象序列化成字节数组,然后将字节数组反序列化。这样原来的值都需要创建新的内存空间,所以引用会发生改变。以达到深度拷贝的效果。
![](https://i-blog.csdnimg.cn/blog_migrate/201c05fd5de8621e89142cb4b5a7ca7d.png)
原型模式总结
默认是使用浅拷贝,如果需要深度拷贝,则需要自定义方法,会破坏OCP原则。
原型的话,就是在Bean的 创建的时候使用,我想,如果创建一个容器,或者获取一些临时的Bean的时候,都需要用到。
建造者模式
好理解,易操作。
设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好,也就是说,这种设计方案,把产品和创建产品的过程封装在一起,耦合性增加了。
解决方案:将产品和产品建造过程解耦 =》 建造者模式。
-
尚硅谷的版本
-
ChatGPT写的版本
生成器模式
聚合一些成员属性,然后使用这些成员属性建构一个新的对象。
下面的例子就是使用 使用一些字符串,建构一个URL链接。
接口适配器
我觉得在适配器里把聚合的思想应用 的非常好。就是把一些不能直接使用的资源,用适配器过滤一下就能用了。并且耦合性不高,扩展性很好。
(视频确实讲的很全)
然后就是有几种实现方式:
总结
就是依赖倒转,为每个可能实现一个Adapter类,然后需要实现对应的操作。
类适配器:在Adapter里,将src当做类,继承
对象适配器:在Adapter里,将src作为一个对象,持有
接口适配器:以Adapter里,将SRC作为一个接口实现
下图就是接口适配器。
![](https://i-blog.csdnimg.cn/blog_migrate/76728397bc9a575c99a2ebcf9dab1605.png)
接口模式
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来。从而可以保持各部分的独立性以及应对他们的功能扩展。
具体的操作:就是抽象一个接口,然后聚合这个接口,但是其使用的依赖倒转,其本质的实现是不相同的。
![](https://i-blog.csdnimg.cn/blog_migrate/c2968d7164b63dd91a3146448073de5a.png)
装饰者设计模式
装饰者就相当于快递打包,一层一层的。线性的角度来看有点像链表,其实就是一个层层加码的过程。你需要什么,你就需要套着什么。例子就是咖啡加一些小料。
我的感觉就是通过引用一层套着一层,在功能上是可以无限扩充的。就是基于一个主体,无限 扩充。
一个哲学的概念就是:“学习我,成为我”。
- 装饰者模式:动态的将新功能附加到对象上,在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则。
下面是执行结果
这个例子是很容易的理解的,非常类似于递归。需要注意的事,在返回price时,需要返回前几个Price总的值,而不是返回当前的price。可以说是对象嵌着对象的递归操作。
组合模式
这是把系统中所有模块都有统一的特性,然后这些模块都是有相互的关系,上下级。然后组成一套系统。
所以说每个模块都实现了这个Component类,然后又是递进的关系进行依赖。
(我越来越感觉这是不是一种抽象级别的数据结构)
- 简化客户端操作。客户端只需要面对一致的对象,而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何发动。
- 方便创建出复杂的层次结构,客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构。
- 需要遍历组织机构,或者处理对象具有树形结构时,非常适合使用组合模式
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式。
![](https://i-blog.csdnimg.cn/blog_migrate/72f121507a21916c0f2772e9774fbee5.png)
总结
跟意思差不多,不过是组合关系。就像人没有心脏是不能活的这种强依赖关系,而形成的组成模式。
外观模式
也是组合的操作。将很多类组合,然后这些类的操作都是统一有条件的操作,是有流程的。
这里的例子非常好理解就是电影院,把各个模块的类,放到电影院,然后只需要操作电影院这个类暴露出来的接口就行了。
让我写个简单的例子。
总结
外观模式的注意事项和细节:
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统的复杂性
- 外观模式对客户端与子系统的耦合关系,让子系统的内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需求进行分层设计时,可以考虑使用Facade模式
- 在维护一个遗留大型系统时,此是可以考虑将难以为扩展的类用一个Facade来实现,让新系统与Facade类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好,要让系统更有层次,利于维护为目的。
享元模式
就是大家一起共享一些资源,这些资源通常是经常使用,而且模块独立性高。
执行结果如下:
总结
享元模式有点 像线程池的概念,“享“表示共享,“元”表示对象。
系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。
用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储。
享元模式提高了系统的复杂度,需要分享出内部的状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是需要注意的地方
使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制
享元模式经典的应用场景是需要缓冲池的场景,比如String常量池,数据库连接池。
代理模式
不直接使用一个对象,而是间接的使用一个对接,在这个间接的过程中可以添加很多的操作。
静态代理
这里的具体操作就是,将一个具有普遍性的方法,用代理来扩展,如果不具有普遍性,则依然是用其本身的功能。
动态代理
这个呢,很像Mybatis的操作,就是用动态代理实例化一个接口。
当然这个作用还可以是给一个类加上其他功能,下面的代码中就是给已经的实现的teach再加了一些代码。这类操作可以用来做一个方法的事务管理。如果在动态代理中执行失误了,就回撤数据,如果没有问题就放行。
在尚硅谷这里的学习收获非常大。
Cglib代理
- 静态代理 和 JDK代理模式都要求目标对象是实现一个接口,但有时候对象只是一个单独的对象,并没有实现任何的接口,这个蚨可以使用目标对象子类来实现代理 - 这就是Cglib代理。
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书将Cglib代理归到动态代理
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java与实现Java接口,它广泛的被许多AOP的框架使用,SpringAOP,实现方法拦截
- 在AOP编程中,如何选择代理模式:
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,Cglib代理
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
下面的代码跑起来需要cblig和asm的库,建议用Maven项目跑,(是这样写的,但我没跑起来,不打算试了。)后面用到的时候肯定能解决问题的。
总结
![](https://i-blog.csdnimg.cn/blog_migrate/eb676e423b0bcd45ffe7bc8e3ee61955.png)
其他代理方法
除了静态代理、动态代理、Cglib代理外,还有几种变体
防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
缓存代理:当请求图片文件等资源时,先到缓存代理取,有则返回,没有则到公网或数据库里取,然后缓存(到时候写缓存的时候可以用)
远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用,远程代理通过网络和真正的远程对象沟通信息。(RPC?Feign?)
同步代理,主要使用在多线程中,完成多线程间同步工作。
行为型
模板方法
用我的话来说就是一些特定的模块,可能需要自定义一些算法,这些自定义的算法需要你根据你的使用场景进行自定义。而且这些实现模块的类,仅是一些小差别,如果差异太大,可能不适合这个模块方法。
AbstractClass 抽象类,类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现,其它的抽象方法
ConcreteClass 实现抽象方法,以完成算法中特点子类的步骤
![](https://i-blog.csdnimg.cn/blog_migrate/8255f45dba633d72a0fbac372ccd832c.png)
总结
这是尚硅谷的总结
模板方法的注意事项和细节:
- 基本思想是:算法只存在于一个地方,也就是父类中,容易修改,需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用,父类的模板方法和已实现的某些步骤会被子类继承而直接使用
- 即统一了算法,也提供了很大的灵活性,父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
- 该模式的不足之处,每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。
- 一般模板方法都加上final关键字,防止子类重写模板方法
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系统的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理
命令模式
![](https://i-blog.csdnimg.cn/blog_migrate/14d29443f7557a69c1ffb714cde80271.png)
SpringJDBC Template
也使用了命令模式,不过使用的是内部类来实现,那具体的执行都放在内部类里实现。
(这里我在思考性能的问题,我从未从匿名内部类的角度思考过问题。)
总结
我的理解:
-
命令设计模式就是把命令组合的可能一个一个实现,然后聚合到一起使用。一个命令一个类,灯的开和关直接是两个类。所以在类的方面是有点小麻烦,命令越多,可能会突出庞大的结构性。但维护起来是有依据的,非常清晰。
-
可以做什么? 可以用来做一个集中的类似于本地版本的消息队列。给中心发送处理的命令,然后其调用相关的方法即可。其实很像是一个消息队列。
尚硅谷韩老师的理解:
- 将发起请求的对象与执行请求的对象解耦,发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:“请求发起者”和“请求执行者”之间的解是通过命令的对象实现的,命令对象起到了纽带桥梁的作用
- 容易设计一个命令队列,只要把命令对象放到列队,就可以多纯种的执行命令(消息队列)
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些有过多的具体命令类,增加了系统的复杂度,这点在使用时候要注意
- 空命令是也是一种设计模式,它为我们省去了判空的操作,在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景,界面的一个按钮都是一条命令、模拟CMD(DOS)命令订单撤销/恢复、触发反馈机制。
访问者模式
(没看懂应用场景,总之就是设定一个对象数组,然后添加操作,然后这个对象数组全部执行同一个操作。)
可能意思就是,以Visitor去操作Element,而不是以数据结构和客户端去操作 Element。
用的是依赖倒转,做的是一种业务隔离,使用于数据结构非常稳定的系统。(那岂不是适用于做服务器核心状态)
![](https://i-blog.csdnimg.cn/blog_migrate/f9e843c889d5abc97c6d41d4717b2c62.png)
总结
访问者模式的注意事项和细节:
优点:
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
缺点
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难。
- 违背了依赖倒转原则,访问者依赖的是具体元素,而不是抽象元素
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。
讲数据结构与操作分离,使用访问者相当于将一些对象具有统一操作的,像 商品(不同的类型),像 过滤器(不同的层面),等等。这些被聚合的对象都需要对外开放自自己,并且使用访问者的this指针直接来调用自己。
这个调用呢是直接讲访问者传入 元素中,可视为元素开放了自己的VISIT接口。
访问者重载了很多关于 不同类型元素的visit方法,这样可以整合很多类型,且不需要修改数据结构和操作。只需要将新添加的类型元素以这种试工,继续添加到数组中即可。
迭代器模式
迭代器模式最早使用在Collection的时候,是使用过这个Iterator的,这里的教的也是继承Iterator用数组做自己的集合。
即是使用迭代器,又是使用迭代模式。
思想就是为每一个聚合到主类中的类,是一集合,所以需要为其实现一个迭代器,在使用时,就不需要直接使用List,而是使用迭代器。
基本介绍
迭代器模式(Interator Pattern)是常用 设计模式,属于行为模式。
如果我们的集合元素是用不同的方式实现的,有数组还有Java集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示。不暴露其内部的结构。
![](https://i-blog.csdnimg.cn/blog_migrate/ac13a793c884e6bb996cd57ea51a71dc.png)
总结
迭代器模式的注意事项和细节
优点:
- 提供一个统一的方法遍历对象,客户不用在考虑聚合的类型,使用一种方法就可以遍历对象了。
- 隐藏了对聚合的内部结构,客户端要遍历聚合时候只能取到迭代器,而不会知道聚合的具体组成
- 提供了一种设计思想,就是一个类应该只有一个引起变化的原因,在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象命令的责任分开,这样一来,集合改变的话,只影响聚合对象,而如果遍历方式改变的话,只影响到迭代器。
- 当要展示一组相似的对象时,或者遍历一组相同 对象 时使用,适合使用迭代器模式
缺点
- 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类。
我的思考:
就是用迭代器来聚合一组相似的对象,然后操作差不多。而其只需要对外开放一个迭代器作为访问接口就可以了,不需要使用其他的方式进行操作数据。(像List内,有什么get 和 add)
而这个迭代器模式是自定义数据结构,然后对外开放迭代器,在使用时,仅需要拿到迭代器。
观察者模式
传统的调用,直接是写死的。代码的耦合度非常高。
场景,订阅的信息场景。就是你只需要发送信息,不用在意实现,发送后,什么事都不管了。我写的例子,中就是一个中心控制器向其他构件的观察者发送信息。其实这个很像是一个订阅的业务。
代码层面,观察者会开放接口用来接收信息,然后订阅中心 ,就是Subject。Subject聚合了一些观察者。每当订阅中心接收到消息,都会发给观察者。然后观察者再做具体的实现。
![](https://i-blog.csdnimg.cn/blog_migrate/e6fdcb509ab3906b3eb3f7ef09cdab7f.png)
总结
- 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
- 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类Subject的子类,遵守了OCP原则。
总之就是类似于订阅的模式,但不限于此。
中介者模式
中介者模式,用一个中介对象来封装一系列的对象交互,中介者使各个对象不需要地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
中介者模式属于行为模式,使代码易于维护
比如MVC模式C(Controller控制器) 是 M(Model模型)和 V(View视图)的中介者,在前后端交互时起到了中间人的作用。
下面的例子是我自己设计的,就是以服务中心为 一个 中介者,你只需要指定状态,然后让其 中介者用 指定的服务处理事情。
总结
- 多个类相互耦合,全形成网状结构,使用中介者模式将网状结构分离为星形结构,进行解耦
- 减少类间依赖,降低了耦合,符合迪米特原则
- 中介者承担了较多的责任,一量中介者出现了问题,整个系统就会受到影响
- 如果设计不当,中介者对象本身变得过于复杂,这点在实际 使用时,需要特别注意
能不能 用中介者结合 命令模式来进行操作。
备忘录模式
记录某个对象当时的状态,在后面用于恢复。
(这个是不是可以用于做 服务状态的 保存和 恢复 ,记录服务运行的状态,然后用命令模式指定其恢复就可以了 )
这里用的记录一个游戏的角色的状态,然后用于后面的恢复
很简单
总结
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到原来的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会战胜比较大的资源,而且每一次保存都会消耗一定的内存,对性能可能有一定的影响
- 应用场景: 1. 后悔药 2. 打游戏时存档 3. Windows 里的 Ctrl+Z ,4. IE 中的后退 5. 数据库的事务管理
- 为了节约内存,备忘录模式可以和原型模式配合使用
解释器模式
基本介绍
- 在编译原理中,一个自述表达式通过词法分析形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树,这里的词法分析器和语法分析器都可以看做是解释器。
- 解释器模式:是指给人上语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
- 应用场景:
- 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一些重复出现的问题可以用一种简单的语言来表达
- 一个简单的语法需要解释的场景
- 编译器、运算表达式、正则表达式、机器人
实操
解释器模式一般是需要对应去做一个解析一段 有意义的字符串做的事,像SPEL表达式,终端中输入 “a+b*c”类似的表达式。
其中的思想,主要是将一个表达式进行拆分。
这个其实难的是逻辑,就是说具体要做成怎么一件事,怎么拆分?一个表达式怎么拆分?拆分存储到对象中,然后在用的时候,进行计算。
存储在结构类似于一二叉树。计算用的是递归。
用来写自定义的一些命令是非常不错的,虽然工作量大,但也不至于连个思路都没有了。
总结
注意事项和细节:
- 当有一个语言需要解释执行,可将语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器可能会引起类膨胀、解释器模式采用递归调用方法,将会导致高度非常复杂,效率可能降低。
(没有金刚钻,不要揽瓷器活)
场景: 用于 对 终端,需要输入命令,这样就需要用到这个解释器模式,或者一些地方需要写一些脚本语言,也需要用到解释器模式,后面开发编程语言的时候是需要用到的。
状态模式
代码实现
下面的例子将围绕的是这样一个,抽奖的例子来写。(因为自创例子时间太久了 ,所以我还是保存了尚硅谷的例子。虽然有在单词方面有语义的差异。
总结
每个状态有不同的实现,如果没有之前的状态,是无法进行下一个状态。是一强状态流程控制的模式。
- 代码有很强的可读性,状态模式将每个状态行为封闭到对应的一个类中
- 方便维护,将容易产生的问题if -else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法都需要判断当前是什么状态,不但会产生很多if-else,而且容易出错
- 符合 “开闭原则”,容易增删状态
- 会产生很多类,每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
- 应用场景:当一个事件或者对象有很多种状态,状态之羊相互转换,对不同的状态要求有不同的行为时候,可以考虑使用状态模式
(我写SCRUM看板的时候可以用到,每个任务都有不同的状态。)
策略模式
我的思考
用来干什么呢?
做类层面的解耦,把类的方法,聚合成一个类。像一个事物,很多种品类,不同品类需要不同的实现,但其中不同品类又有相同的实现,所以用上了策略模式,把实现方法的方法,变成策略层面的类。
汽车公司有几类产品,汽车,电动车,个人飞机。
汽车用充能的方法是加油,电动车是充电,个人飞机也是加油。所以这里的加油可以复用,成为一个策略类。
汽车有导航,电动车有导航,个人飞机也有导航。所以这里的导航,可以复用的,都是一个方法。
把实现抽象成类,然后进行复用。
简单实现
上面是我的简单总结,目前的水平只能看到这些,后面持续精进
![](https://i-blog.csdnimg.cn/blog_migrate/035da64bb985cf8cfbf1594c1d3d3e8f.png)
总结
- 策略模式的关键是: 分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合 /聚合 少用继承, 用行为类组合,而不是行为继承,更有弹性
- 体现了 “ 对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略即可,避免了使用多重转移语句(if..else if..else)
- 提供了可以替换继承关系的办法,策略模式将算法封闭在独立的Strategy类中使得你可以独立于其中Context改变它,使它易于切换、易于理解、易于扩展。
- 需求注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大。
是的,每一个功能都会成为单独的一个策略类。
职责链模式
我的理解就是,将处理者全部链接起来,每个环节每个可能的条件都会有一个处理者。当前无法处理的,就调用下一个处理者进行执行。
简单的关于链的处理方式的思考🤔:
- 如果条件不符合所有的处理者,则直接抛异常,
- 从头执行到尾,如果没有调用也需要抛异常
- 是以一个环形进行处理的,所以一个对象如果经常一个环了还没有解决,可能是需要抛异常了
当然在性能上可能会有一些问题,这不是目前讨论的。
代码实现
![](https://i-blog.csdnimg.cn/blog_migrate/3d97a596add732f392f19e38f3179d03.png)
总结
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是 在边比较长的时候,因此需要控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法 中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
缺点:
- 高度不方便,采用了类似递归的方式,高度时逻辑可能比较复杂
- 最佳应用场景;有多个对象可以处理同一个请求时,比如: 多级请求、请假/加薪 等审批流程,JavaWEB中Tomcat对Encoding的处理,拦截器