首页 > 编程语言 >分享 Java 开发中常用到的设计模式(一)

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

时间:2023-08-09 10:44:59浏览次数:45  
标签:设计模式 return 角色 观察者 接口 class Java 分享 public

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

标签:设计模式,return,角色,观察者,接口,class,Java,分享,public
From: https://www.cnblogs.com/Apluemxa/p/17594211.html

相关文章

  • JavaSE--super关键字
    一、super关键字1、super是一个关键字,全部小写2、this与super对比  this:    this可以出现在实例方法中和构造方法中    语法:this.或者this()    this不能使用在静态方法中    this.大部分可以省略,在区分局部变量和实例变量的时候不能省略,    this......
  • JavaSE--方法覆盖和多态
    一、方法覆盖Override1、什么时候使用Override  例如:以下代码:鸟儿在执行move方法时,最好输出“鸟儿在飞翔”,但是当前程序在执行move方法时,输出“动物在移动”,显然Bird子类继承来的方法无法满足需求  子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权......
  • JavaSE--封装
    一、封装概念及作用1、封装  现实生活中很多都是封装的,例如手机、笔记本电脑......外部有一个壳子保护内部的部件,保证内部的部件是安全的  封装之后对于我们使用者来说不需要了解内部的结构和关心内部的复杂,只需要使用就可以2、封装的作用保证内部结构的安全屏蔽复杂,暴......
  • TSINGSEE青犀视频监控汇聚平台EasyCVR视频分享页面WebRTC流地址播放不了是什么原因?
    开源EasyDarwin视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放,可同时播放多路视频流,也能支持视频定时轮播。视频监控汇聚平台EasyC......
  • JavaSE--继承
    一、继承extends1、继承的作用  基本作用:子类继承父类,代码可以得到复用  主要作用:因为有了继承关系,才有了后期的方法覆盖和多态机制2、继承的语法结构class类名extends父类名{类体;}//使用继承机制来解决代码复用问题。//继承也是存在缺点的:耦合度高,父类修......
  • JAVASE学习笔记
    JavaSE基础知识1、Java应用与特性jdk8常用jdk11常用jdk17推荐使用java之父高斯林1、常用的java程序分为JavaSE、JavaEE、JavaME三个版本2、J2SE:定位在服务端的开发(WEB网页)3、JavaME:定位在消费行电子产品的应用上。特性和优势:1、面向对象2、可移植性3、高性能4、......
  • JavaSE--控制语句
    一、控制语句1、控制语句分类顺序结构  按照顺序来执行程序分支结构/选择语句  单分支if、多分支switch循环结构  for、while、dowhile2、分支结构单分支if语法格式:第一种写法:if(布尔表达式){java语句;}......
  • JavaSE--方法
    一、方法1、方法的定义/*[修饰符列表]返回值类型方法名(形式参数列表){方法体;//return;终止当前方法}方法写完之后需要调用去使用,不调用没法用1、修饰符列表:不是必选的2、返回值类型:可以是任何数据类型(基本数据类型和引用数据类......
  • Java 中的 7 种重试机制,还有谁不会?!
    随着互联网的发展项目中的业务功能越来越复杂,有一些基础服务我们不可避免的会去调用一些第三方的接口或者公司内其他项目中提供的服务,但是远程服务的健壮性和网络稳定性都是不可控因素。在测试阶段可能没有什么异常情况,但上线后可能会出现调用的接口因为内部错误或者网络波动而出......
  • JavaSE--运算符
    一、运算符运算符:用于指明对于操作数的运算方式1、运算符分类  1)按照操作数的数目分  单目操作符、双目操作符、三目操作符  2)按照运算符的功能来分  算术运算符、赋值运算符、关系运算符、逻辑运算符2、运算符详解  1)算数运算符+加法-减法*乘法/除法......