文章目录
- 1 七大开发原则
- 1.1 单一职责原则SRP(Single Responsibility Principle)
- 1.2 开放封闭原则OCP(Open-Close Principle)
- 1.3 里式替换原则LSP(the Liskov Substitution Principle LSP)
- 1.4 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
- 1.5 接口分离原则ISP(the Interface Segregation Principle ISP)
- 1.6 迪米特法则(最少知识原则)(Law Of Demeter)
- 1.7 合成复用原则(Composite/Aggregate Reuse Principle CARP)
- 2 设计模式
1 七大开发原则
1.1 单一职责原则SRP(Single Responsibility Principle)
简介:类的功能要单一,不能包罗万象,跟杂货铺似的。
说明:对类来说的,即一个类应该只负责一项职责(不是说一个类里只能有一个方法,指的是一项职责,比如这个类管订单了,就不要去管物流。)
,如类A负责两个不同职责: 职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2
单一职责原则注意事项和细节
- (1)降低类的复杂度,一个类只负责一项职责。
- (2)提高类的可读性,可维护性
- (3)降低变更引起的风险
- (4)通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一-职责原则
例子:
// 方式1
// 1.在方式1的run方法中,违反了单一职责原则
// 2. 解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可
class Vehicle {
public void run(String vehicle)
System.out.println(vehicle+"在公路上运行..");
}
}
// 方案2的分析
// 1.遵守单一职责原则
// 2.但是这样做的改动很大,即将类分解,同时修改客户端
// 改进:直接修改Vehicle类,改动的代码会比较少 =>方案3
class WaterVelhjicle {
public void run(String vehicle) {
System.out.println(vehicle + "水中运行");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "天空运行");
}
}
// 方式3
// 1.这种修改方法没有对原来的类做大的修改,只是增加方法
// 2. 这里虽然没有在类这个级别上遵守单一 职责原则,但是在方法级别上,仍然是遵守单一职责
class Vehicle2 {
public void run(String vehicle) {
//处理
System.out.println(vehicle+"在公路上运行..");
}
public void runAir(String vehicle) {
System.out.println(vehicle+"在天空上运行...");
}
}
1.2 开放封闭原则OCP(Open-Close Principle)
简介:一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
基本概念:
- (1)开闭原则(Open Closed Principle) 是编程中最基础、最重要的设计原则
- (2) 一个软件实体如类,模块和函数应该
对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节
。 - (3)
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
。 - (4)编程中遵循其它原则,以及
使用设计模式的目的就是遵循开闭原则
。
例子: 看一个画图形的功能
![](https://i-blog.csdnimg.cn/blog_migrate/e5badf4e37f402fce8f7b035ac07d700.png)
问题代码:
public class Test {
public static void main(String[] args) {}
}
class GraphicEditor {
public void drawShape(Shape s){
if (s.m_ _type== 1)
drawRectangle(s);
elseif(s.m_ type== 2)
drawCircle(s);
}
public void drawRectangle(Shape r) {
System.out.println("矩形");}
public void drawCircle(Shaper) {
System.out.println("圆形");
}
}
class Shape {
int m_ _type;
}
class Rectangle extends Shape {
Rectangle(){
super.m_ type = 1;
}}
class Circle extends Shape {
Circle(){
super.m_ type= 2;
}
}
- 优点是比较好理解,简单易操作。
- 缺点是违反了设计模式的ocp原则,即
对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
- 比如我们这时要新增加一一个图形种类,我们需要修改的地方较多
改进思路分析:
思路: 把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,使用方的代码就不需要修。满足了开闭原则
1.3 里式替换原则LSP(the Liskov Substitution Principle LSP)
简介:子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
背景——OO中的继承性的思考和说明:
- (1)继承包含这样一层含义:
父类中凡是已经实现好的方法,实际上是在设定规范和契约
,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。 - (2) 继承在给程序设计带来便利的同时,也带来了弊端。
比如使用继承会给程序带来侵入性,降低程序的可移植性降低,增加对象间的耦合性
,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障 - (3)问题提出:在编程中,如何正确的使用继承?
=>里氏替换原则
里氏替换原则:
- (1) 里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的以为姓里的女士提出的。
- (2) 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,
所有引用基类的地方必须能透明地使用其子类的对象
。 - (3)在使用继承时,遵循里氏替换原则,
在子类中尽量不要重写父类的方法
- (4)里氏替换原则告诉我们,
继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题
。
例子:
- 我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法。造成原有功能出现错误。
在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
- 通用的做法是:原来的父类和子类都继承一个更通俗的基类,
原有的继承关系去掉,采用依赖,聚合,组合等关系代替.
1.4 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
简介:高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
说明: 依赖倒转原则(Dependence Inversion Principle)是指:
- (1)高层模块不应该依赖低层模块,二者都应该依赖其抽象
- (2)抽象不应该依赖细节,细节应该依赖抽象
- (3)依赖倒转(倒置)的中心思想是面向接口编程
- (4)依赖倒转原则是基于这样的设计理念: 相对于细节的多变性,抽象的东西要稳定的多。在java中, 抽象指的是接口或抽象类,细节就是具体的实现类
- (5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
问题代码:
![](https://i-blog.csdnimg.cn/blog_migrate/4e3685852ab16b14901227039bc7ae88.png)
这种代码的
- 好处:简单。
- 坏处:person不应该依赖email类,如果后期接收消息变成微信、短信,该怎么处理?
- 解决思路:引入抽象的接口IReceiver,表示接收者。使得person与IReceiver接口发生依赖。细节要依赖抽象,而不是抽象依赖细节。
修正代码:
依赖关系传递的三种方式和应用案例:
依赖倒转原则的注意细节:
- (1)底层模块尽量都有抽象类或接口,或者两者都有。
- (2)变量的声明类型尽量是抽象类或接口。这样我们的变量引用和实际对象间,就存在一个缓冲层,有利于程序扩展和优化。
- (3)继承时遵循里氏替换原则
1.5 接口分离原则ISP(the Interface Segregation Principle ISP)
简介:客户端不应该依赖它不需要的接口
,即一个类对另一个类的依赖应该建立在最小的接口上。
说明:设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
例子:
下图是我们编码中遇到的常见问题:
类A通过接口Interface1依赖类B,类c通过接口Interface1依赖类D,如果接口Interface1对于类A和类c来说不是最小接口,那么类B和类D必须去实现他们不需要的方法。
//接口
interface Interface1 {
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1 {
void operation1() {
System. out. println();
}
void operation2(){
System. out. println();
}
void operation3(){
System. out. println();
}
void operation4(){
System. out. println();
}
void operation5() {
System. out. println();
}
}
class D implements Interface1 {
void operation1() {
System. out. println();
}
void operation2(){
System. out. println();
}
void operation3(){
System. out. println();
}
void operation4(){
System. out. println();
}
void operation5() {
System. out. println();
}
}
class A { //A 类通过接口Interface1依赖(使用) B类,但是只会用到1,2,3方法
public void depend1(Interface1 i) {
i. operation1();
}
public void depend2(Interface1 i) {
i. operation2();
}
public void depend3(Interface1 i) {
i. operation3();
}
}
class D { //D 类通过接口Interface1依赖(使用) D类,但是只会用到1,4,5方法
public void depend1(Interface1 i) {
i. operation1();
}
public void depend2(Interface1 i) {
i. operation4();
}
public void depend3(Interface1 i) {
i. operation5();
}
}
按隔离原则应当这样处理:
将接口Interface1 拆分为独立的几个接口,类A和类c分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则
interface Interface1 {
void operation1();
}
//接口2
interface Interface2 {
void operation2();
void operation3();
}
//接口3
interface Interface3 {
void operation4();
void operation5();
}
class A {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface2 i) {
i. operation2();
}
public void depend3(Interface2 i) {
i. operation3();
}
}
class C {
public void depend1(Interface1 i) {
i . operation1();
}
public void depend4(Interface3 i) {
i. operation4();
}
public void depend5(Interface3| i) {
i. operation5();
}
}
1.6 迪米特法则(最少知识原则)(Law Of Demeter)
简介:一个软件实体应当尽可能少的与其他实体发生相互作用
基本概念:
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
直接的朋友:
每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。
耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友
,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部
。
迪米特法则注意事项和细节:
- 迪米特法则的核心是降低类之间的耦合
- 但是注意: 由于每个类都减少了不必要的依赖,因此
迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系
1.7 合成复用原则(Composite/Aggregate Reuse Principle CARP)
简介:尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象
例子:
B类需要使用A类的方法,可以使用继承。但是使用继承会让B与A的耦合性增强。
改进,将A作为参数传入B类的方法中(即,依赖关系),B类依然可以使用到A类的方法。
或者使用组合关系
设计原则的核心思想
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 为了交互对象之间的松耦合设计而努力
依赖——a依赖b,说明a使用b (dependency)
姐(接口隔离原则)和(合成复用原则)弟(迪米特法则)依(依赖倒置原则)理(里氏替换原则)开(开闭原则)战(单一职责原则)。
2 设计模式
身为程序员,我们不需要华丽的编程技巧,高深的代码,对于我们来说只需要熟练的掌握业界给予我们总结的23种设计模式,才是最重要的。因为掌握这23种设计模式,你就可以成为不一般的开发人员了。
模式: 在一定环境中解决某一问题的方案,包括三个基本元素–问题,解决方案和环境。大白话:在一定环境下,用固定套路解决问题。
设计模式(Design pattern): 是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码变成真正工程化。
设计模式的分类:
创建型模式: 通常和对象的创建有关,涉及到对象实例化的方式。(共 5 种模式)
结构型模式: 描述的是如何组合类和对象以获得更大的结构。(共 7 种模式)
行为型模式: 用来对类或对象怎样交互和怎样分配职责进行描述。(共 11 种模式)
2.1 创建型模式
该模式用来处理对象的创建过程,主要包含以下五种设计模式:(单原建抽象工厂)
创建型模式有五种:工厂方法模式 、抽象工厂模式 、单例模式 、建造者模式 、原型模式
口诀:原来的建设工人单独抽奖
解释:原(原型模式)来的建(建造者模式)设工(工厂方法模式)人单(单例模式)独抽(抽象方法模式)奖。
1,单例模式(Singleton Pattern)是保证一个类仅有一个实例,并提供一个访问它的全局访问点。
2, 工厂方法模式(Factory Method Pattern)的用意是定义一个创建产品对象的工厂接口, 将实际创建工作推迟到子类中。
3,抽象工厂模式(Abstract Factory Pattern)的意图是提供一个创建一系列相关或者相互依赖的接口,而无需指定它们具体的类。
4,建造者模式(Builder Pattern)的意图是将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
5,原型模式(Prototype Pattern)是用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2.1.1 单例模式(Singleton)
一、什么是单例模式
单例模式属于创建型设计模式,是Java种最简单的设计模式之一。
这种模式要求一个类仅有一个实例,并且提供了一个全局的访问点。在程序中多次使用同一个对象且作用相同时,所有需要调用的地方就会共享
这个访问点(对象),从而防止因频繁创建对象而损耗系统性能。
二、优缺点
优点:
- 全局唯一实例:通过单例模式,可以确保一个类只有一个实例对象存在,全局范围内可以方便地访问该实例。
- 节省资源:由于只有一个实例存在,单例模式可以节省系统资源(如内存、CPU等),避免多次创建和销毁对象的开销。
- 避免对共享资源的竞争:在多线程环境中,单例模式可以避免对共享资源的竞争问题,确保数据一致性和线程安全性。
缺点:
- 不支持多态:单例模式一般只能创建一个固定类型的对象实例,不支持多态的灵活性。
- 降低了代码的灵活性:对于使用了单例模式的代码,如果需要改变实例化策略或使用其他类型的实例,可能需要修改代码和重新设计。
- 隐藏了依赖关系:单例模式可能隐藏了对其他组件或对象的依赖关系,使得代码的结构不够清晰,增加了代码的理解和维护的难度。
三、应用场景
3.1 生活场景
- 一个班级只有一个班主任。
- windows桌面上的回收站:我们打开一个回收站,当我们再次试图打开一个新的时,Windows并不会为你弹出一个新窗口,也就是说整个系统运行过程中,系统只维护一个回收站的实例。
- 网站计数器:一般也采用单例模式实现,如果你存在多个计数器,每一个用户访问都刷新的计数器,这样的话你的实际数量是难以同步的。
3.2 Java场景
- Bean定义的默认作用域:在Spring中,默认情况下,所有的Bean都是单例的,也就是说 Spring 容器中只会创建一个特定类型的Bean实例。
- Spring容器(ApplicationContext):Spring容器本身也是一个使用了单例模式的对象。无论是基于XML配置的ClassPathXmlApplicationContext,还是基于注解的AnnotationConfigApplicationContext,它们都是单例的,只会生成一个容器实例。
- Spring AOP中的切面(Aspect):在Spring AOP中,切面是用来定义横切关注点(如日志、事务等)的类。默认情况下,Spring会将切面定义为单例,以确保在整个应用程序中只有一个切面实例,以避免创建过多的代理对象。
四、创建单例
注意看代码中的注释。
4.0 代码结构
4.1 饿汉式(2种)
/**
* 饿汉:在类刚一初始化的时候就立即把单例对象创建出来,下面两种都是饿汉模式的实现
*/
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private static Singleton instance1=null;
static {
instance1=new Singleton();
}
public Singleton getInstance1(){
return instance1;
}
}
4.2 懒汉式(4种)
/**
* 懒汉:懒加载,就是在需要的时候才回去创建对象
*/
public class Singleton {
private Singleton() {
}
/**
* 1.单例模式【线程不安全,不推荐】
* 因为没有加锁synchronized,严格意义上不算单例。
* @return
*/
private static Singleton instance1;
public static Singleton getInstance() {
if (instance1 == null) {//这里是不安全的,可能得到两个不同的实例
instance1 = new Singleton();
}
return instance1;
}
/**
* 2.线程安全但效率低【不推荐】
* 99%的情况下不需要同步
* @return
*/
private static Singleton instance2;
public static synchronized Singleton getInstance1() {
if (instance2 == null) {
instance2 = new Singleton();
}
return instance2;
}
/**
* 3.单例模式,线程不安全【不推荐】
* 虽然加了锁,但是等到第一个线程执行完instance2=new Singleton();跳出锁时
* 令一个线程恰好刚判断完instance2为null,此时又会加载另一个实例
*/
private static Singleton instance3;
public static Singleton getInstance2() {
if (instance3 == null) {
synchronized (Singleton.class) {//不安全
instance3 = new Singleton();
}
}
return instance3;
}
/**
* 4.双重校验锁:延迟加载+线程安全【推荐】
*/
private static volatile Singleton instance4;
public static Singleton getInstance4() {
if (instance4 == null) {
synchronized (Singleton.class) {
if (instance4 == null) {
instance4 = new Singleton();
}
}
}
return instance4;
}
}
4.3 静态内部类
/**
* 静态内部类【推荐】
* 这种方式跟饿汉式方式采用的机制类似,但又有不同。
* 两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
* 不同的地方:
* 在饿汉式方式是只要Singleton类被装载就会实例化,
* 内部类是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类
* 优点:避免了线程不安全,延迟加载,效率高。
*/
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4.4 枚举类
/**
* 枚举实现
* 这种方式还没有被广泛采用,但是这种是实现单例的最佳方式。
* 线程安全,自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化
*/
public enum Singleton {
INSTANCE;
public void whateverMethod(){
System.out.println("单例模式实现的最佳方式");
}
}
五、总结
- 单例最常见的写法:懒汉式、饿汉式。
- 懒汉式:懒加载,在调用的时候才会实例化对象,推荐使用双重校验锁的方式。
- 饿汉式:类加载的时候就已经实例化好对象了,不会存在并发安全和性能问题。
- 在开发中,如果内存要求高,就用懒汉式,不高则用饿汉式。
- 最优雅的方式是使用枚举,代码极简,没有线程安全问题,又能防止反射和序列化时破坏单例。
2.1.2 工厂模式(Factory)
一、什么是工厂模式
工厂模式属于创建型设计模式,它用于解耦对象的创建和使用。通常情况下,我们创建对象时需要使用
new操作符
,但是使用new操作符创建对象会使代码具有耦合性
。工厂模式通过提供一个公共的接口,使得我们可以在不暴露对象创建逻辑的情况下
创建对象。
二、工厂分类
工厂模式分为三种类型:
简单工厂
、方法工厂
和抽象工厂
,其本质就是对获取对象过程的抽象。
三、应用场景
3.1 生活场景
- 你需要一辆汽车,可以直接从工厂里提货,而不用关心它具体是怎么实现的
- Hibernate换数据库只需要换方言和驱动就可以
3.2 java场景
- BeanFactory:它是Spring IoC容器的核心接口,通过读取配置文件或注解来创建Bean实例,并将它们注入到其他对象中。BeanFactory使用了工厂模式来隐藏具体的对象实例化过程,客户端只需要通过接口获取Bean对象,而不需要关心具体的实例化细节。
- FactoryBean:是一种更高级别的工厂模式实现,用于创建特定类型的Bean对象。与普通的BeanFactory不同,FactoryBean的getObject()方法可以返回任意类型的对象实例,并且可以通过配置方式创建和管理实例。
- Executors:提供了一系列的工厂方法来创建线程池(ThreadPoolExecutor)及其相关组件。
- Charset:提供了一系列的静态工厂方法,如forName()、availableCharsets()等,用于创建Charset实例。
四、 工厂模式实现
4.0 代码结构
4.1 传统模式
在介绍工厂模式之前先来看看传统模式,以卖包子为例,如下:
//简单的制作流程
public BaoZi createBaoZi() {
BaoZi baozi = new BaoZiImpl();
//准备材料
baozi.prepare();
//制作包子
baozi.make();
//蒸包子
baozi.braise();
return baozi;
}
包子肯定有很多种类吧,那我们可以直接在上述代码中添加根据包子的种类生成不同类型的对象
/**
* 包子肯定有不同的馅:酸菜、豆沙、猪肉,那么他的材料、售价等方式也不同
* 我们可以直接在上述代码中,添加根据包子的不同种类生成不同的对象。
*/
public BaoZi createBaoZi(String type) {
BaoZi baoZi = null;
switch (type){
case "suancai":
baoZi=new SuanCaiBaoZi();
break;
case "dousha":
baoZi=new DouShaBaoZi();
break;
case "pork":
baoZi=new PorkBaoZi();
break;
default:
throw new IllegalArgumentException("Invalid BaoZi Type");
}
//准备材料
baoZi.prepare();
//制作包子
baoZi.make();
//蒸包子
baoZi.braise();
return baoZi;
}
此时的类图如下,让调用者去创建包子对象。
Test:
//1.传统模式
@Test
void traditonal(){
SaleBaoZi saleBaoZi=new SaleBaoZi();
//以猪肉包为例
saleBaoZi.createBaoZi("pork");
}
4.2 简单工厂模式
简单工程根据客户端的需求创建具体的实例,这种模式对调用者隐藏了实例创建的过程,也使得创建过程更加容易维护。
还是以卖包子为例,简单工厂模式实现如下:
/**
* 2.简单工厂方法:希望能够创建一个对象,但创建过程比较复杂,希望对外隐藏这些细节
*/
public class SimpleFactory {
public static BaoZi createBaoZi(String type){
BaoZi baoZi=null;
switch (type){
case "suancai":
baoZi=new SuanCaiBaoZi("酸菜包");
break;
case "dousha":
baoZi=new DouShaBaoZi("豆沙包");
break;
case "pork":
baoZi=new PorkBaoZi("猪肉包");
case "beef":
//老板拓展业务了,新加了一个牛肉包类型的包子,那对于简单工厂模式而言,
//于是就得修改源代码,那么就违反了ocp原则,假如新增100个?
baoZi=new BeefBaoZi("牛肉包");
break;
default:
throw new IllegalArgumentException("Invalid BaoZi Type");
}
return baoZi;
}
}
此时的类图:
Test:
//2.简单工厂模式
@Test
void simpleFactory(){
//以猪肉包为例
BaoZi pork = SimpleFactory.createBaoZi("pork");
pork.prepare();
pork.make();
pork.braise();
}
相比传统模式,从类图上就可以看出来,在sale和baozi中间又
加了一层
。
通过封装SimpleFactory这个类,我们将sale和baozi进行了解耦合。
4.3 方法工厂模式
简单工厂模式下,如果老板拓展业务了,加了一个牛肉种类的包子,就得在源码基础上修改,那么这就违背了
开闭原则(ocp)
,即对扩展开放,对修改关闭
。于是,为了解决这个问题,就又了工厂方法模式。
工厂方法模式是一种更加抽象的工厂模式,它将工厂的职责抽象为接口,由具体的工厂实现创建具体的对象。工厂方法模式弱化了工厂的实现,使得
每个工厂只负责一个产品的创建
。
抽象工厂MeAbStractFactory:
public interface MeAbstractFactory {
BaoZi createBaoZi();
}
DouShaFactory:
//豆沙包
public class DouShaFactory implements MeAbstractFactory {
@Override
public BaoZi createBaoZi() {
return new DouShaBaoZi("豆沙包");
}
}
PorkFactory:
//猪肉包
public class PorkFactory implements MeAbstractFactory {
@Override
public BaoZi createBaoZi() {
return new PorkBaoZi("猪肉包");
}
}
BeefFactory:
//牛肉包
public class BeefFactory implements MeAbstractFactory {
@Override
public BaoZi createBaoZi() {
return new BeefBaoZi("牛肉包");
}
}
此时的类图:
Test:
//3.方法工厂模式
@Test
void methodFactory(){
MeAbstractFactory factory=new PorkFactory();
BaoZi pork = factory.createBaoZi();
pork.prepare();
pork.make();
pork.braise();
}
之前的SimpleFactory在createBaoZi中直接就new出来了,但在方法工厂中,我们将createBaoZi这个动作推迟到了MeAbStactFactory的子类(XXFactory)中才完成。
好处就是,比如后期要卖个羊肉包,我们直接编写个羊肉包类,然后实现MeAbstractFactory类就,实现它自己的功能,这样完全不用修改原来的代码了,也就解决了违反OCP原则的问题。
4.4 抽象工厂模式
抽象工厂模式是基于工厂方法模式的基础上进行的。在这种模式中,每一个工厂不再只负责一个产品的创建,而是负责
一组产品
的创建。抽象工厂模式将每个产品组都提取为一个接口,每个工厂都负责一个产品组。
假如老板的生意做大了,在北京开了个分店,并且不止卖包子,还卖蛋糕,那么该怎么拓展呢,很简单,只需要在抽象工厂类中新增创建蛋糕的抽象方法就行,如下:
AbstractFactory:
public interface AbstractFactory {
//制作包子
BaoZi createBaoZi(String type);
//制作蛋糕
Cake createCake(String type);
}
BJFactory:
//北京分店
public class BJFactory implements AbstractFactory{
@Override
public BaoZi createBaoZi(String type) {
BaoZi baoZi=null;
switch (type){
case "beef":
baoZi=new BJBeefBao("北京牛肉包");
break;
case "pork":
baoZi=new BJPorkBao("北京猪肉包");
default:
break;
}
return baoZi;
}
@Override
public Cake createCake(String type) {
Cake cake=null;
switch (type){
case "apple":
cake=new BJAppleCake("北京苹果蛋糕");
break;
case "pear":
cake=new BJPearCake("北京梨味蛋糕");
default:
break;
}
return cake;
}
}
可以看到,抽象工厂仅仅是在工厂方法模式下新增了一些接口,只是工厂方法模式的一个拓展,当抽象工厂只有一个产品体系的话就会退化成工厂模式,所以两者本质上没有太大的区别。
Test:
//4.抽象工厂模式
@Test
void abstractFactory(){
AbstractFactory factory=new BJFactory();
Cake apple = factory.createCake("apple");
BaoZi pork = factory.createBaoZi("pork");
apple.prepare();
apple.make();
apple.bake();
apple.sale();
pork.prepare();
pork.make();
pork.braise();
pork.sale();
}
五、总结
5.1 简单工厂模式
优点:
- 简单工厂模式实现简单,易于理解和使用;
- 可以对对象的创建进行集中管理,客户端和具体实现解耦。
缺点:
- 工厂类负责创建所有对象,如果需要添加新类型的产品,则需要修改工厂类的代码,这违反了开闭原则;
- 工厂类职责过重,导致单一职责原则被破坏。
适用场景:
- 工厂类负责创建的对象较少,客户端不需要知道对象的创建过程;
- 客户端需要根据传递的参数来获取对应的对象。
5.2 方法工厂模式
**优点: **
- 方法工厂模式具有良好的可扩展性,如果需要添加新类型的产品,只需要添加对应的工厂方法即可;
- 与简单工厂模式相比,方法工厂模式更符合开闭原则和单一职责原则。
缺点:
- 需要客户端自行选择使用哪个工厂方法,不能像简单工厂模式那样直接传参获取对应对象,因此对客户端的编写有一定要求。
适用场景:
- 应用中需要创建的对象较少,但是需要具备良好的可扩展性;
- 客户端可以自行选择创建哪种对象。
5.3 抽象工厂
优点:
- 抽象工厂模式可以创建多个产品族的产品,这些产品之间有相互依赖或约束关系,有助于保持系统的一致性和稳定性;
- 客户端与具体产品解耦,通过产品族的方式进行管理。
缺点:
- 抽象工厂模式增加了系统的抽象性和理解难度,不易于理解和修改;
- 新增产品族时需要修改工厂接口、工厂实现类和产品类,增加了系统的复杂性。
适用场景:
- 系统需要一系列相互依赖或约束的产品;
- 客户端不需要知道具体产品的创建过程,只需要知道产品族即可。
2.1.3 抽象工厂模式(工厂模式已包含,不再赘述)
2.1.4 建造者模式(Builder)
一、什么是建造者模式
建造者模式是一种
创建型
设计模式,也叫生成器模式
。
定义:封装一个复杂对象构造过程,并允许按步骤构造。
解释:就是将复杂对象的创建过程拆分成多个简单对象的创建过程,并将这些简单对象组合起来构建出复杂对象。
二、角色组成
1. 产品类(Product):表示被创建的复杂对象。它通常包含多个部分或者组成,并由具体的建造者逐步构建而成。
2. 抽象建造者类(Builder):定义了建造复杂对象所需要的各个部分的创建方法。它通常包括多个构建方法和一个返回产品的方法。
3. 具体建造者类(ConcreteBuilder):实现Builder接口,并提供各个部分或者组成的构建方法。
4. 指挥者类(Director):负责控制建造者的构建顺序,指挥建造者如何构建复杂对象
。
三、优缺点
优点:
- 灵活:可以分步骤地构建复杂对象,使得构建过程更加灵活。
- 解耦:可以隔离复杂对象的创建和使用,客户端不必关心对象的创建细节。
- 易扩展:增加新的具体建造者很方便,可以扩展构建器功能,符合开闭原则。
缺点:
- 增加工作量:需要额外的代码来创建和管理具体建造者类,增加了程序员的工作量。
- 效率低:相比于其他创建型模式,在运行时效率较低,特别是对象太复杂时。
四、应用场景
4.1 生活场景
- 盒饭套餐:顾客可以选择不同的菜,服务员按照顾客的要求,将这些菜组合起来,最终构建出一个完整的套餐。
- 盖房子:需要分多个阶段进行,比如准备材料、打地基、盖围墙…。建造者模式可以将房屋的建造分解成多个步骤,每个步骤对应一个具体的建造者,最终由包工头(指导者)来调用不同的建造者,完成整个房子的建造。
4.2 java场景
- StringBuilder:能够动态地构建字符串。
- Stream API:将集合类转为stream流,通过一系列的中间操作和终止操作来生成最终结果。
- Lombok的@Builder注解:一个注解就可以生成建造者模式的代码。
五、代码实现
肯德徳都吃过吧,里面有很多的套餐。假设套餐主要由汉堡、薯条和饮料三种组成,每个组件都有不同种类和大小,并且每个套餐的组合方式也不同。下面以肯德徳套餐为例,解释建造者模式。
1. 产品类:Meal
2. 抽象建造者类:MealBuilder
3. 具体建造者类:BeefBurgerMealBuilder、ChickenMealBuilder、ShrimpMealBuilder
4. 指挥者类:MealDirector
5.0 UML类图
5.1 产品类(Product)
/**
* @author Created by njy on 2023/6/12
* 1.产品类(Product)
*/
@Data
public class Meal {
//汉堡包
private String burger;
//薯条
private String fries;
//饮料
private String drink;
}
5.2 抽象建造者(Builder)
/**
* @author Created by njy on 2023/6/12
* 2.抽象建造者(Builder)
*/
public abstract class MealBuilder {
protected Meal meal=new Meal();
//构建汉堡
public abstract void buildBurger();
//构建薯条
public abstract void buildFries();
//构建饮料
public abstract void buildDrink();
public Meal getMeal(){
return meal;
}
}
5.3 具体构建者(ConcreteBuilder)
/**
* @author Created by njy on 2023/6/12
* 3.具体建造者(ConcreteBuilder):鸡肉汉堡套餐
*/
public class ChickenMealBuilder extends MealBuilder{
@Override
public void buildBurger() {
meal.setBurger("鸡肉汉堡");
}
@Override
public void buildFries() {
meal.setFries("中份薯条");
}
@Override
public void buildDrink() {
meal.setDrink("大杯果汁");
}
}
/**
* @author Created by njy on 2023/6/12
* 3.具体建造者(ConcreteBuilder):牛肉汉堡套餐
*/
public class BeefBurgerMealBuilder extends MealBuilder {
@Override
public void buildBurger() {
meal.setBurger("牛肉汉堡");
}
@Override
public void buildFries() {
meal.setFries("大份薯条");
}
@Override
public void buildDrink() {
meal.setDrink("中杯可乐");
}
}
/**
* @author Created by njy on 2023/6/12
* 3.具体建造者(ConcreteBuilder):虾肉汉堡套餐
*/
public class ShrimpMealBuilder extends MealBuilder {
@Override
public void buildBurger() {
meal.setBurger("虾肉汉堡");
}
@Override
public void buildFries() {
meal.setFries("小份薯条");
}
@Override
public void buildDrink() {
meal.setDrink("大杯芬达");
}
}
5.4 指导者(Director)
/**
* @author Created by njy on 2023/6/12
* 4.指导者(Director)
*/
public class MealDirector {
private MealBuilder mealBuilder;
public void setMealBuilder(MealBuilder mealBuilder){
this.mealBuilder=mealBuilder;
}
public Meal getMeal(){
return mealBuilder.getMeal();
}
//制作套餐
public void constructMeal(){
mealBuilder.buildBurger();
mealBuilder.buildFries();
mealBuilder.buildDrink();
}
}
5.5 testBuilder
/**
* @author Created by njy on 2023/6/12
* 建造者模式测试类
*/
@SpringBootTest
public class TestBuilder {
@Test
void testBuilder(){
//创建指导者
MealDirector director=new MealDirector();
//执导建造牛肉套餐
director.setMealBuilder(new BeefBurgerMealBuilder());
director.constructMeal();
Meal meal = director.getMeal();
System.out.println("牛肉套餐:"+meal.toString());
//鸡肉套餐
director.setMealBuilder(new ChickenMealBuilder());
director.constructMeal();
Meal meal2 = director.getMeal();
System.out.println("鸡肉套餐:"+meal2.toString());
//虾肉套餐
director.setMealBuilder(new ShrimpMealBuilder());
director.constructMeal();
Meal meal3 = director.getMeal();
System.out.println("虾肉套餐:"+meal3.toString());
}
}
可以看到,根据不同的需求,建造者模式可以构造出不同的套餐对象。每个套餐的构建过程都由不同的建造者实现,在构建过程中可定制相应的属性。最终,因为套餐的构建过程和表示分离,所以同样的构建过程可以创建出不同的表示。
六、总结
使用场景:
- 当需要创建一些特定的对象,但是它们拥有共同的组成部分时,比如:一个房子可以由个个部件:框架、墙、窗户等,这些部件可以组合起来构造完整的房子。
- 当对象的构建过程比较复杂且需要多个步骤时,例如,创建一份电子商务订单需要多个步骤,如选择商品、填写地址和支付等,这些步骤可以被分别封装成为订单构建器中的不同方法。
- 当需要创建一些特定类型的对象,例如复杂的数据结构或配置对象时,这在编写配置文件解析器以及通用数据结构如二叉树等时很有用。
- 建造者模式也可以被用于通过更高级的方式来构建复杂对象,例如:序列化和反序列化。
`
与抽象工厂模式的区别:
- 抽象工厂模式强调的是产品族的创建,即相关的产品一起被创建出来,而建造者模式强调的是一个复杂对象的创建,即它的各个部分逐步被创建出来。
2.1.5 原型模式(Prototype)
一、什么是原型模式
原型模式属于
创建型
设计模式。通过复制现有的实例来创建新的实例,无需知道相应类的信息。
简单的讲就是当我需要创建一个指定的对象时,刚好现在就有这个对象,但又不能直接使用,所以简单的方式就是克隆一个一摸一样的对象来使用。
二、角色组成
1. 抽象原型类(Prototype):定义了一个抽象的克隆方法。
2. 具体原型类(ConcretePrototyoe):实现抽象原型类(接口)定义的克隆方法,提供一个具体的克隆方法来复制自己。
3. 客户端(Client):使用原型类的对象来实现具体的操作,即通过复制原型对象来创建新的对象。
三、优缺点
优点:
- 提高了对象创建的效率,在创建大量对象时可以节省时间和资源;
- 可以隐藏对象创建和初始化的复杂性,并且更容易管理和维护;
- 可以在运行时动态添加和删除对象;
- 可以保护原始对象,防止意外修改对原对象产生影响。
缺点:
- 必须保证原始对象和克隆对象之间的区别,否则可能会产生副作用;
- 有些对象可能无法进行有效地复制,例如涉及到与其他外部对象交互的对象;
- 原型模式需要给对象添加一个克隆方法。但是,该方法可能不适用于所有对象类型,例如具有命令行参数的程序。
四、应用场景
4.1 生活场景
- 克隆羊多利
- 细胞分裂
- 孙悟空的七十二变
4.2 java场景
- Object类:Java中的所有类都直接或间接继承自Object类,它提供了一个clone()方法,允许对象在克隆时使用它们的原型对象。
- Collection框架:Iterator接口使用原型模式来提供多个访问数据的独立副本(例如ListIterator和Enumeration)。这种方式可以确保迭代器始终指向正确的位置。
- Apache Commons BeanUtils:Apache Commons BeanUtils库采用了原型模式的方法,通过使用BeanUtils.cloneBean()方法来创建新对象并通过复制其属性来克隆一个Bean。
- Spring框架:在Spring框架中,原型范围bean使用原型模式。例如,在Spring中,可以将作用域设置为prototype,来创建一个bean的多个独立实例,这样每次在容器中注入bean时,将创建新的实例。
五、代码实现
下面以英雄联盟塞拉斯窃取其他英雄大招为例。
5.0 UML类图
5.1 HeroSkill(英雄–具体原型类)
定义一个具体原型类(HeroSkill)
,也就是被窃取技能的英雄,实现了Cloneable
接口(抽象原型类)。
/**
* @author Created by njy on 2023/6/7
* 具体的原型类,被窃取技能的英雄
*/
public class HeroSkill implements Cloneable{
private String name;
private String bigMove;
public HeroSkill(){
}
public HeroSkill(String name, String bigMove){
this.name=name;
this.bigMove=bigMove;
}
@Override
public HeroSkill clone() {
HeroSkill clone= null;
try {
clone = (HeroSkill) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
System.out.println("塞拉斯窃取"+name+"的大招:"+bigMove);
return clone;
}
//英雄的大招展示
public void showSkill() {
System.out.println(name+"的大招:"+bigMove);
}
}
5.2 StealManFactory(塞拉斯–客户端)
塞拉斯工厂客户端
,用于复制对象(英雄)
/**
* @author Created by njy on 2023/6/7
* 工厂类,用于在客户端中复制对象
* (塞拉斯,窃取别的英雄技能)
*/
public class StealManFactory {
private HeroSkill heroSkill;
public StealManFactory(HeroSkill heroSkill){
this.heroSkill=heroSkill;
}
public void setHeroSkill(HeroSkill heroSkill){
this.heroSkill=heroSkill;
}
public HeroSkill cloneHeroSkill(){
return heroSkill.clone();
}
}
5.3 TestPrototype
/**
* @author Created by njy on 2023/6/7
* 原型模式
*/
@SpringBootTest
public class TestPrototype {
@Test
void testPrototype(){
//初始化英雄
HeroSkill heroSkill=new HeroSkill("盲僧","神龙摆尾");
//初始化工厂类(塞拉斯)
StealManFactory factory=new StealManFactory(heroSkill);
//复制英雄技能
HeroSkill cloneHeroSkill = factory.cloneHeroSkill();
//塞拉斯复制的技能
cloneHeroSkill.showSkill();
System.out.println("-------下方原英雄技能展示----------");
//原英雄技能
heroSkill.showSkill();
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/eb1ab5e2a52edbed9e5f19cb17c811d4.png)
六、总结
适用于开发的场景:
- 如果一个对象的创建过程包括繁琐的准备工作或重量级的资源初始化,那么每次需要创建新对象时,都需要必须执行这些初始操作,这时就可以使用原型模式,通过复制旧对象来创建新对象,从而避免创建成本高的问题。
- 如果对象需要修改的属性较多,使用原型模式则可以在原始对象的基础上进行修改,减少代码量。
- 如果存在多个对象需要共享同一个数据源,可以使用原型模式基于已有的原始对象来进行克隆,避免了重复创建多个对象。
- 当对象的创建过程涉及多个线程时,需要注意线程安全性。原型模式可以用于在不同的线程之间共享原型对象,并在每个线程中创建对象的副本,确保线程安全性。
`在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
2.2 结构型模式
6,代理模式(Proxy Pattern)就是为其他对象提供一种代理以控制对这个对象的访问。
7,装饰者模式(Decorator Pattern)动态的给一个对象添加一些额外的职责。就增加功能来说,此模式比生成子类更为灵活。
8,适配器模式(Adapter Pattern)是将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
9,桥接模式(Bridge Pattern)是将抽象部分与实际部分分离,使它们都可以独立的变化。
10,组合模式(Composite Pattern)是将对象组合成树形结构以表示“部分–整体”的层次结 构。使得用户对单个对象和组合对象的使用具有一致性。
11,外观模式(Facade Pattern)是为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
12,享元模式(Flyweight Pattern)是以共享的方式高效的支持大量的细粒度的对象。
结构型模式有七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
口诀:经过小元代表的装饰、适配和组合,大桥的外观很nice。
解释:经过小元(享元模式)代表(代理模式)的装饰(装饰器模式)、适配(适配器模式)和组合(组合模式),大桥(桥接模式)的外观(外观模式)很nice。
2.3 行为模式
13,模板方法模式(Template Method Pattern)使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
14,命令模式(Command Pattern)是将一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
15,责任链模式(Chain of Responsibility Pattern),在该模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
16,策略模式(Strategy Pattern)就是准备一组算法,并将每一个算法封装起来,使得它们可以互换。
17,中介者模式(Mediator Pattern)就是定义一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示的相互调用,从而使其耦合性松散,而且可以独立的改变他们 之间的交互。
18,观察者模式(Observer Pattern)定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
19,备忘录模式(Memento Pattern)是在不破坏封装的前提下,捕获一个对象的内部状态, 并在该对象之外保存这个状态。
20,访问者模式(Visitor Pattern)就是表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
21,状态模式(State Pattern)就是对象的行为,依赖于它所处的状态。
22,解释器模式(Interpreter Pattern)就是描述了如何为简单的语言定义一个语法,如何在该语言中表示一个句子,以及如何解释这些句子。
23,迭代器模式(Iterator Pattern)是提供了一种方法顺序来访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
行为型模式有十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
口诀:当车模他爹责备中介时,访问者命令观察者解释一下状态
解释:当车(策略模式)模(模板方法模式)他爹(迭代模式)责(责任链模式)备(备忘录模式)中介(中介者模式)时,访问者(访问者模式)命令(命令模式)观察者(观察着模式)解释(解释器模式)一下状态(状态模式)。
2.3.1 策略模式(Strategy)
一、什么是策略模式
策略模式属于
行为型
设计模式。定义了一系列算法,并将这些算法封装到一个类中,使得他们可以相互替换。这样,我们可以在改变某个对象使用的算法的情况下,选择一个合适的算法来处理特定的任务,主要解决多重if-else的判断逻辑。
二、角色组成
- 环境类(Context):环境类是策略模式的核心类,它持有一个策略对象的引用,并在需要时调用策略对象的方法。
- 抽象策略类(Strategy):定义了策略方法,这些方法表示不同的策略行为。
- 具体策略类(Concrete Strategy):抽象策略类的实现,它实现了抽象策略类中定义的策略方法,并提供了具体的算法实现。不同的具体策略类具有不同的实现算法,它们之间可以相互替换,使得环境类在运行时可以动态地改变策略。
三、优缺点
优点:
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好
缺点:
- 策略类会增多。
- 所有策略类都需要对外暴露。
- 判断逻辑在客户端,需求改变时,要更改客户端的程序。
四、应用场景
4.1 生活场景
- 手机开锁方式:密码、指纹、面部识别
- 出游的交通方式:自驾、汽车、火车、飞机、高铁,每一种方式都是一个策略。
- 开通csdn会员的方式:月卡、年卡、超级年卡、两年卡、连续包月。
4.2 java场景
- Spring Security中的认证和授权策略:在Spring Security中,可以使用不同的策略来定义认证和授权的行为,例如PasswordAuthentication(密码验证)、RememberMeAuthentication(记住我功能验证)、RoleBasedAuthorization(基于角色的授权)等。通过配置不同的策略,可以灵活地适应不同的安全需求。
- Comparator接口:在使用排序算法时,可以通过实现Comparator接口来定义不同的比较策略,从而可以根据需求对对象进行自定义排序。
- 线程池(ThreadPoolExecutor):在ThreadPoolExecutor中,可以通过设定不同的拒绝策略(RejectedExecutionHandler)来处理无法提交的新任务。拒绝策略可以根据系统的需求选择不同的处理方式,如抛出异常、丢弃任务等。
五、代码实现
下面以视频平台为例,解释一下策略模式。
- 环境类(Context):VideoContext
- 抽象策略类(Strategy):VideoStrategy
- 具体策略类(Concrete Strategy):DyVideoStrategy、WxVideoStrategy、KsVideoStrategy
5.0、UML类图
5.1、VideoContext——环境类(Context)
/**
* @author Created by njy on 2023/5/25
* 1.策略环境类(Context)
* 在初始化VideoContext对象时,将所有策略实现类塞进Map中,
* key为视频类型(dy、ks、wx) value为对应的视频实现类
* 通过getVideoStrategy方法,根据videoType从map中渠道对应的视频策略
* 从而隐藏了策略的具体实现逻辑。这种方式可以遵循开闭原则,因为在新增视频类型时,只需要增加对应的实现类
*/
@Component
public class VideoContext {
private static final Map<String, VideoStrategy> videoMap = new HashMap<>();
/**
* 对象初始化时,将所有策略实现类加入到map中
* @param videoStrategies
*/
public VideoContext(List<VideoStrategy> videoStrategies) {
videoStrategies.forEach(strategy -> videoMap.put(strategy.getVideoType(), strategy));
}
/**
* 根据videoType获取对应的策略实现类
* @param videoType
* @return
*/
public VideoStrategy getVideoStrategy(String videoType){
VideoStrategy videoStrategy = videoMap.get(videoType);
if (videoStrategy==null) {
throw new RuntimeException("videoType inValid!");
}
return videoStrategy;
}
}
5.2、VideoStrategy——抽象策略类(Strategy)
/**
* @author Created by njy on 2023/5/25
* 2.抽象策略类(Strategy)
*/
public interface VideoStrategy {
//刷视频
String brushVideo();
//视频类型
String getVideoType();
}
5.3、具体策略类(Concrete Strategy)
/**
* @author Created by njy on 2023/5/25
* 3.具体策略类(Concrete Strategy):微信
*/
@Component
public class WxVideoStrategy implements VideoStrategy{
@Override
public String brushVideo() {
return "我在刷微信视频号";
}
@Override
public String getVideoType() {
return "wx";
}
}
/**
* @author Created by njy on 2023/5/25
* 3.具体策略类(Concrete Strategy):快手
*/
@Component
public class KsVideoStrategy implements VideoStrategy{
@Override
public String brushVideo() {
return "我在刷快手";
}
@Override
public String getVideoType() {
return "ks";
}
}
/**
* @author Created by njy on 2023/5/25
* 3.具体策略类(Concrete Strategy):抖音
*/
@Component
public class DyVideoStrategy implements VideoStrategy{
@Override
public String brushVideo() {
return "我在刷抖音";
}
@Override
public String getVideoType() {
return "dy";
}
}
5.4、TestStrategy
/**
* @author njy
* @date 2023/5/24 13:58
* 策略模式测试类
*/
@SpringBootTest
public class TestStrategy {
@Autowired
private VideoContext videoContext;
@Test
void testStrategy(){
//策略模式
VideoStrategy video1 = videoContext.getVideoStrategy("dy");
String v1 = video1.brushVideo();
System.out.println("v1 : " + v1);
VideoStrategy video2 = videoContext.getVideoStrategy("ks");
String v2 = video2.brushVideo();
System.out.println("v2 : " + v2);
VideoStrategy video3 = videoContext.getVideoStrategy("wx");
String v3 = video3.brushVideo();
System.out.println("v3 : " + v3);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/3f9f81287e52c62ec3cb84f0be3c72ab.png)
2.3.2 迭代器模式(Iterator)
一、什么是迭代器模式
迭代器模式是一种
行为型
设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。
二、角色组成
- 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法,包括hashNext()和next()方法等,用于遍历聚合对象中的元素。
- 具体迭代器(Concrete Iterator):它是实现迭代器接口的具体实现类,负责具体的遍历逻辑。它保存了当前遍历的位置信息,并可以根据需要向前或向后遍历集合元素。
- 抽象聚合器(Aggregate): 一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。
- 具体聚合器(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
三、 优缺点
优点:
- 简化了集合类的接口,使用者可以更加简单地遍历集合对象,而不需要了解集合内部结构和实现细节。
- 将集合和遍历操作解耦,使得我们可以更灵活地使用不同的迭代器来遍历同一个集合,根据需求选择不同的遍历方式。
- 满足开闭原则,如果需要增加新的遍历方式,只需实现一个新的具体迭代器即可,不需要修改原先聚合对象的代码。
缺点:
- 具体迭代器实现的算法对外不可见,因此不利于调试和维护。
- 对于某些小型、简单的集合对象来说,使用迭代器模式可能会显得过于复杂,增加了代码的复杂性。
四、应用场景
4.1 生活场景
- 遍历班级名单:假设你是一名班主任,你需要遍历班级名单来点名。班级名单可以看作是一个集合,每个学生名字可以看作是集合中的一个元素。使用迭代器模式,你可以通过迭代器对象逐个访问学生的名字,而不需要了解班级名单的具体实现细节。
- 遍历音乐播放列表:当我们在手机或电脑上播放音乐时,通常会创建一个播放列表。播放列表可以被视为一个集合,每首歌曲可以被视为集合中的一个元素。使用迭代器模式,我们可以通过迭代器对象逐个访问播放列表中的歌曲,进行播放、暂停或切歌等操作。
4.2 java场景
- 集合框架中的迭代器:在Java中,集合包括List、Set、Map等等,每个集合类中都提供了一个获取迭代器的方法,例如List提供的iterator()方法、Set提供的iterator()方法等等。通过获取对应的迭代器对象,可以对集合中的元素进行遍历和访问。
- JDBC中的ResultSet对象:在Java中,如果需要对数据库中的数据进行遍历和访问,可以使用JDBC操作数据库。JDBC中,查询结果集使用ResultSet对象来表示,通过使用ResultSet的next()方法,就可以像使用迭代器一样遍历和访问查询结果中的数据。
- 文件读取:在Java中,我们可以使用BufferedReader类来读取文本文件。BufferedReader类提供了一个方法readLine()来逐行读取文件内容。实际上,BufferedReader在内部使用了迭代器模式来逐行读取文本文件的内容。
五、代码实现
下面以班级名单为例,解释一下迭代器模式。
- 抽象迭代器:StudentIterator
- 具体迭代器:StudentListIterator
- 抽象聚合器:StudentAggregate
- 具体聚合器:ClassList
5.0 UML类图
5.1 Student——学生实体类
首先我们定义一个学生类,用来表示学生信息。
/**
* @author Created by njy on 2023/6/25
* 学生实体类
*/
@Data
public class Student {
private String name;
private Integer age;
public Student(String name,Integer age){
this.age=age;
this.name=name;
}
}
5.2 StudentIterator——抽象迭代器(Iterator)
接下来创建一个抽象迭代器(学生迭代器)并继承Iterator接口(java.util包下的Iterator)
import java.util.Iterator;
/**
* @author Created by njy on 2023/6/25
* 抽象迭代器(Iterator):学生迭代器
* 实现Iterator接口
* 负责定义访问和遍历元素的接口,例如提供hasNext()和next()方法。
*/
public interface StudentIterator extends Iterator<Student> {
}
5.3 StudentListIterator——具体迭代器(Concrete iterator)
在这个具体迭代器中,实现抽象迭代器,重写hashNext()和next()方法。
/**
* @author Created by njy on 2023/6/25
* 具体迭代器(Concrete iterator):
* 实现抽象迭代器定义的接口,负责实现对元素的访问和遍历。
*/
public class StudentListIterator implements StudentIterator{
private List<Student> students;
private int index;
public StudentListIterator(List<Student> students) {
this.students = students;
this.index = 0;
}
//检查是否还有下一个元素
@Override
public boolean hasNext() {
return (index < students.size());
}
//返回下一个元素
@Override
public Student next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Student student = students.get(index);
index++;
return student;
}
}
5.4 StudentAggregate——抽象聚合器(Aggregate)
定义一个抽象聚合器,并定义一个iterator()方法,用于创建具体的迭代器对象。
/**
* @author Created by njy on 2023/6/25
* 抽象聚合器(Aggregate):学生聚合器
* 提供创建迭代器的接口,例如可以定义一个iterator()方法。
*/
public interface StudentAggregate {
//用于创建具体的迭代器对象
StudentIterator iterator();
void add(Student student);
}
5.5 ClassList——具体聚合器(Concrete Aggregate)
实现抽象聚合器定义的接口,负责创建具体的迭代器对象。
/**
* @author Created by njy on 2023/6/25
* 具体聚合器(ConcreteAggregate):班级列表
* 实现抽象聚合器定义的接口,负责创建具体的迭代器对象,并返回该对象。
*/
public class ClassList implements StudentAggregate{
private List<Student> students = new ArrayList<>();
//创建迭代器对象
@Override
public StudentIterator iterator() {
return new StudentListIterator(students);
}
//向班级名单中添加学生信息
@Override
public void add(Student student) {
students.add(student);
}
}
5.6 testIterator
/**
* @author Created by njy on 2023/6/25
* 迭代器模式测试类
*/
@SpringBootTest
public class TestIterator {
@Test
void testIterator(){
ClassList classList = new ClassList();
// 添加学生信息
classList.add(new Student("张三", 18));
classList.add(new Student("李四", 19));
classList.add(new Student("王五", 20));
// 获取迭代器,遍历学生信息
StudentIterator iterator = classList.iterator();
while(iterator.hasNext()) {
Student student = iterator.next();
System.out.println("学生姓名:" + student.getName() + ",学生年龄:" + student.getAge());
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/31f354d89a9a86d363d6bd8ec9450b92.png)
六、总结
- 迭代器模式提供了一种统一的方式来遍历集合对象中的元素。
- 它将遍历操作封装到一个独立的迭代器对象中,使得我们可以按照特定的方式访问集合中的元素。
- 迭代器模式将集合对象和遍历操作分离开来,提高了代码的灵活性和可维护性。
- 使用迭代器模式可以让我们用相同的方式遍历不同类型的集合对象,而不需要了解集合的内部结构。
2.3.3 模板模式(Template)
一、什么是模板模式
模板模式是一种基于
继承
实现的设计模式,它是行为型
的模式。
主要思想是将定义的算法抽象成一组步骤,在抽象类种定义算法的骨架,把具体的操作留给子类来实现。
通俗地说,模板模式就是将某一行为制定一个框架,然后子类填充细节。比如说做菜,流程通常就是洗菜、切菜、炒菜等步骤,那么这个流程就可以看作是一个模板,而具体做什么菜由子类来实现。
二、角色组成
- 抽象类(Abstract):定义了算法骨架,包含一个或多个抽象方法,这些方法由子类来具体实现。抽象类中通常还包含一个模板方法,用来调用抽象方法和具体方法,控制算法的执行顺序;还可以定义钩子方法,用于在算法中进行条件控制。
- 具体类(Concrete Class):继承抽象类,实现抽象方法。
三、优缺点
优点:
- 提高代码复用性:将算法的骨架定义在父类中,子类只需要实现具体的细节部分,减少了代码的重复。
- 符合开闭原则:在模板模式种,由父类控制子类的执行,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
- 提高代码可维护性:模板模式定义了一套固定的模板,便于开发人员理解和修改,易于维护。
缺点:
- 部分子类可能无法灵活定制:由于模板模式制定的是一个固定的结构,所以某些子类可能无法适用,导致无法实现特定的需求或定制。
- 类的数量增加:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
四、应用场景
4.1 生活场景
- 写文章:假设我们要写一篇文章,其中包括标题、引言、正文和结论等部分。我们可以将文章的写作过程定义为一个模板,在抽象类中定义写作方法,如先写标题、再写引言、接着写正文,最后写结论。具体类就是每篇文章的实现类,它们可以根据主题和内容的不同来实现对应的部分。这样,每个具体文章类只需要关注自己特定的内容,而写作的步骤则由模板来控制
- 学习流程:比如学习某门课程的流程,在学习过程中有一些共同的步骤,比如预习、上课、复习做练习等。我们可以定义一个抽象类StudyCourse,在其中定义学习的方法。具体类就是每门具体课程的实现类,它们根据课程内容和学习方式来实现抽象类中的方法。模板方法则是定义在抽象类中的一组方法,用于规定学习的整体流程和一些基本规则。
4.2 java场景
- JdbcTemplate:JdbcTemplate提供了一系列的模板方法,如execute、query、update等。开发者可以通过继承JdbcTemplate并实现相应的抽象方法来完成数据库操作的具体实现。
- HttpServlet:HttpServlet类是一个抽象类,提供了handleRequest、doGet、doPost等模板方法,用于处理HTTP请求。Servlet开发者可以继承HttpServlet并实现这些方法来处理具体的请求,从而完成一个特定的Servlet实现。
- Servlet过滤器:Java Servlet API中提供了过滤器(Filter)接口,用于对Servlet请求进行拦截和处理。该接口中定义了一个doFilter()方法,该方法是一个模板方法,由子类实现具体的请求拦截和处理方式。
五、代码实现
下面以订外卖为例,解释一下模板模式。假设订外卖的过程包含三个步骤:选择外卖、支付、取外卖、是否打赏,我们可以定义一个OderFood的抽象类,那么选择外卖就可以是抽象方法,需要子类取实现它,支付和取外卖可以定义为具体方法,另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩,还需要定义一个模板方法,用以控制流程;不同的商家,如KFC、星巴克就是具体类。
5.0 UML类图
5.1 OrderFood——抽象类(Abstract)
/**
* @author Created by njy on 2023/6/24
* 1.抽象类(Abstract Class):点外卖
* 包含选择外卖、支付、取外卖三个方法,
* 其中选择外卖为抽象方法,需要子类实现
* 支付、取外卖为具体方法
* 另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩
*/
public abstract class OrderFood {
//模板方法
public void order(){
selectFood();
pay();
getFood();
}
//选择外卖 抽象方法 由子类实现具体细节
public abstract void selectFood();
//是否打赏 钩子方法 可以重写来做条件控制
public boolean isGiveAward(){
return false;
}
//-------具体方法----------
public void pay(){
System.out.println("支付成功,外卖小哥正在快马加鞭~~");
}
//取外卖
public void getFood(){
System.out.println("取到外卖");
if (isGiveAward()){
System.out.println("打赏外卖小哥");
}
}
}
5.2 具体类(Concrete Class)
/**
* @author Created by njy on 2023/6/24
* 具体类(Concrete Class):星巴克
*/
public class Starbucks extends OrderFood{
//实现父类方法
@Override
public void selectFood() {
System.out.println("一杯抹茶拿铁");
}
//重写钩子方法,打赏外卖小哥
@Override
public boolean isGiveAward(){
return true;
}
}
/**
* @author Created by njy on 2023/6/24
* 具体类(Concrete Class):KFC
*/
public class KFC extends OrderFood{
@Override
public void selectFood() {
System.out.println("一份汉堡炸鸡四件套");
}
}
5.3 testTemplate
/**
* @author Created by njy on 2023/6/24
* 模板模式测试类
*/
@SpringBootTest
public class TestTemplate {
@Test
void testTemplate(){
//星巴克(重写了钩子方法,打赏外卖小哥)
OrderFood orderFood=new Starbucks();
orderFood.order();
System.out.println("--------KFC------------");
//KFC
OrderFood orderFood1=new KFC();
orderFood1.order();
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/c5a9e52a0483001e1a605f4ddff460a8.png)
六、总结
我们可以把模板看作是一个公共的蓝本,而子类就像是根据这个蓝本来实现自己独特的需求。所以当我们在开发中遇到一些情况,比如多个类共享一些相同的操作,或者说想要控制子类扩展某个算法的一部分功能时,就可以考虑模板模式了。另外,还有以下几个适用场景以做参考:
- 子类需要扩展算法的部分功能:当需要控制子类对算法的某些步骤进行扩展或修改,同时保持算法骨架不变时,可以使用模板模式。
- 实现多个算法的不同细节:当需要在不同的场景下使用相同的算法,但是细节实现不同,可以使用模板模式。
- 需要统一流程的业务:在一些对流程敏感的业务中,例如订餐、下单等,使用模板方法模式可以统一流程,使代码更加简洁和易维护。并且在统一流程的同时,也能保证业务的正确性。
注意事项: 为防止恶意操作,一般模板方法都加上
final
关键词。
2.3.4 观察者模式(Observer)
一、什么是观察者模式
观察者模式属于
行为型
模式。在程序设计中,观察者模式通常由两个对象组成:观察者和被观察者。当被观察者状态发生改变时,它会通知所有的观察者对象,使他们能够及时做出响应,所以也被称作“发布-订阅模式”。
二、特点
优点:
- 被观察者和观察者对象之间不需要知道对方的具体实现,只需要知道对方的接口,
避免了紧耦合的关系
。- 由于被观察者对象并不关心具体的观察者是谁,所以在程序运行的过程中,可以动态地增加或者删除观察者对象,
增加了灵活性
。符合开闭原则
,当需要添加新的观察者时,只需要添加一个实现观察者接口的类,而不需要修改被观察者对象的代码。缺点:
- 当观察者没有被正确移除时,可能会导致
内存泄漏
的问题。- 实现观察者模式,需要定义多个接口和类,增加了
程序的复杂度
。- 在某些情况下,被观察者和观察者对象之间可能出现
循环依赖
的问题。
三、组成
- 抽象被观察者(Subject):定义了一个接口,包含了注册观察者、删除观察者、通知观察者等方法。
- 具体被观察者(ConcreteSubject):实现了抽象被观察者接口,维护了一个观察者列表,并在状态发生改变时通知所有注册的观察者。
- 抽象观察者(Observer):定义了一个接口,包含了更新状态的方法。
- 具体观察者(ConcreteObserver):实现了抽象观察者接口,存储了需要观察的被观察者对象,并在被观察者状态发生改变时进行相应的处理。
四、应用场景
4.1 生活场景
- 拍卖的时候,拍卖师是观察者,价格是被观察者。拍卖师观察最高标价,然后通知给其他竞价者竞价。
- 共享单车:共享单车是被观察者对象,用户是观察者对象。当有新的单车被放置或被租用时,系统会发送给用户通知。
- 微信公众号:微信公众号是被观察者对象,粉丝是观察者对象。当公众号发布了新的文章或消息时,系统会发送消息给关注该公众号的粉丝。
4.2 程序场景
- 当一个对象的状态发生改变时,需要通知多个对象做出相应的响应。例如,王者荣耀更新前,会通知所有用户要更新的时间。
- 当很多对象同时对某一个主题感兴趣时,可以采用观察者模式实现发布-订阅模式。例如,生产者发送消息到消息队列中,并通知所有订阅此队列的消费者进行消费。
- 数据库开发中,当数据库表中的数据发生变化时,需要通知相关的模块进行更新或其他操作。例如,当用户更新了数据库中的某个记录时,就可以通过观察者模式通知所有注册的监听器进行响应。
五、观察者模式实现
下面以报纸和报纸的订阅者为例,假设你在订阅一份报纸,每天早上送到你门口。你订阅的这份报纸就是被观察者。你和其他的订阅者是观察者。当报纸被送到你门口时,它会自动通知所有的订阅者,让他们知道这份报纸已经到了。
- 抽象被观察者:Newspaper
- 具体被观察者:NewspaperImpl
- 抽象观察者:Subscriber
- 具体观察者:SubscriberImpl
5.0 UML类图
5.1 Newspaper
报纸接口,也就是被观察者接口,包含添加、删除、通知三个动作。
/**
* @author Created by njy on 2023/6/1
* 报纸接口,即被观察者接口
*/
public interface Newspaper {
/**
* 添加订阅者
* @param subscriber
*/
void addSubscriber(Subscriber subscriber);
/**
* 移除订阅者
* @param subscriber
*/
void removeSubscriber(Subscriber subscriber);
/**
* 通知订阅者
* @param message
*/
void notifySubscribers(String message);
}
5.2 NewspaperImpl
报纸接口Newspaper的实现类,维护了一个订阅者列表,当报纸到达时会通知所有订阅者。
import java.util.ArrayList;
import java.util.List;
/**
* @author Created by njy on 2023/6/1
* 报纸实现类
*/
public class NewspaperImpl implements Newspaper{
//订阅者集合
List<Subscriber> subscribers = new ArrayList<>();
//添加订阅者
@Override
public void addSubscriber(Subscriber subscriber) {
subscribers.add(subscriber);
}
//移除订阅者
@Override
public void removeSubscriber(Subscriber subscriber) {
subscribers.remove(subscriber);
}
//通知订阅者
@Override
public void notifySubscribers(String message) {
for (Subscriber s : subscribers) {
s.update(message);
}
}
}
5.3 Subscriber
订阅者接口
/**
* @author Created by njy on 2023/6/1
* 订阅者(即观察者)接口
*/
public interface Subscriber {
void update(String message);
}
5.4 SubscriberImpl
订阅者接口的实现类,用于接收报纸到达的通知。
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author Created by njy on 2023/6/1
* 具体订阅者
*/
@Data
@AllArgsConstructor
public class SubscriberImpl implements Subscriber{
private String name;
@Override
public void update(String message) {
System.out.println(name + "---接到消息: " + message);
}
}
5.5 测试
测试代码中,创建了一个NewspaperImpl对象,然后注册了两个订阅者(李老头、王奶奶),执行了两次通知,第一次通知,李老头和王奶奶都收到了消息,第二次通知前,移除了李老头这个订阅者,只有王奶奶收到了通知。
@SpringBootTest
public class TestObserver {
@Test
void testObserver(){
Newspaper newspaper = new NewspaperImpl();
Subscriber li = new SubscriberImpl("李老头");
Subscriber wang = new SubscriberImpl("王奶奶");
//李老头和王奶奶订阅了报纸
newspaper.addSubscriber(li);
newspaper.addSubscriber(wang);
//报纸到了,通知订阅者
newspaper.notifySubscribers("今天的报纸到了!!!");
//李老头取消订阅了,移除
newspaper.removeSubscriber(li);
newspaper.notifySubscribers("明天的报纸还是这个点到!!!");
}
}
2.3.5 命令模式(Command)
一、什么是命令模式
命令模式(Command Pattern)是一种
行为型
设计模式,又叫动作模式或事务模式。它将请求(命令)封装成对象,使得可以用不同的请求对客户端
进行参数化,具体的请求可以在运行时更改、排队或记录,它讲发出者和接收者解耦(顺序:发出者–>命令–>接收者)
本质:封装请求
二、角色组成
- 抽象命令(Command):命令是一个抽象接口,定义了执行操作的统一方法。具体的命令类会实现这个接口,并提供执行相应操作的具体逻辑。
- 具体命令(Concrete Command):具体命令类实现了抽象命令,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
- 接收者(Receiver):执行实际命令的类,命令对象会调用接收者的方法来执行请求。
- 调用者(Invoker):持有命令对象,通常是多个,并通过访问命令对象来执行相关请求,他不直接访问接收者。
三、优缺点
优点:
- 解耦:命令模式可以将发送命令的对象和执行命令的对象解耦,使得两者可以独立变化。
- 可扩展性:可以方便地添加新的命令类和接收者类,而无需修改现有的代码结构。
- 容易实现撤销和重做功能:由于每个命令对象都封装了具体的操作,可以很容易地实现命令的撤销和重做功能。
- 支持操作的队列化和延迟执行:命令对象可以被组织成队列或堆栈,实现对操作的排队和延迟执行。
缺点:
- 增加了类和对象的数量:使用命令模式可能会增加一些新的类和对象,从而增加了代码的复杂性。
- 需要额外的开销:封装命令对象和操作会增加一些额外的开销,可能会稍微降低性能。
- 可能导致过多的具体命令类:如果系统的命令比较多,可能会导致需要创建很多具体的命令类,增加了代码维护的难度。
四、应用场景
4.1 生活场景
- 餐厅点餐:在一家餐厅中,服务员充当调用者,厨师充当接收者,菜品可以作为具体命令。当顾客想点菜时,服务员会将顾客的需求封装成一个命令对象,并传递给厨师。厨师根据命令对象中的信息来完成相应的烹饪工作。这样,顾客和厨师之间不需要直接沟通,而是通过命令对象来实现点餐和烹饪的解耦。
- 遥控器控制家电:拿电视遥控器举例,遥控器是调用者,电视是接收者。每个按键都可以看作是一个具体命令,例如音量加、音量减、切换频道等。当用户按下某个按键时,遥控器会发送相应的命令给电视,然后电视根据命令执行相应的操作,如增加音量、减小音量或切换频道。
4.2 java场景
- Runnable接口:Java中的Runnable接口就是一个典型的命令模式的应用。Runnable接口封装了需要执行的任务,然后可以交给线程去执行。
- Timer和TimerTask类:这两个类用于定时任务调度,TimerTask类封装了要执行的任务,然后由Timer类作为调用者执行这些任务。
- Statement接口:在Java中与数据库交互时,SQL语句被封装成Statement对象,然后由数据库驱动程序执行相应的命令。
五、代码实现
下面以餐厅点餐为例,解释以下命令模式。在一家餐厅中,服务员充当调用者,厨师充当接收者,菜品可以作为具体命令。当顾客想点菜时,服务员会将顾客的需求封装成一个命令对象,并传递给厨师。厨师根据命令对象中的信息来完成相应的烹饪工作。
- 抽象命令:Command
- 具体命令:OrderCommand
- 接收者:Chef
- 调用者:Waiter
5.0 UML类图
![](https://i-blog.csdnimg.cn/blog_migrate/eb251180a0ec572f9f107245aa3ec746.png)
5.1 抽象命令(Command)——Command
首先,创建一个命令接口(Command),它定义了点菜和取消点菜的方法
/**
* @author Created by njy on 2023/6/29
* 1.抽象命令(Command): 点菜和取消两个命令
* 定义:命令是一个抽象接口,定义了执行操作的统一方法。
*/
public interface Command {
//点菜
void order();
//取消点菜
void cancelOrder();
}
5.2 接收者(Receiver)——Chef
创建接收者类(Chef)实现具体的烹饪操作。
/**
* @author Created by njy on 2023/6/29
* 2.接收者(Receiver):厨师
* 定义:执行实际命令的类,命令对象会调用接收者的方法来执行请求。
*/
public class Chef {
public void cook() {
System.out.println("厨师执行点菜命令:正在烹饪菜品...");
}
public void cancelCooking() {
System.out.println("厨师执行取消命令:停止烹饪菜品!");
}
}
5.3 具体命令(Concrete Command)——OrderCommand
创建具体命令类(如点菜命令)实现命令接口,并将点菜的请求和具体的烹饪者(厨师)关联起来
/**
* @author Created by njy on 2023/6/29
* 3.具体命令(Concrete Command):点菜命令
* 定义:具体命令类实现了抽象命令,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
*/
public class OrderCommand implements Command{
// 厨师
private Chef chef;
public OrderCommand(Chef chef) {
this.chef = chef;
}
public void order() {
//与具体的烹饪者(厨师)关联,执行点菜操作
chef.cook();
}
public void cancelOrder() {
//与具体的烹饪者(厨师)关联,执行取消点菜操作
chef.cancelCooking();
}
}
5.4 调用者(Invoker)——Waiter
最后,在服务员类中,创建调用者(服务员),接收命令(点菜命令),并执行相应的操作
/**
* @author Created by njy on 2023/6/29
* 4.调用者(Invoker):服务员
* 定义:持有命令对象,通常是多个,并通过访问命令对象来执行相关请求,他不直接访问接收者。
*/
public class Waiter {
//命令对象
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void takeOrder() {
// 服务员接收到顾客的点菜请求
System.out.println("服务员接收到顾客(客户端)点菜请求!");
// 执行点菜操作
command.order();
}
public void cancelOrder() {
// 服务员收到顾客的取消点菜请求
System.out.println("服务员接收到顾客(客户端)取消点菜请求!");
// 执行取消点菜操作
command.cancelOrder();
}
}
5.5 testCommand
/**
* @author Created by njy on 2023/6/29
* 命令模式测试类
*/
@SpringBootTest
public class TestCommand {
@Test
void testCommand(){
// 创建厨师(接收者)
Chef chef = new Chef();
// 创建点菜命令
Command orderCommand = new OrderCommand(chef);
// 创建服务员(调用者)
Waiter waiter = new Waiter();
// 设置命令
waiter.setCommand(orderCommand);
// 服务员接收到点菜请求
waiter.takeOrder();
// 服务员接收到取消点菜请求
waiter.cancelOrder();
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/f8a87c1d7a22d9e59ef75c7cb2759b05.png)
六、总结
命令模式适用于需要将请求封装成对象,实现请求发出者和接收者之间的解耦,并支持撤销、队列化和延迟执行的场景。它虽然可以提高系统的可扩展性和灵活性,但是需要权衡额外的开销和复杂性。