A闪的 BLOG 技术与人文
最近在做架构设计时,在一个非常不起眼的功能包装中遇到一个函数重载的接口设计。当我实现完所有接口后,不禁重新审视起我的接口设计是否合理且不存在二义性。
接口是这样的,在一个 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);
通过上面的参数调整顺序,修改数据类型,达到了统一接口使用行为,消除二义性的目的。