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

分享 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接口

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

    public abstract class AbstractScoreGenerator {
        /**
         * 获取分数的抽象方法
         * @return 分数
         */
        public abstract int getScore();
    
        /**
         * 生成分数的抽象方法
         */
        public abstract void execute();
    
        /**
         * 初始化观察者Observer集合
         */
        private final ArrayList<Observer> observerList = new ArrayList<>();
    
        /**
         * 添加/注册Observer观察者
         * @param observer
         */
        public void addObserver(Observer observer){
            observerList.add(observer);
        }
    
        /**
         * 向Observer观察者发送通知
         */
        public void notifyObservers(){
            for (Observer observer : observerList) {
                observer.update(this);
            }
        }
        
    }
    
  • ScoreGenerator功能实现类

    public class ScoreGenerator extends AbstractScoreGenerator {
    
        /**
         * 随机数对象
         */
        private final Random random = new Random();
    
        private int score;
    
        /**
         * 子类必须重写父类的抽象方法
         * @return
         */
        @Override
        public int getScore() {
            return score;
        }
    
        /**
         * 子类必须重写父类的抽象方法
         * @return
         */
        @Override
        public void execute() {
            for (int i = 0; i < 10; i++) {
                score = random.nextInt(20);
                this.notifyObservers();
            }
        }
    
    }
    
  • DigitalObserver感知类

    public class DigitalObserver implements Observer {
        /**
         * 具体的观察者,感知被观察者的变化,用数值的形式实现
         * @param abstractScoreGenerator
         */
        @Override
        public void update(AbstractScoreGenerator abstractScoreGenerator) {
            System.out.println("数值观察者:" + abstractScoreGenerator.getScore());
            try {
                Thread.sleep(100);
            }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  • GraphObserver感知类

    public class GraphObserver implements Observer {
        /**
         * 具体的观察者,感知被观察者的变化,用图示的形式实现
         * @param abstractScoreGenerator
         */
        @Override
        public void update(AbstractScoreGenerator abstractScoreGenerator) {
            System.out.print("图示观察者:");
            int count = abstractScoreGenerator.getScore();
            for (int i = 0; i < count; i++) {
                System.out.print("*");
            }
            System.out.println("");
            try {
                Thread.sleep(100);
            }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  • Main入口

    public class Main {
        public static void main(String[] args) {
            // 抽象类不可以实例化,子类可以实例化,且由于继承关系,子类可以调用父类的普通方法
            ScoreGenerator scoreGenerator = new ScoreGenerator();
            // 接口不能实例化,但是可以通过实例化实现接口的类,从而”构造“该接口,并实现接口的抽象方法
            DigitalObserver digitalObserver = new DigitalObserver();
            GraphObserver graphObserver = new GraphObserver();
            // 注册两个观察者(数值和图示),实际上就是成生成两个”接口对象“,目的是调用接口的具体实现
            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接口

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

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

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

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

    public class OperationMultiplication implements Strategy {
    
        /**
         * 表示乘法的具体策略,两数相乘
         * @param num1
         * @param num2
         * @return
         */
        @Override
        public int calculate(int num1, int num2) {
            return num1 * num2;
        }
    
    }
    
  • Main入口

    public class Main {
        public static void main(String[] args) {
    
            // 初始化分值
            int num1 = 6;
            int num2 = 8;
    
            // 实例化上下文对象,通过实例化实现接口的某个类去生成”接口对象“,目的是调用对应实现类的抽象方法实现
            CalculatorContext calculatorContext1 = new CalculatorContext(new OperationAddition());
            System.out.println("加法策略:num1+num2= " + calculatorContext1.executeStrategy(num1, num2));
            // 通过调用每个不同实现类的不同抽象方法实现,可以解决if-else的逻辑判断
            CalculatorContext calculatorContext2 = new CalculatorContext(new OperationDivision());
            System.out.println("减法策略:num1-num2= " + calculatorContext2.executeStrategy(num1, num2));
    		// 策略模式最核心的:客户端可以通过实例化不同的”接口对象“去调用不同的具体实现
            CalculatorContext calculatorContext3 = new CalculatorContext(new OperationMultiplication());
            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
public class Computer {
    /**
     * 中央处理器
     */
    private String cpu;

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

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

    // 创建产品对象
    protected Computer computer = new Computer();

    // 创建产品对象的各组成部件,即设置对象的属性
    public abstract void setCpu();
    public abstract void setMemory();
    public abstract void setDisk();

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

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

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

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

    private final ComputerBuilder computerBuilder;

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

    /**
     * 调用抽象方法的具体实现
     * @return
     */
    public Computer computerConstruct() {
        // 调用 builder 的属性设置方法
        computerBuilder.setCpu();
        computerBuilder.setMemory();
        computerBuilder.setDisk();
        // 返回组装好的电脑
        return computerBuilder.getComputer();
    }
}
  • Main主类
public class Main {
    public static void main(String[] args) {
        ConcreteComputerBuilder builder = new ConcreteComputerBuilder();
        Director director = new Director(builder);
        Computer computer = 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
public class Computer {
    /**
     * 中央处理器
     */
    private String cpu;

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

    /**
     * 硬盘
     */
    private String disk;
}
  • 链式调用建造
public class Main {
    public static void main(String[] args) {
       Computer computer = 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")
public class BlogController {

    @Resource
    private BlogFacade blogFacade;

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

    @ApiOperation(value = "编辑博客", httpMethod = "POST", response = Boolean.class)
    @PostMapping("/updateBlog")
    public ResponseData updateBlog(@RequestBody UpdateBlogDto dto){
        Boolean boolean = blogFacade.updateBlog(dto);
        return ResponseData.success(boolean);
    }
}
  • BlogFacade
@Service
public class BlogFacade {
    /**
     * 注入interface
     */
    @Resource
    private BlogService blogService;
    /**
     * 注入interface
     */
    @Resource
    private BlogLikeService blogLikeService;
    
    /**
     * 具体实现
     */
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public Boolean createBlog(CreateBlogDto dto){
        Blog blog = new Blog();
        BeanUtils.copyProperties(dto, blog);
        blog.setCreateTime(new Date());
        Integer blogId = blogService.saveBlog(blog);
    }
    
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public Boolean updateBlog(UpdateBlogDto dto){
        // 具体实现
        // 略
        }
        
}

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

  • BlogService
public interface BlogService extends IService<Blog> {

    /**
     * 新建博客
     * @param blog
     * @return 是否成功
     */
    Integer saveBlog(Blog blog);
}
  • BlogServiceImpl
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements BlogService {

    @Resource
    private BlogMapper blogMapper;

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

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

  • BlogMapper
@Mapper
public interface BlogMapper extends BaseMapper<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接口
public interface BritishStandard {
    /**
     * 目标角色,用户只适配这个接口
     * @return
     */
    String getBritishStandard();

}
  • ChineseStandard类
public class ChineseStandard {

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

}
  • StandardAdapter适配器类
public class StandardAdapter extends ChineseStandard implements BritishStandard {

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

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

    public static void main(String[] args) {
        // 通过实例化实现接口的类来传递"接口对象"
        Boolean result = new Notebook().charge(new StandardAdapter());
        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接口
public interface MusicPlayer {

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

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

    @Resource
    private CdPlayer cdPlayer;

    /**
     * 使用有参构造的方式,传递对象
     * @param fileName
     * @return
     */
    @Override
    public String playMusic(String fileName) {
       return cdPlayer.playCD(fileName);
    }
}
  • 入口(播放音乐)
public class Play {
    public static void main(String[] args) {
        PlayerAdapter playerAdapter = new PlayerAdapter(new CdPlayer());
        String result = 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.csdn.net/u014454538/article/details/122377789
  4. https://blog.csdn.net/qq_36566262/article/details/124242610
  5. https://blog.csdn.net/weixin_51466332/article/details/123345199

热门相关:倾心之恋:总裁的妻子   不科学御兽   大妆   后福   大妆