当接口要加入新方法时,我后悔没有早点学设计模式了

🍄 大家好,我是风筝

🌍 个人博客:【古时的风筝】。

本文目的为个人学习记录及知识分享。如果有什么不正确、不严谨的地方请及时指正,不胜感激。

每一个赞都是我前进的动力。

公众号:「古时的风筝」

假设系统中有一个接口,这个接口已经被10个实现类实现了,突然有一天,新的需求来了,其中5个实现类需要实现同一个方法。然后你就在接口中添加了这个方法的定义,想着一切都很完美。

当你在接口和其中5个实现类中加完这个方法后,一编译。不妙啊,另外那 5 个实现类报错了,没实现新加的这个方法。要知道,接口中的方法定义必须要在实现类中实现才行,缺一个都编译不过。

这时候你耳边突然响起了开发之初的老前辈跟你说的话:"这几个实现以后可能差距越来越大,接口中可能会加方法,注意留口子"。

现在咋整

假设之前的接口是这样的,只有吃饭和喝水两个方法。

java 复制代码
public interface IUser {

    /**
     * 吃饭啊
     */
    void eat();

    /**
     * 喝水啊
     */
    void drink();
}

现在有 5 个实现类厉害了,要加一个 play() 方法。

既然情况已经这样了,现在应该怎么处理。

破罐子破摔吧,走你

不管什么接口不接口的了,哪个实现类要加,就直接在那个实现类里加吧,接口还保持之前的样子不动,仍然只有吃饭和喝水两个方法,play 方法就直接加到 5 个实现类中。

java 复制代码
public class UserOne implements IUser{

    @Override
    public void eat() {
        System.out.println("吃饭");
    }

    @Override
    public void drink() {
        System.out.println("喝水");
    }
    
    public void play() {
        System.out.println("玩儿");
    }
}

虽然可以实现,但是完全背离了当初设计接口的初衷,本来是照着五星级酒店盖的,结果盖了一层之后,上面的变茅草屋了。

从此以后,接口是接口,实现类是实现类,基本上也就没什么关系了。灵活性倒是出来了,以后想在哪个实现类加方法就直接加了。

再加一个接口行不

还是有点儿追求吧,我新加一个接口行不行。之前的接口不动,新建一个接口,这个接口除了包含之前的两个方法外,再把 play 方法加进去。

这样一来,把需要实现 play 方法的单独在弄一个接口出来。就像下面这样 IUser是之前的接口。IUserExtend接口是新加的,加入了 play() 方法,需要实现 play() 方法的实现类改成实现新的IUserExtend接口,只改几个实现关系,改动不是很大嘛,心满意足了。

但是好景不长啊,过了几天,又要加新方法了,假设是上图的 UserOneUserNine要增加方法,怎么办呢?

假如上天再给我一次机会

假如上天再给我一次重来的机会,我会对自己说:"别瞎搞,看看设计模式吧"。

适配器模式

适配器模式可以通过创建一个适配器类,该适配器类实现接口并提供默认实现,然后已有的实现类可以继承适配器类而不是直接实现接口。这样,已有的实现类不需要修改,而只需要在需要覆盖新方法的实现类中实现新方法。

不是要加个 play() 方法吗,没问题,直接在接口里加上。

java 复制代码
public interface IUser {
    void eat();
    void drink();
    void play();
} 

适配器类很重要,它是一个中间适配层,是一个抽象类。之前不是实现类直接 implements 接口类吗,而现在适配器类 implements 接口类,而实现类 extends 适配器类。

在适配器类可以给每个方法一个默认实现,当然也可以什么都不干。

java 复制代码
public abstract class UserAdapter implements IUser {
    @Override
    public void eat() {
        // 默认实现
    }

    @Override
    public void drink() {
        // 默认实现
    }

    @Override
    public void play() {
        // 默认实现
    }
}
java 复制代码
public class UserNine extends UserAdapter {
    @Override
    public void eat() {
        System.out.println("吃饭");
    }

    @Override
    public void drink() {
        System.out.println("喝水");
    }

    @Override
    public void play() {
        System.out.println("玩儿");
    }
}

public class UserTen extends UserAdapter {
    @Override
    public void eat() {
        System.out.println("吃饭");
    }

    @Override
    public void drink() {
        System.out.println("喝水");
    }
}

调用方式:

java 复制代码
IUser userNine = new UserNine();
userNine.eat();
userNine.drink();
userNine.play();

IUser userTen = new UserTen();
userTen.eat();
userTen.drink();

这样一来,接口中随意加方法,然后在在适配器类中添加对应方法的默认实现,最后在需要实现新方法的实现类中加入对应的个性化实现就好了。

策略模式

策略模式允许根据不同的策略来执行不同的行为。在这种情况下,可以将新方法定义为策略接口,然后为每个需要实现新方法的实现类提供不同的策略。

把接口改成抽象类,这里面 eat() 和 drink() 方法不变,可以什么都不做,实现类里想怎么自定义都可以。

而 play() 这个方法是后来加入的,所以我们重点关注 play() 方法,策略模式里的策略就用在 play() 方法上。

java 复制代码
public abstract class AbstractUser {

    IPlayStrategy playStrategy;
  
  	public void setPlayStrategy(IPlayStrategy playStrategy){
        this.playStrategy = playStrategy;
    }
  
   	public void play(){
        playStrategy.play();
    }

    public void eat() {
        // 默认实现
    }

    public void drink() {
        // 默认实现
    } 
}

IPlayStrategy是策略接口,策略模式是针对行为的模式,玩儿是一种行为,当然了,你可以把之后要添加的方法都当做行为来处理。

我们定一个「玩儿」这个行为的策略接口,之后不管你玩儿什么,怎么玩儿,都可以实现这个 IPlayStrategy接口。

java 复制代码
public interface IPlayStrategy {

    void play();
}

然后现在做两个实现类,实现两种玩儿法。

第一个玩儿游戏的实现

java 复制代码
public class PlayGameStrategy implements IPlayStrategy{

    @Override
    public void play() {
        System.out.println("玩游戏");
    }
}

第二个玩儿足球的实现

java 复制代码
public class PlayFootballStrategy implements IPlayStrategy{
    @Override
    public void play() {
        System.out.println("玩儿足球");
    }
}

然后定义 AbstractUser的子类

java 复制代码
public class UserOne extends AbstractUser{
    @Override
    public void eat() {
        //自定义实现
    }

    @Override
    public void drink() {
        //自定义实现
    }
}

调用方式:

java 复制代码
public static void main(String[] args) {
  AbstractUser userOne = new UserOne();
  // 玩儿游戏
  userOne.setPlayStrategy(new PlayGameStrategy());
  userOne.play();
  // 玩儿足球
  userOne.setPlayStrategy(new PlayFootballStrategy());
  userOne.play();
}

整体的类关系图大概是这个样子:

最后

通过适配器模式和策略模式,我们即可以保证具体的实现类实现共同的接口或继承共同的基类,同时,又能在新增功能(方法)的时候,尽可能的保证设计的清晰。不像之前那种破罐子破摔的方式,接口和实现类几乎脱离了关系,每个实现类,各玩儿各的。

推荐阅读

我的第一个 Chrome 插件上线了,欢迎试用!

用了8年的方式-用 Docker 瞬间搭建本地开发环境

RPC框架的核心到底是什么

相关推荐
小江的记录本4 分钟前
【JVM虚拟机】JVM调优:常用JVM参数、调优核心指标、OOM排查、GC日志分析、Arthas工具使用(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
程序员cxuan16 分钟前
我花了两天时间,终于把 Codex 额度掉太快的问题整明白了!!
人工智能·后端·程序员
IT_陈寒17 分钟前
Vue这个动态响应坑把我整不会了
前端·人工智能·后端
金銀銅鐵17 分钟前
[Java] 用图形化界面演示 iadd, isub, iconst_<i> 指令的效果
java·后端·python
AskHarries29 分钟前
做国内还是出海
后端
J2虾虾35 分钟前
Spring AI Alibaba文档
java·人工智能·spring
YikNjy41 分钟前
break和continue
java·开发语言·算法
SomeOtherTime42 分钟前
Geojson相关(AI回答)
java·前端·python
日月云棠1 小时前
10 Integer —— 最常用的整数包装类深度解析
java·后端
大鸡腿同学1 小时前
大模型为何总 “胡说八道”?做完 RAG 知识库,我看懂了它的底层逻辑
后端