分享 Java 开发中常用到的设计模式

前言

不知道大家在开发的时候,有没有想过(遇到)这些问题:

  • 大家都是按需要开发,都是一个职级的同事,为什么有些人的思路就很清晰,代码也很整洁、易懂;而自己开发,往往不知道怎么下手设计,写完了也是bug一堆,codereview的时候更是频频被怼...

  • 感觉每天都是CURD,写重复的代码,做类似的需求,怎么才能提高自己的水平?

  • 每每看到大佬的代码,或者优秀框架的源码,总是似懂非懂,怀疑自己是不是缺少了哪些知识?

如果你有这些问题,或者思考过这些问题,那么你起码意识到了自己的不足,这其实是没有熟练掌握软件开发中的重要技能-设计模式而导致的。

先大致来看一下关于设计模式的思维导图,包括六大软件开发原则、设计模式分类以及23种设计模式的名称:

23种设计模式总结

作为软件开发人员,看着这些设计模式的名称,你是否多少会有一些印象呢?

今天就和大家一起分享一下几个在 Java 开发中常见的设计模式,包括具体的应用场景、代码 demo 以及 UML 图解等。

图解设计模式


一、观察者模式

1.1基本概念

观察者模式,即Observer模式,顾名思义,指的是:当被观察对象发生变化时,会告知观察者。适用于根据对象状态进行相应处理的场景。

1.2Demo案例

下面用一个demo来直观地展示观察者模式的实际应用场景:观察者将会观察一个能生成分数的对象,并且观察者还可以通过观察,得到不同形式的分数结果(数字形式、图示形式)。

类和接口一览,如表1.1所示:

表1.1

名称

说明

Observer

观察者的接口,感知来自被观察对象的状态变化

AbstractScoreGenerator

被观察者的抽象类

ScoreGenerator

被观察者的具体功能实现

DigitalObserver

具体的观察者,感知被观察者的变化,用数值的形式实现

GraphObserver

具体的观察者,感知被观察者的变化,用图示的形式实现

Main

入口

接口与各个类的类图,如图1-1所示:

图1-1

  • Observer接口

    publicinterfaceObserver {
    /**
         * 1、观察者的抽象方法,作用是感知来自被观察对象的状态变化;
         * 2、即两个具体的观察者实现该方法后,可以得到来自被观察者的一些变化。
         * @param abstractScoreGenerator
         */
    voidupdate(AbstractScoreGenerator abstractScoreGenerator);
    }
    
  • AbstractScoreGenerator抽象类

    publicabstractclassAbstractScoreGenerator {
    /**
         * 获取分数的抽象方法
         * @return 分数
         */
    publicabstractintgetScore();
    
    /**
         * 生成分数的抽象方法
         */
    publicabstractvoidexecute();
    
    /**
         * 初始化观察者Observer集合
         */
    privatefinal ArrayList<Observer> observerList = newArrayList<>();
    
    /**
         * 添加/注册Observer观察者
         * @param observer
         */
    publicvoidaddObserver(Observer observer){
            observerList.add(observer);
        }
    
    /**
         * 向Observer观察者发送通知
         */
    publicvoidnotifyObservers(){
    for (Observer observer : observerList) {
                observer.update(this);
            }
        }
    
    }
    
  • ScoreGenerator功能实现类

    publicclassScoreGeneratorextendsAbstractScoreGenerator {
    
    /**
         * 随机数对象
         */
    privatefinalRandomrandom=newRandom();
    
    privateint score;
    
    /**
         * 子类必须重写父类的抽象方法
         * @return
         */
    @Override
    publicintgetScore() {
    return score;
        }
    
    /**
         * 子类必须重写父类的抽象方法
         * @return
         */
    @Override
    publicvoidexecute() {
    for (inti=0; i < 10; i++) {
                score = random.nextInt(20);
    this.notifyObservers();
            }
        }
    
    }
    
  • DigitalObserver感知类

    publicclassDigitalObserverimplementsObserver {
    /**
         * 具体的观察者,感知被观察者的变化,用数值的形式实现
         * @param abstractScoreGenerator
         */
    @Override
    publicvoidupdate(AbstractScoreGenerator abstractScoreGenerator) {
            System.out.println("数值观察者:" + abstractScoreGenerator.getScore());
    try {
                Thread.sleep(100);
            }catch (InterruptedException e) {
    thrownewRuntimeException(e);
            }
        }
    }
    
  • GraphObserver感知类

    publicclassGraphObserverimplementsObserver {
    /**
         * 具体的观察者,感知被观察者的变化,用图示的形式实现
         * @param abstractScoreGenerator
         */
    @Override
    publicvoidupdate(AbstractScoreGenerator abstractScoreGenerator) {
            System.out.print("图示观察者:");
    intcount= abstractScoreGenerator.getScore();
    for (inti=0; i < count; i++) {
                System.out.print("*");
            }
            System.out.println("");
    try {
                Thread.sleep(100);
            }catch (InterruptedException e) {
    thrownewRuntimeException(e);
            }
        }
    }
    
  • Main入口

    publicclassMain {
    publicstaticvoidmain(String[] args) {
    // 抽象类不可以实例化,子类可以实例化,且由于继承关系,子类可以调用父类的普通方法
    ScoreGeneratorscoreGenerator=newScoreGenerator();
    // 接口不能实例化,但是可以通过实例化实现接口的类,从而”构造“该接口,并实现接口的抽象方法
    DigitalObserverdigitalObserver=newDigitalObserver();
    GraphObservergraphObserver=newGraphObserver();
    // 注册两个观察者(数值和图示),实际上就是成生成两个”接口对象“,目的是调用接口的具体实现
            scoreGenerator.addObserver(digitalObserver);
            scoreGenerator.addObserver(graphObserver);
    // 计算分数结果:引入两个具体的观察者,将本方法计算的结果调用各自的具体实现
            scoreGenerator.execute();
        }
    }
    

    运行结果(部分),如图1-2所示:

图1-2

1.3模式要点
  • 四种角色:观察者(接口)、具体观察者(接口实现类)、被观察者(抽象类),被观察者实现(子类)

  • 利用抽象类和接口从具体的类中抽出抽象方法

  • 将实例作为参数传递到类中,且不使用具体类型,而使用抽象类型或接口

  • 观察者(Observer)不关心要观察的对象是谁,被观察者(Subject)也不关心是谁在观察自己,两者之间通过抽象类和接口产生关联


二、策略模式

2.1基本概念

策略模式,即Strategy模式:用不同的算法(方式)去解决同一个问题,并各个算法间得到替换,其主要目的是通过定义相似的算法,替换if-else写法。

2.2Demo案例

接下来同样还是使用一个简单的demo来介绍策略模式。

Java 在进行数值计算的时候,会经常用到加减乘除方法。如果我们想得到两个数字相加的和,我们需要用到“+”符号,得到相减的差,需要用到“-”符号等。

虽然我们可以通过字符串比较使用if-else写成通用方法,但是计算的符号每次增加,我们就不得不加在原先的方法中进行增加相应的代码,如果后续计算方法增加、修改或删除,那么会使后续的维护变得困难。
但是在这些方法中,我们发现其基本的加减乘除是固定的,这时我们就可以通过策略模式来进行开发,可以有效避免通过if-else来进行判断,即使后续增加其他的计算规则也可灵活进行调整。

类和接口一览,如表2.1所示:

表2.1

名称

说明

Strategy

策略的对外接口,所有具体策略类都需实现该接口

CalculatorContext

策略的上下文

OperationAddition

一个策略的具体实现

OperationDivision

一个策略的具体实现

OperationMultiplication

一个策略的具体实现

Main

入口

下面则是UML类图,如图2-1所示:

图2-1

  • Strategy接口

    publicinterfaceStrategy {
    /**
         * 策略的对外接口,所有具体策略类都需实现该接口
         * @param num1
         * @param num2
         * @return
         */
    intcalculate(int num1, int num2);
    }
    
  • CalculatorContext 策略上下文类

    publicclassCalculatorContext {
    
    privatefinal Strategy strategy;
    
    /**
         * 接口作为类的属性,有参构方法造,构造策略上下文对象
         * @param strategy
         */
    publicCalculatorContext(Strategy strategy){
    this.strategy = strategy;
        }
    
    /**
         * 策略执行方法,调用后会直接实现对应的策略
         * @param num1
         * @param num2
         * @return
         */
    publicintexecuteStrategy(int num1, int num2){
    return strategy.calculate(num1,num2);
        }
    }
    
  • OperationAddition 策略的具体实现类(加法)

    publicclassOperationAdditionimplementsStrategy {
    
    /**
         * 表示加法的具体策略,两数相加
         * @param num1
         * @param num2
         * @return
         */
    @Override
    publicintcalculate(int num1, int num2) {
    return num1 + num2;
        }
    
    }
    
  • OperationDivision 策略的具体实现类(减法)

    publicclassOperationDivisionimplementsStrategy {
    
    /**
         * 表示减法的具体策略,两数相减
         * @param num1
         * @param num2
         * @return
         */
    @Override
    publicintcalculate(int num1, int num2) {
    return num1 - num2;
        }
    
    }
    
  • OperationMultiplication 策略的具体实现(乘法)

    publicclassOperationMultiplicationimplementsStrategy {
    
    /**
         * 表示乘法的具体策略,两数相乘
         * @param num1
         * @param num2
         * @return
         */
    @Override
    publicintcalculate(int num1, int num2) {
    return num1 * num2;
        }
    
    }
    
  • Main入口

    publicclassMain {
    publicstaticvoidmain(String[] args) {
    
    // 初始化分值
    intnum1=6;
    intnum2=8;
    
    // 实例化上下文对象,通过实例化实现接口的某个类去生成”接口对象“,目的是调用对应实现类的抽象方法实现
    CalculatorContextcalculatorContext1=newCalculatorContext(newOperationAddition());
            System.out.println("加法策略:num1+num2= " + calculatorContext1.executeStrategy(num1, num2));
    // 通过调用每个不同实现类的不同抽象方法实现,可以解决if-else的逻辑判断
    CalculatorContextcalculatorContext2=newCalculatorContext(newOperationDivision());
            System.out.println("减法策略:num1-num2= " + calculatorContext2.executeStrategy(num1, num2));
    // 策略模式最核心的:客户端可以通过实例化不同的”接口对象“去调用不同的具体实现
    CalculatorContextcalculatorContext3=newCalculatorContext(newOperationMultiplication());
            System.out.println("乘法策略:num1*num2= " + calculatorContext3.executeStrategy(num1, num2));
        }
    }
    

运行结果,如图2-2所示:

图2-2

2.3模式角色

主要由这3个角色组成:

  • 策略上下文角色(Context),提供给客户端使用,持有该类的一个策略对象的引用,核心就是串联策略接口与其具体实现。

  • 抽象策略角色(Strategy):这是一个抽象角色,通常是接口或者抽象类,给出了所有具体策略类所需的接口。

  • 具体策略角色:封装了一些具体的实现方法(行为)或算法。

2.4模式要点
  • 为什么需要抽象策略角色?

    通常我们在开发的时候,具体的行为会写在方法中,而策略模式却将算法与其它部分分离开,只是暴露了算法的API接口,然后在需要使用的地方以委托的方式来使用。

    这样看起来代码好像变复杂了,有种脱裤子放屁的嫌疑。其实不然,当业务上有调整需要新增、修改这些算法时,我们只需要选择性地调用即可,就不必再修改策略角色了。最重要的是,利用委托这种弱关联关系可以方便地整体替换算法。


三、建造者模式

大都市中林立着许多高楼大厦,这些高楼大厦都是具有建筑结构的大型建筑,在英文中通常把这些建筑称为 Builder。

建造这些庞然大物时,一般难以一气呵成。此时我们需要先建造组成这个物体的各个部分,然后再分阶段将它们组装起来。

在编写代码时,我们肯定遇到过新建对象、并为对象的属性赋值的场景。下面就介绍 Builder 建造者模式在创建对象并赋值时的作用。

3.1传统的Builder模式

首先介绍的是更为抽象的传统Builder模式,其核心思想在于:分阶段组装具有复杂结构的实例,并隐藏具体的组装过程,本质还是一种调用抽象方法具体实现的思想。

需要借助的角色有4个:需要被Builder的类、抽象的Builder类、具体实现Builder的类、调用具体实现的Director。如表3.1所示:

表3.1

名称

说明

Computer

一个需要被建造的实体类,其属性就是这个“建筑的一部分”

ComputerBuilder

一个抽象类,定义了建造属性的抽象方法

ConcreteComputerBuilder

一个具体的实现类,实质上就是使用setter方法为属性赋值

Director

监督者,实质上就是调用方

Main

入口

下面是对应的UML图,如图3-1所示:

图3-1

具体的场景:模拟由中央处理器、内存和硬盘这3种部件组装成一台计算机的过程。以下是按照角色出场顺序的代码 demo 示例:

  • Computer类
@Data
publicclassComputer {
/**
     * 中央处理器
     */
private String cpu;

/**
     * 内存
     */
private String memory;

/**
     * 硬盘
     */
private String disk;
}
  • ComputerBuilder建造抽象类
publicabstractclassComputerBuilder {

// 创建产品对象
protectedComputercomputer=newComputer();

// 创建产品对象的各组成部件,即设置对象的属性
publicabstractvoidsetCpu();
publicabstractvoidsetMemory();
publicabstractvoidsetDisk();

// 返回产品对象
public Computer getComputer(){
return computer;
    }
}
  • ConcreteComputerBuilder具体的建造过程
publicclassConcreteComputerBuilderextendsComputerBuilder {

@Override
publicvoidsetCpu() {
        computer.setCpu("i5-7500");
    }

@Override
publicvoidsetMemory() {
        computer.setMemory("16GB");
    }

@Override
publicvoidsetDisk() {
        computer.setDisk("500GB");
    }
}
  • Director监督者调用
publicclassDirector {

privatefinal ComputerBuilder computerBuilder;

/**
     * 有参构造
     * @param builder
     */
publicDirector(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
    }

/**
     * 调用抽象方法的具体实现
     * @return
     */
public Computer computerConstruct() {
// 调用 builder 的属性设置方法
        computerBuilder.setCpu();
        computerBuilder.setMemory();
        computerBuilder.setDisk();
// 返回组装好的电脑
return computerBuilder.getComputer();
    }
}
  • Main主类
publicclassMain {
publicstaticvoidmain(String[] args) {
ConcreteComputerBuilderbuilder=newConcreteComputerBuilder();
Directordirector=newDirector(builder);
Computercomputer= director.computerConstruct();
// 查看电脑信息
        System.out.println("cpu型号:"+computer.getCpu()+"," +"内存大小:"+computer.getMemory()+ ","+"硬盘大小:"+ computer.getDisk());
    }
}

运行结果,如图3-2所示:

图3-2

#### 3.2改良后的Builder模式

而改良后的Builder模式,只需要两步就可以实现建造一个类并为其赋值的过程,同时还可以灵活地调整顺序,也可以只建造一部分。

具体分为两个部分:1、添加@Builder注解;2、链式调用建造。

  • 添加@Builder注解
@Builder
publicclassComputer {
/**
     * 中央处理器
     */
private String cpu;

/**
     * 内存
     */
private String memory;

/**
     * 硬盘
     */
private String disk;
}
  • 链式调用建造
publicclassMain {
publicstaticvoidmain(String[] args) {
Computercomputer= Computer.builder()
               .cpu("R7-6800H")
               .memory("SAMSUNG 32GB")
               .disk("SSD 1TB")
               .build();
// 查看电脑信息
        System.out.println("cpu型号:" + computer.getCpu() + ","
                         + "内存大小:"+ computer.getMemory() + ","
                         + "硬盘大小:"+ computer.getDisk());
    }
}

运行结果,如图3-3所示:

图3-3

3.3模式要点

先说说改良版的优点:

  1. 对象的创建过程更加灵活,可以选择性的初始化对象的某些属性,而非所有属性;

  2. 相对通过构造函数创建对象,代码可读性更高:能将属性和所赋的值关联起来,也能清晰地知道对象的内容。

再说缺点(不算缺点的缺点):

  1. 一旦需要建造的对象属性有变化,在链式调用的地方也需要同步修改。

传统版的建造者模式,在许多优秀的开源框架中有大量地应用,由于本人的水平、认识有限,在实际的项目中很少使用到这样的思想去创建对象并赋值。

但不乏举出两个显而易见的优点:

  1. 封装性好,实现了对象的创建与表示分离;

  2. 扩展性好,具体建造者之间相互独立,有利于系统的解耦。


四、外观(门面)模式

随着时间的推移、需求的增加,程序的结构很有可能会越来越大,子系统可能会越来越多。

我们可以为项目或者程序准备一个对外的”窗口“,这样用户不需要关注每个类和接口之间的联系,只需简单地对这个窗口提出请求即可。

4.1基本概念

facade 来源于法语单词,原意为”建筑物的正面“,使用 facade 模式,其中的 facade 角色可以让整个系统对外只有一个简单的API,并且还会考虑到系统内部各个类之间的依赖关系,同时按照正确的顺序调用各个类。

4.2Demo案例

下面以一个博客系统项目的设计为 demo 举例,博客系统里主要包括博客(文章)后台,APP端(H5)。

其中后台又具体包括:博客编辑(用户)、博客审核(管理员)、数据统计(点赞&收藏)、用户列表等模块;

下面以博客编辑为例,看看 facade 模式在这样的系统中扮演了什么角色,又起到了什么作用。

类和接口一览,如表4.1所示:

表4.1

名称

说明

BlogController

与前端交互的接口暴露,Restful-API 风格

BlogFacade

facade 角色,BlogController 直接引用该类的对象

BlogService

抽象方法的集合

BlogServiceImpl

抽象方法的具体实现

BlogMapper

操作 MySQL 的封装框架

Main

程序入口

在写具体的代码之前,我们可以先梳理一下项目的基本结构,如图4-1所示,除了经典的 controller、service、model、mapper 外,还有一个facade 层

大家可以重点观察一下,看新加了 facade 后,系统内部各个类、接口之间的调用是怎么样的关系。如图4-1所示:

图4-1

下面则是对应的UML图,如图4-2所示:

图4-2

  • BlogController
@RestController
@RequestMapping("/Blog")
@Api(value = "博客接口", tags = "Blog")
publicclassBlogController {

@Resource
private BlogFacade blogFacade;

@ApiOperation(value = "新增博客", httpMethod = "POST", response = Boolean.class)
@PostMapping("/createBlog")
public ResponseData createBlog(@RequestBody CreateBlogDto dto){
Booleanboolean= blogFacade.createBlog(dto);
return ResponseData.success(boolean);
    }

@ApiOperation(value = "编辑博客", httpMethod = "POST", response = Boolean.class)
@PostMapping("/updateBlog")
public ResponseData updateBlog(@RequestBody UpdateBlogDto dto){
Booleanboolean= blogFacade.updateBlog(dto);
return ResponseData.success(boolean);
    }
}
  • BlogFacade
@Service
publicclassBlogFacade {
/**
     * 注入interface
     */
@Resource
private BlogService blogService;
/**
     * 注入interface
     */
@Resource
private BlogLikeService blogLikeService;

/**
     * 具体实现
     */
@Transactional(rollbackFor = Exception.class)
@SneakyThrows
public Boolean createBlog(CreateBlogDto dto){
Blogblog=newBlog();
        BeanUtils.copyProperties(dto, blog);
        blog.setCreateTime(newDate());
IntegerblogId= blogService.saveBlog(blog);
    }

@Transactional(rollbackFor = Exception.class)
@SneakyThrows
public Boolean updateBlog(UpdateBlogDto dto){
// 具体实现
// 略
        }

}

与经典的在Controller类中注入service不同,这里引入的是facade类,在facade类中再去注入service的接口,整个facade类中做的是Controller中所需要的具体实现。

  • BlogService
publicinterfaceBlogServiceextendsIService<Blog> {

/**
     * 新建博客
     * @param blog
     * @return 是否成功
     */
    Integer saveBlog(Blog blog);
}
  • BlogServiceImpl
@Service
publicclassBlogServiceImplextendsServiceImpl<BlogMapper, Blog> implementsBlogService {

@Resource
private BlogMapper blogMapper;

/**
     * 新建博客
     * @param blog
     * @return
     */
@Override
public Integer saveBlog(Blog blog) {
this.save(essayManage);;
return blog.getId();
    }

而service层中的impl实现类,只做与数据库相关的操作。

  • BlogMapper
@Mapper
publicinterfaceBlogMapperextendsBaseMapper<Blog> {
}

有的同学可能已经发现了,facade 好像只是把原来在 impl 层做的逻辑实现,放到了 facade 层里,但仍然还是要实现 service 接口的抽象方法,那还有必要再分一个facade 层出来吗?

答案:在整个系统复杂且子系统多的时候,比较适合使用 facade 模式。

4.3模式要点

facade 模式出现的角色比较简单,分为3个:facade 角色、系统其它角色(类或接口等)、请求方。

  • facade 角色:抽象角色,负责将请求中转(转发)给子系统处理

  • 系统其它角色:功能的具体实现,目的是完成内部封装,只返回数据或结果给API;

  • 请求方:其实就是项目启动后,调用接口的用户,或者说是对外暴露可供调用的API;

由以上分析可知,Facade设计模式更注重从构架的层次去看整个系统,而不是单个类、接口的层次,对于各个子系统的解耦很有帮助。

解耦的重点在于:起码从直观上可以很明显地发现——interface变少了,这里的API指的是系统内部的接口,而非暴露给外部的Restful-API。

在开发的时候,如果有这么一种模式:能让接口变少的同时,还能让我们专注逻辑实现,且可以方便地对外暴露请求的 facade 角色,该是多么地美好!


五、适配器模式

5.1基本概念

常见的设计模式之一,其最核心的思想:在不改变现有系统结构的情况下,将一个类的接口转换成用户希望的另一个接口

5.2Demo案例

下面介绍两种不同实现,这两种都是很经典的适配器模式实现。

5.2.1类适配器

本质是通过继承的方式来实现接口的适配的,具体看代码大家就明白了,这个继承的妙处到底在哪里。

背景简述:我到香港迪士尼去游玩,晚上在酒店想给笔记本充电,但我发现香港的插座是英式三角插座,我的充电器插不进去,这个时候就可以使用适配器模式进行适配。

类与接口的关系如表5.1所示:

表5.1

名称

说明

BritishStandard

已经存在的角色接口,是面向用户的、最终使用的接口

ChineseStandard

也是已存在的角儿,是需要被转换(被适配)的接口

StandardAdapter

新的角色,也是核心角色,作用是转换接口

Main

程序入口

对应的UML图所图5-1所示:

图5-1

  • BritishStandard接口
publicinterfaceBritishStandard {
/**
     * 目标角色,用户只适配这个接口
     * @return
     */
    String getBritishStandard();

}
  • ChineseStandard类
publicclassChineseStandard {

/**
     * 已存在的角色,但是需要被转换才能被用户使用
     * @return
     */
public String getChineseStandard() {
return"中式插座";
    }

}
  • StandardAdapter适配器类
publicclassStandardAdapterextendsChineseStandardimplementsBritishStandard {

/**
     * 实质是通过继承,将源方法放入目标方法中
     * @return
     */
@Override
public String getBritishStandard() {
returnthis.getChineseStandard();
    }
}
  • 启动入口(笔记本)
publicclassNotebook {

public Boolean charge(BritishStandard britishStandard) {
if ("中式插座".equals(britishStandard.getBritishStandard())) {
            System.out.println("充电成功!");
return Boolean.TRUE;
        } else {
thrownewBusinessException("充电失败!");
        }
    }

publicstaticvoidmain(String[] args) {
// 通过实例化实现接口的类来传递"接口对象"
Booleanresult=newNotebook().charge(newStandardAdapter());
        Assert.isTrue(result,"适配失败!请重试");
    }

}

运行结果如图5-2所示:

图5-2

5.2.2对象适配器

本质上是通过构造器传递(委托)的方式来实现适配的,具体看代码:

背景简述:我的车有车载音乐播放系统,一个是播放数字音乐的接口MusicPlayer,另一个是播放CD光盘的接口CdPlayer。而我想要将CD光盘中我喜欢的音乐转化成 mp3 的数字音乐格式来播放。这个时候就可以使用适配器模式进行适配。

类与接口的关系如表5.2所示:

表5.2

名称

说明

MusicPlayer

已经存在的角色接口,是面向用户的、最终使用的接口

CdPlayer

也是已存在的角儿,是需要被转换(被适配)的接口

PlayerAdapter

新的角色,也是核心角色,作用是转换接口

Main

程序入口

对应的UML图所图5-3所示:

图5-3

  • MusicPlayer接口
publicinterfaceMusicPlayer {

/**
     * 已存在的角色,用户只能使用这个接口
     * @param fileName
     */
    String playMusic(String fileName);
}
  • CdPlayer类
publicclassCdPlayer {

/**
     * 已存在的角色,需要被转换才能使用
     * @param fileName
     * @return
     */
    String playCD(String fileName){
return"播放CD歌曲"+ fileName +"成功!";
    }
}
  • PlayerAdapter适配器类
@AllArgsConstructor
publicclassPlayerAdapterimplementsMusicPlayer{

@Resource
private CdPlayer cdPlayer;

/**
     * 使用有参构造的方式,传递对象
     * @param fileName
     * @return
     */
@Override
public String playMusic(String fileName) {
return cdPlayer.playCD(fileName);
    }
}
  • 入口(播放音乐)
publicclassPlay {
publicstaticvoidmain(String[] args) {
PlayerAdapterplayerAdapter=newPlayerAdapter(newCdPlayer());
Stringresult= playerAdapter.playMusic("《韩宝仪-往事只能回味》");
        System.out.println(result);
        Assert.hasLength(result, "播放失败,请重试!");
    }
}

运行结果如图5-4所示:

图5-4

4.3模式要点

出现的3种角色:

  • 目标角色(Target):已经存在的角色,是用户最终需要的接口;

  • 源角色(Adaptee):需要被转换的接口,也是已经存在的角色;

  • 适配器角色(Adapter):核心角色,通过继承或者类关联的方式将源角色转换为目标角色。

优缺点分析:

类适配器

  • 优点:可以根据需求重写 Target 的方法,使得 Adapter 的灵活性增强了。

  • 缺点:有一定局限性。因为类适配器需要继承 Adaptee 类,而 Java 是单继承机制,所以要求 Adaptee 必须是一个类。

对象适配器

  • 优点:同一个 Adapter 可以把 Adaptee 类和他的子类都适配到目标接口。

  • 缺点:需要重新定义 Adaptee 行为时,需要重新定义 Adaptee 的子类,并将适配器组合适配。


文章小结

到这里5种常用的设计模式就和大家分享完了,熟练使用设计模式可以提高我们的代码质量,且能使得我们的程序设计地更优雅,也更易读易懂。

由于本人水平有限,对于文章有问题的地方欢迎大家指正,不吝赐教,有其它想法也可以在评论区一起交流学习。


参考文献

  1. 《图解设计模式》【日】结城浩 著,杨文轩 译,中国工信出版集团,人民邮电出版社;

  2. https://www.cnblogs.com/xuwujing/p/9954263.html#5195605

  3. https://blog.youkuaiyun.com/u014454538/article/details/122377789

  4. https://blog.youkuaiyun.com/qq_36566262/article/details/124242610

  5. https://blog.youkuaiyun.com/weixin_51466332/article/details/123345199

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值