一个函数重载引发的接口设计思考

最近在做架构设计时,在一个非常不起眼的功能包装中遇到一个函数重载的接口设计。当我实现完所有接口后,不禁重新审视起我的接口设计是否合理且不存在二义性。

接口是这样的,在一个 send 接口中,最多会包含一下4个参数,分别为 index, id, x, y。起初我的接口设计如下:

void send(int index);
void send(int id, int index);
void send(int index, int x, int y);
void send(int id, int index, int x, int y);

由于接口恰巧不需要 (int id, int x, int y) 这种情况,看似代码没有任何问题。但仔细想想,你会发现在使用过程中,这4个接口设计非常危险,很容易带来二义性。

首先我们要确定一点是,每个接口都需要必要参数 index。而四4个接口中2个设计为第一参数,2个设计为第二参数。这是一个低级且愚蠢的错误。当你使用该接口的时候,应该第一时间想到要传递 index,而不是 id

所以我将接口修改为如下:

void send(int index);
void send(int index, int id);
void send(int index, int x, int y);
void send(int index, int id, int x, int y);

这样在认知和记忆上不会发生错误,因为参数类型相同,编译器仅能靠参数数量来推导到底使用哪个接口调用,数据类型无法作有效的类型检查来排除潜在的数据错误。

解决了上面这个愚蠢的问题后,下面的问题让我陷入思考。如果某一个类实现,将其中一个参数设置了默认值,这是否会让使用者感到非常困惑。

void send(int index, int x, int y = 0);

send(88, 5);

对于上面的代码,当我们阅读时 send(88, 5) 时,它到底是指向 void send(int index, int x, int y = 0) 还是 void send(int index, int id) ? 无论编译器为我们如何做 “正确” 的选择,从文法上该设计都存在二义性。

我很想将不同的参数类型进行重命名,可C#不允许这样做(虽然C/C++可以,但我仍然不喜欢这种做法)。

那是否将4个接口命名进行修改?可以,但该设计的前提是 “函数重载”。当上层模块访问该接口时,它可以挑选4个接口中任意一个来使用,但其行为定义都是一致的。虽然我可以将其中一个接口命名改为 sendWithPoint(int index, int x, int y) ,但这违背了设计初衷。那么唯一的方法就是改变参数数据类型来达到防止二义性的目的。可以修改为一下方式:

void send(int index);
void send(int index, int id);
void send(int index, int id, Vector2Int point);
void send(int index, Vector2Int point);

看上去解决了问题,但此处又引入了 Vector2Int 类型。事实上我们引入了很多不必要的内容,为了继续优化,我将Vector2Int 类型改为了一个简单的struct。

public struct Point
{
	public int Width;
	public int Height;
}

接口则变成了下面这样:

void send(int index);
void send(int index, int id);
void send(int index, int id, Point point);
void send(int index, Point point);

通过上面的参数调整顺序,修改数据类型,达到了统一接口使用行为,消除二义性的目的。