关于Java多参数方法的设计
2017年02月11日


先来看问题,

 【问题】有些方法,内容都差不多,只是参数的个数或者类型不一样。此时,到底是写多个方法呢,还是写一个方法涵盖所有功能(根据传入的标识去执行不同的步骤)?

举例1:

changeCode(String orgStr, String charSet);

changeCode(String orgStr); // 默认charSet为null

其实方法2,就是调用的方法1,但之所以再写一个方法2,是为了方便使用,charSet参数不是必须的,可以省略(如果省略,会用策略)。


举例2:

下面的6个方法,每个方法都有区别,要么参数个数不同,要么参数类型不同。

比如方法3和方法4,允许传入String或者byte[]类型的参数,而他们最终会调用方法1,但是方法1又最终调用的是方法2。

之所以有这种情况,是为了方便 使用方,使用方 允许传入不同类型的参数,方法内部自动进行参数转换。

Result shaWithRsa(CodeData<?> data, CodeData<?> key) {
    return shaWithRsa(data, key, "SHA256WithRSA");
}

Result shaWithRsa(CodeData<?> data, CodeData<?> key, String algorithm) {
    // 省略
}

Result shaWithRsa(String data, CodeData<?> key) {
    return shaWithRsa(new Utf8Data(data), key);
}
Result shaWithRsa(byte[] data, CodeData<?> key) {
    return shaWithRsa(new PlainData(data), key);
}
Result shaWithRsa(String data, byte[] key) {
    return shaWithRsa(new Utf8Data(data), new PlainData(key));
}
Result shaWithRsa(byte[] data, String key) {
    return shaWithRsa(new PlainData(data), new Utf8Data(key));
}


想做一个优秀的API (接口或者工具方法)的设计,必须要严谨,因为一旦公布大家使用,后期很难再去改API了。


所以,本文专门来讨论这个问题,多参数的接口,该如何设计


两种不同的观念

A、写多个方法,每个方法参数不同

优点:

    每个方法参数清晰,见名知义,使用方便,调用也简单。

    每个方法内部的处理逻辑清晰,很符合面向对象的多态思维。

缺点:

    多个方法,有很多代码是冗余的,而且修改起来很麻烦。

    方法太多,虽然各有各的用处,但是容易让调用者眼花缭乱。


B、写一个方法,涵盖所有参数

优点:

    只需要编写和维护一套代码;

    接口个数少,不会给用户造成选择困扰。

缺点:

    调用时需要明确指定所有参数,即使有些参数不需要,也要传null代替。

    使用时不够清晰,对于需要传哪些参数、需要传什么类型的参数,都有疑惑。

    方法内部要处理多种情况,逻辑要稍微复杂,性能稍微低一点(可以忽略)。

    

  按照这个方案,前面的案例1,只保留下面一个方法:

changeCode(String orgStr, String charSet);

  案例2,只保留:

Result shaWithRsa(CodeData<?> data, CodeData<?> key, String algorithm);

  很明显,方法个数变少了,但是使用难度也增加了。


如上所述,其实二者各有优劣,我都纠结了好几年但是最近,我找到了一个完美的结合点,方法如下:


1、对于多参数的情况,且(参数个数有3种以上不同组合的情况),我建议使用上面的方案B,因为:

对于调用者而言,只有一个方法供选择,那么他必须显式的指定所有参数,但对调用者而言,使用是透明的。哪些需要,哪些不需要,调用者可以自行决定,不需要的,传入null即可。

对于写方法的人而言,注意到,他应该检查参数的合法性,不管使用者传入了什么参数,方法内部都应该做检查,某些参数本身就支持传入null。


2、对于有多类型参数的情况,且参数类型有3种以上不同组合的情况,我建议使用上面的方案A,同时将参数定义为一个Bean对象。

如下所示:

Result shaWithRsa(RsaEncryptReq req);

public class RsaEncryptReq {

    private CodeData<?> data;
    private CodeData<?> key;
    private String algorithm;
    
    public RsaEncryptReq(byte[] data, byte[] key, String algorithm) {
        this.data = new PlainData(data);
        this.key = new PlainData(key);
        this.algorithm = algorithm;
    }
    
    public RsaEncryptReq(String data, CodeData<?> key, String algorithm) {
        this.data = Utf8.data(data);
        this.key = key;
        this.algorithm = algorithm;
    }
    
        public RsaEncryptReq(byte[] data, String key, String algorithm) {
        this.data = new PlainData(data);
        this.key = Utf8.data(key);
        this.algorithm = algorithm;
    }
    
    public RsaEncryptReq(String data, byte[] key, String algorithm) {
        this.data = Utf8.data(data);
        this.key = new PlainData(key);
        this.algorithm = algorithm;
    }

    public CodeData<?> getData() {
        return data;
    }

    public CodeData<?> getkey() {
        return key;
    }
    
    public String getAlgorithm() {
        return algorithm;
    }
}

这个方案的兼具方法A和B的优点,

首先,只保留了一个方法,使得API的定义非常清晰,而且API兼容性好,定义好之后就无需更改。

其次,通过Bean的构造函数来传入不同类型的参数,非常方便扩展,也很方便使用。



3、对于参数比较少的情况(参数的组合小于等于3),并且某些参数又比较常用,则可以采用A方案。