C++ 隐性介面与显性介面
介面(Interface),我称之为一种 行为合约,代表是若是你的物件持有这种合约,那么我对此物件,操作了合约上的任何动作,都不会有问题。是一个让 caller 与 callee 做为沟通的工具。一般所说的介面,都是指物件导向(OO)上的显性介面,这篇文章就是要来简单描述一下,另一种隐性介面是什么。
显性介面
以往的显性介面使用上,你的每个遵守合约物件,一定要 继承/实作 了某个 Interface Class, Compiler 对于合约检查,早在物件的型别是否符合就决定了。
隐性介面
C++ 的 template ,使得隐性介面成为可能,它能够使得 行为合约的粒度,从型别 降成更低的 能够完成函式唿叫。使用了隐性介面,操作的物件不需要是某某 Interface Class 的子类别,不需要是某某特定型别,它的合约条件是只要 有用到的函式唿叫,都要能够支援就好,就算你的函式签名不一样也可以,它只要求其 行为 是否能够执行。Compiler 对于此种合约检查,则是延迟到函式唿叫的那一行,才去做判断。以下我们就看 Code,来表示隐性介面的不同之处:
下面有定义了两个 Class ,分别是 Cat 与 Dog,他们都 继承/遵守 了一个CryOutAble 型别/合约。还有另一个 Class 叫 Alien,完全独立于 Cat 与 Dog ,相同之处单单只有都能够唿叫一个 cryOut(); 并且回传某个东西。
遵守显性介面的一族 Classesclass CryOutAble {public:virtual int cryOut() = 0;};class Cat: public CryOutAble {public:virtual int cryOut() {cout << "MEOW! MEOW!" << endl;return 100;}};class Dog: public CryOutAble{public:virtual int cryOut() {cout << "GULF! GULF!" << endl;return 200;}};
个 Class,它遵守一个[能够唿叫 cryOut() 并且回传某个东西]的隐性介面
class Alien {public:
const char* cryOut() {cout << "ALIEN NEEDS LOVE!" << endl;
return "REALLY!";}
};
操作显式介面
在使用显性介面的操作上,就算你只是想唿叫 cryOut() 仅此而己,但是只要你传入的物件,不是继承此 Interface Class ,就会无法通过编译。
void ExplicitlyInterface(const CryOutAble& speaker)
{
speaker.cryOut();
}
Cat cat;
Dog dog;
Alien alien;
ExplicitlyInterface(cat); // OK! cat 是 CryOutAble 的子类别,印出 MEOW! MEOW!
ExplicitlyInterface(dog); // OK! dog 是 CryOutAble 的子类别,印出 GULF! GULF!
ExplicitlyInterface(alien); // Compile Error! alien 没有继承 CryOutAble。
操作隐式介面
我们现在来看使用隐性介面的操作,它对于物件的要求,只要能够支援 唿叫cryOut() 并且能够回传某个东西 就好,只有无法支援此项要求的物件,才会无法通过编译。
一个遵守 **能够唿叫 cryOut() 并且回传某值** 的隐性介面
template
void ImplicitlyInterface(const T& speaker)
{
// auto 会去由 compiler 推导回传值的型别是什么,所以此行的要求是只要能够回传某值就好。
auto i = speaker.cryOut();
cout << typeid(i).name() << endl;
}
2 个无法满足隐性介面的 Class
// 拥有 cryOut() 但是无法回传某值
class Bear {public:
void cryOut() {cout << "a hee-ahee ha-hee" << endl; }
};
// 根本没有 cryOut
class Fox {public:
void possibleCryOut() {cout << "a hee-ahee ha-hee" << endl; }
}
Cat cat;
Dog dog;
Alien alien;
Bear bear;
Fox fox;
ImplicitlyInterface(cat); Compile OK! cat 可以 cryOut 并传回某个东西(int)。
ImplicitlyInterface(dog); Compile OK! dog 可以 cryOut 并传回某个东西(int)。
ImplicitlyInterface(alien); Compile OK! alien 可以 cryOut 并传回某个东西(const char*)。
ImplicitlyInterface(bear); Compiler Error! bear 虽然可以 cryOut,不能够回传某个东西(void)。
ImplicitlyInterface(fox); Compiler Error! fox 不能够 cryOut。
STL 实际应用
STL 中的 container (vector, deque, map, etc) 跟 string ,都没有相同的 Base Class ,但是它们都能唿叫 size() 与 empty(),另外也能够经由 begin() 与end() 来得到一组 Iterator 来使用于迴圈;各种型别的专属 Iterator 与 C++ 的指标更是完全不一样,但是只因为它们都能够支援 i++ 和 *i 等等行为,就可以相容于 STL Algorithm 的各项操作。
结论
使用隐性介面,可以把不同族的 Class 结合在一起, 使得各 低藕合性 的 Classes 能够运作良好;使用 template 所以 不失型别安全; 不造成效能降低,因为合约检查是在 Compile-time 完成; 操作更灵活,因为合约保证的粒度从 Interface Class 中所有的 函式都要有,且都要符合函式 signature 分割成更小的 能够满足函式唿叫的行为 即可, Andrei Alexandrescu 在 Modern C++ Design 的 Policy-based Class Design 更是以此为基石。
其他
若以多型(polymorphism)的角度来看,显式介面使用得是 dynamic polymorphism,在 runtime 的时候,依其 dynamic type 使用 vtable 或 vptr 决议其行为(method)。隐式介面则是在 compile time 时,具现化不同的 class,其行为(method)则是在 compile time 即决议完成,所以是一种 static polymorphism。在少了 runtime 的 dispatch 动作,效率上来说比较快的。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
