何为流畅接口?先上代码String(“1”)(“2”)(“3”)(“4”)(“5”)。流畅接口从字面上看是用起来很顺手,究竟是有多顺手,又应用在哪里呢?相信你看完本文多少会有些答案了。
以自定义String类(继承QString)为例。
class String : public QString { public: String() { } String(const QString &str) { append(str); } };
当我们需要往String增加字符串时我们常规做法:
String str; str.append("1"); str.append("2"); str.append("3"); str.append("4"); str.append("5");
感觉上面起来都有些啰嗦,看了下QString文档,QString的append接口调用后返回自己的引用,那么就可以这样写。
String str; str.append("1") .append("2") .append("3") .append("4") .append("5");
在这里看来,append接口就是流畅接口了,它避免啰嗦的调用。
头铁君这时候跳出来说,Qt君啊,你还是失算了。我还有更流畅的做法,你看:
String &operator()(const QString &str) { append(str); return *this; }
通过String类重载 ()
操作符后我还可以这样做:
String("1")("2")("3")("4")("5");
头铁君向我投来得瑟的目光,看得我瑟瑟发抖。
头铁君啊,你别老是这么头铁,看来我要放大招了。你那做法那里是流畅接口的精神啊,看到那么多括号都怕了,你别说这是给别人用?!
头铁君,来看看我的做法吧。
String &operator<<(const QString str) { append(str); return *this; }
通过重载 <<
操作符做出类似管道效果的流畅接口。
String str; str << "1" << "2" << "3" << "4" << "5";
头铁君看了不感惊叹,还真是比我那堆括号好用啊。就是看起来有些眼熟的呀。哦,对了。原来和 qDebug()
原理相似的呀。
qDebug() << "1" << "2" << "3" << "4" << "5";
头铁君出了名是头铁,还有其他的应用场合吗?
来来来,别急。我们再看下QString的 arg()
接口的使用。
QString fileName; QString size; QString md5; QString status = QString("File Info: file name: %1; size: %2; checksum md5: %3") .arg(fileName) .arg(size) .arg(md5);
头铁君若有所思一会,立马打开电脑查了起来。看着头铁君桌面的卡布奇诺都凉了,他还在查。看起来冻的卡布奇诺更有味道,感觉就是太甜了。
哦,原来是这样啊。这不就是Builder模式吗?通过返回自己的引用或指针来实现流式(链式)编程。明白了,头铁君抬头看到我喝着他的卡布奇诺,为了避免尴尬,说了一句,今天天气不错哈。
从流畅接口(Builder模式)到只读对象的实现。
Network类只提供获取ip,mask,gateway,dns的方法,而设置方法都被隐藏起来了。而真正实现设置网络属性是通过NetworkBuilder类来进行操作。并可以通过它来体现流畅接口。
class NetworkBuilder; class Network { public: Network(); static NetworkBuilder builder(); QString ip() const; QString mask() const; QString gateway() const; QString dns() const; private: friend NetworkBuilder; QString m_ip; QString m_mask; QString m_gateway; QString m_dns; }; class NetworkBuilder { public: NetworkBuilder &ip(const QString &ip); NetworkBuilder &mask(const QString &ip); NetworkBuilder &gateway(const QString &gateway); NetworkBuilder &dns(const QString &dns); Network build(); };
通过NetworkBuilder构建Network属性后通过build的调用返回Network对象。
Network::builder().ip("192.168.1.1") .mask("255.255.255.0") .gateway("192.168.1.1") .dns("8.8.8.8") .build();
现实意义:
- 通过只读对象来限定接口使用者的乱用行为;
- 明确确定设置参数后需要调用build接口生效,这就意味着可以提醒接口调用者生效了那些参数。
- 可以体现出清晰明确的编程。
- 让接口调用者用来顺手(流畅)。
template<typename T> class Array{ ... Array &push_back(const T& value){ ... return *this; } };
这样插入操作的时候,就可以
1 Array<int> arr; 2 arr.push_back(1).push_back(2);
这只是简单的用法。
之前看到过一个问题,如何在 C++ 中实现这种函数调用效果:
1 if ( add(3)(4)(10) == 17 ) 2 return true;
我的方法是用 operator() 重载 + 隐式类型转换:
class Add{ public: Add():sum(0){} Add(int value) : sum(value){} Add &operator() (int value){ sum += value; return *this; } operator int(){ return sum; } private: int sum; }; int main(void){ if (Add(3)(4)(10) == 17) cout << "True" << endl; else cout << "False" << endl; return 0; }
这个例子里,Add(3) 会调用 Add(int value) 来构造一个临时 Add 对象,然后后面会调用两次 Add& operator()(int value) ,此时 == 左边是一个 sum 为 17 的 Add 临时对象,同时 == 会调用 Add 对 int 的隐式类型转换,所以比较的是临时对象的 sum 和 17。