学习设计模式之适配器模式,但是宝可梦

前言

作者在准备秋招中,学习设计模式,做点小笔记,用宝可梦为场景举例,有错误欢迎指出。

代码同步更新到 github ,要是点个Star您就是我的神

适配器模式

意图:将一个类的接口转换成客户希望的另一个接口

主要解决:把现有对象放到新环境里,而新环境要求的接口,现有对象不满足

何时使用:现有的类被需要,而这个类的接口不符合要求;建立一个可复用的类,用于让彼此之间无关的类或未来可能引入的类可以一起工作

适配器模式有3个角色:

  • 目标接口 (Target):当前系统业务期待的接口
  • 适配者类:被适配的现有组件的接口
  • 适配器类:一个转换器,继承或引用适配者对象,把适配者接口转为目标接口

1 情景假设

现在我们假设这样一个场景:小智在绿宝石的存档中,有一只巨tm强的裂空座,性格好,个体高,努力值也刷得完美。现在已经发售了朱紫,他还想用这只裂空座,但是问题来了,绿宝石是GBA主机的游戏,朱紫是Switch主机的游戏,他们存储数据的格式不同啊!这怎么办呢?那么就只能推出一个适配器,把GBA的数据转化成Switch的数据,把裂空座从绿宝石移植到朱紫,可能熟悉宝可梦游戏的朋友已经想到了,Pokemon Home就是干这个事的,所以我们可以把这个故事中的角色抽象为适配器模式需要的三个角色:

  • 目标接口:Switch数据
  • 适配者类:GBA数据
  • 适配器类:Pokemon Home

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XgCLolGk-1692546675681)(D:\笔记笔记笔记\设计模式.assets\image-20230820232858251.png)]

2 代码示例

在这里插入图片描述

首先定义个顶层接口:

public interface Game {
}

现有的接口,就是老版本GBA游戏:

public interface GbaGame extends Game{
    void usePokemon(String dataFormat);
}

public class EmeraldVersion implements GbaGame{
    /**
     * 在绿宝石中使用宝可梦
     */
    public void usePokemon(String dataFormat, String version) {
        System.out.println("Go! Rayquaza! (In " + version + " Version)");
    }

    public void usePokemon() {
        System.out.println("Go! Rayquaza! (In GBA Version )");
    }
}

有绿宝石一个实现类,然而,现在游戏已经到了朱紫版本,需要的是Switch上的数据:

public interface NsGame extends Game{
    void usePokemon(String dataFormat);
}

public class ScarletVersion implements NsGame{

    public void usePokemon(String dataFormat){
        if ("nsData".equals(dataFormat)){
            // 内置功能,使用当前版本的宝可梦
            System.out.println("Go! Chikorita (Get In Scarlet Version)");
        }
    }
}

于是为了让NS端适配GBA的数据,我们需要一个适配器,这个适配器要有旧版本的属性(因为适配器是为已有的类设计的)

/**
 * Pokemon Home
 */
public class DataAdapter implements GbaGame{
    GbaGame gbaGame; // 旧世代

    public DataAdapter(GbaGame gbaGame) {
        this.gbaGame = gbaGame;
    }

    @Override
    public void usePokemon(String dataFormat) {
        if("gbaData".equals(dataFormat)){
            gbaGame.usePokemon(dataFormat);
        }
    }
}

所以,要在新版本(NsGame)中使用旧版本的数据,新版本应该是:

public class ScarletVersion implements NsGame{

    DataAdapter dataAdapter;
    public void usePokemon(String dataFormat){
        if ("gbaData".equals(dataFormat)){
            dataAdapter = new DataAdapter(new EmeraldVersion());
            dataAdapter.usePokemon(dataFormat, "Switch");
        }else if ("nsData".equals(dataFormat)){
            // 内置功能,使用当前版本的宝可梦
            System.out.println("Go! Chikorita (Get In Scarlet Version)");
        }
    }
}

注意:这里并不违背“开闭原则”,因为前面的ScarletVersion类只是为了说明逻辑,并不是对代码进行修改

测试类

public class AdapterDemo {
    public static void main(String[] args) {
        // 老版本使用
        EmeraldVersion emeraldVersion = new EmeraldVersion();
        emeraldVersion.usePokemon();

        // 新版本使用
        NsGame pokemon = new ScarletVersion();
        pokemon.usePokemon("gbaData");
        pokemon.usePokemon("nsData");
    }
}
Go! Rayquaza! (In GBA Version )
Go! Rayquaza! (In Switch Version)
Go! Chikorita (Get In Scarlet Version)

3 扩展

有聪明的读者就会问了:那既然有开闭原则,那如果有新的旧版本或者新版本要加入怎么办呢?

  • 旧版本增加:比如我想把3DS上的宝可梦拿到朱紫用,要怎么办?

如果无视开闭原则,可以这样修改,但其实这种情况更适用于,一开始就告诉了开发者,要适配2个旧版本。

public class DataAdapter implements GbaGame, ThreeDS{
    GbaGame gbaGame; // 旧世代
    ThreeDS threeDS; // 3DS世代游戏

    public DataAdapter(GbaGame gbaGame, ThreeDS threeDS) {
        this.gbaGame = gbaGame;
        this.threeDS = threeDS;
    }

    @Override
    public void usePokemon(String dataFormat, String newVersion) {
        if("gbaData".equals(dataFormat)){
            gbaGame.usePokemon(dataFormat, newVersion);
        }else if("threeDS".equals(dataFormat)){
        	threeDS.usePokemon(dataFormat, newVersion);
        }
    }
}

所以正确的做法是:为ThreeDS单独再写一个适配器类

public ThreeDsAdapter implements ThreeDS{
	//...
}
  • 目标接口的新实现:比如剑盾版本也是NS端的,和朱紫写一样的逻辑即可

3 应用

2023/08/26 更新

在学习多线程八股文的时候,新了解了FutureTask类。关于这个类的作用,不在本文中赘述。

FutureTask类有2个构造方法:

class FutureTask{
    private Callable<V> callable;
    
    public FutureTask(Callable<V> callable) {
        if (callable == null)
        throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
        }

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
        }
}
    
    

可见,FutureTask的构造方法参数可以是Callable也可以是Runnable的实现类,然而,在传入Runnable实现类时,
还是对变量callable赋值,这是一个Callable对象。所以,无论传入什么,最终都变成了Callable
点进Executors.callable():

public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

可以看到一个RunnableAdapter, 一个适配器类,传入Runnable,得到的是继承了Callable接口的适配器对象。

4 另一个例子

题外话,在学习这个设计模式的时候想到的,在做机器学习的时候,通常有这么个流程:

数据集 -> 数据源 -> 模型 -> …

数据集的格式各不相同,然而,模型的输入是固定的,我们把数据集转换成能够进入模型的过程通常被叫做数据预处理,这是为了让数据集和模型输入的格式适配。比如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jOAlRVL1-1692546675682)(D:\笔记笔记笔记\设计模式.assets\image-20230820234746268.png)]

每个数据集的分割符不同,那么为了提供给模型一个模型能够接受的格式,就可以使用适配器模式,让数据从csv或者dat格式转换为data_df,即dataframe对象的过程,也就是适配的过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值