设计模式与导入导出:策略模式选择格式、适配器转换数据、模板方法定义流程

导入导出功能,说白了就是「把数据从系统里倒出来,或者从外面塞进去」。听起来简单?我做过不下二十个导入导出模块,每次都能踩到不同的坑。格式五花八门、数据结构千奇百怪、流程还动不动就变。今天咱们就用三个设计模式,把这套东西彻底理清楚。

一、核心问题:导入导出到底难在哪?

先说说我个人的血泪史。有一次做报表系统,客户说「支持Excel就行」。我吭哧吭哧写完了,第二天客户说「再加个CSV吧」。好,我加。第三天「PDF也要」。第四天「XML行不行?」。第五天……我直接把代码重构了。

你想想看,导入导出模块的痛点其实就三个:

  • 格式多变:Excel、CSV、PDF、JSON、XML……今天用这个,明天换那个
  • 数据异构:外部系统的数据结构和咱们内部的不一样,得做转换
  • 流程固定但步骤可变:读取→校验→转换→写入,大流程不变,但每个步骤的实现千差万别

这三个问题,正好对应三个设计模式:策略模式管格式选择,适配器模式管数据转换,模板方法模式管流程定义。咱们一个一个说。

二、策略模式:让格式选择变得灵活

策略模式,说白了就是把「怎么做」封装起来,让调用方决定「用哪个」。我习惯把每种导出格式做成一个策略类,这样加新格式就像加插件一样简单。

核心思想:定义一组算法,把它们封装起来,让它们可以互相替换。

先看一个导出策略的接口:

// 导出策略接口
public interface ExportStrategy {
    void export(Data data, OutputStream out);
}

// 具体策略:Excel导出
public class ExcelExportStrategy implements ExportStrategy {
    @Override
    public void export(Data data, OutputStream out) {
        // 使用Apache POI写入Excel
        System.out.println("导出为Excel格式");
    }
}

// 具体策略:CSV导出
public class CsvExportStrategy implements ExportStrategy {
    @Override
    public void export(Data data, OutputStream out) {
        // 写入逗号分隔的文本
        System.out.println("导出为CSV格式");
    }
}

// 上下文类
public class ExportContext {
    private ExportStrategy strategy;
    
    public void setStrategy(ExportStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void executeExport(Data data, OutputStream out) {
        strategy.export(data, out);
    }
}

嗯,这里要注意:策略模式不是让你写一堆if-else。我见过有人把策略类塞进Map里,用枚举做key,那其实还是if-else的变种。真正的策略模式,应该让调用方自己决定用哪个策略,而不是在代码里硬编码。

我的经验:在Spring项目里,我会把所有策略类注入到一个Map中,key是格式名称。这样前端传一个「format=excel」,后端直接map.get(format)就能拿到对应的策略。加新格式只需要新增一个实现类,不用改任何现有代码。

三、适配器模式:搞定数据格式不一致

导入导出最头疼的是什么?数据对不上。外部系统给的数据字段叫「user_name」,咱们系统里叫「username」;人家用「yyyy/MM/dd」,咱们用「yyyy-MM-dd」。这时候适配器模式就派上用场了。

适配器模式,说白了就是「转接头」。把外部数据转成内部能用的格式,或者把内部数据转成外部需要的格式。

// 目标接口:内部系统期望的数据格式
public interface InternalData {
    String getUsername();
    Date getBirthday();
}

// 适配者:外部系统的数据格式
public class ExternalData {
    public String getUserName() { return "张三"; }
    public String getBirthDate() { return "1990/01/01"; }
}

// 适配器:把外部数据转成内部格式
public class DataAdapter implements InternalData {
    private ExternalData externalData;
    
    public DataAdapter(ExternalData externalData) {
        this.externalData = externalData;
    }
    
    @Override
    public String getUsername() {
        return externalData.getUserName();
    }
    
    @Override
    public Date getBirthday() {
        // 把 "1990/01/01" 转成 Date
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        return sdf.parse(externalData.getBirthDate());
    }
}

我曾经踩过的坑:适配器里做太多逻辑。有一次我在适配器里做了数据校验、格式转换、甚至业务计算,结果适配器变得又臭又长。后来我明白了,适配器只做「格式转换」,不做「业务处理」。校验和计算应该放到后面的步骤里。

四、模板方法模式:固定流程,灵活步骤

导入导出的流程其实很固定:读取→校验→转换→写入。但每个步骤的实现方式可以完全不同。模板方法模式就是干这个的——把流程骨架定死,把具体实现留给子类。

public abstract class DataImportTemplate {
    
    // 模板方法:定义导入流程
    public final void importData(String filePath) {
        Object rawData = readData(filePath);
        boolean valid = validate(rawData);
        if (!valid) {
            throw new RuntimeException("数据校验失败");
        }
        InternalData converted = convert(rawData);
        save(converted);
    }
    
    // 子类必须实现的步骤
    protected abstract Object readData(String filePath);
    protected abstract boolean validate(Object data);
    protected abstract InternalData convert(Object data);
    
    // 可选覆盖的步骤
    protected void save(InternalData data) {
        // 默认实现:保存到数据库
        System.out.println("保存数据到数据库");
    }
}

// 具体实现:Excel导入
public class ExcelImport extends DataImportTemplate {
    @Override
    protected Object readData(String filePath) {
        System.out.println("读取Excel文件");
        return new Object();
    }
    
    @Override
    protected boolean validate(Object data) {
        System.out.println("校验Excel数据");
        return true;
    }
    
    @Override
    protected InternalData convert(Object data) {
        System.out.println("转换Excel数据");
        return new InternalData();
    }
}

你想想看,模板方法模式最大的好处是什么?是「强制统一流程」。团队里每个人写导入模块,都得按照这个流程来,不会出现「张三先校验再读取,李四先转换再校验」这种混乱情况。

五、三者结合:一个完整的导入导出框架

现在我们把三个模式串起来。我画了一张图,你看看它们是怎么配合的:

设计模式在导入导出中的协作关系 策略模式 格式选择 ExcelExportStrategy CsvExportStrategy PdfExportStrategy JsonExportStrategy 可动态切换,扩展方便 适配器模式 数据转换 ExternalData DataAdapter InternalData 解决数据格式不一致 模板方法模式 流程定义 readData() validate() convert() save() 流程固定,步骤可定制 选择格式 传入数据

这张图展示了三个模式的协作关系:策略模式负责「选格式」,适配器模式负责「转数据」,模板方法模式负责「定流程」。它们各司其职,互不干扰。

六、实战中的避坑指南

最后分享几个我踩过的坑,希望能帮你少走弯路:

坑一:策略类太多导致内存爆炸

我曾经把每种格式的每个版本都做成一个策略类,结果策略类上百个。后来改用「策略工厂+缓存」的方式,只实例化当前需要的策略,问题就解决了。

坑二:适配器里做数据校验

适配器只做格式转换,不做业务校验。校验应该放在模板方法的validate步骤里。否则适配器会变得臃肿,而且违反单一职责原则。

坑三:模板方法里放太多钩子方法

模板方法模式允许子类覆盖某些步骤,但别放太多「可选覆盖」的钩子。我见过一个模板类有十几个钩子方法,子类根本不知道哪些该覆盖。保持简单,3-5个核心步骤就够了。

好了,关于导入导出中的设计模式,我就说这么多。这三个模式配合起来,基本能应对90%的导入导出场景。下次你遇到类似需求,不妨试试这个组合拳。

一句话总结:策略模式管「选什么格式」,适配器模式管「怎么转数据」,模板方法模式管「按什么流程走」。各管各的,互不打架。


公众号:蓝海资料掘金营,微信deep3321