设计模式-流畅接口

何为流畅接口?先上代码String(“1”)(“2”)(“3”)(“4”)(“5”)。流畅接口从字面上看是用起来很顺手,究竟是有多顺手,又应用在哪里呢?相信你看完本文多少会有些答案了。

以自定义String类(继承QString)为例。

1
2
3
4
5
6
7
8
class String : public QString {
public:
String() { }
String(const QString &str)
{
append(str);
}
};

当我们需要往String增加字符串时我们常规做法

1
2
3
4
5
6
String str;
str.append("1");
str.append("2");
str.append("3");
str.append("4");
str.append("5");

感觉上面起来都有些啰嗦,看了下QString文档,QString的append接口调用后返回自己的引用,那么就可以这样写。

1
2
3
4
5
6
String str;
str.append("1")
.append("2")
.append("3")
.append("4")
.append("5");

在这里看来,append接口就是流畅接口了,它避免啰嗦的调用。

头铁君这时候跳出来说,Qt君啊,你还是失算了。我还有更流畅的做法,你看:

1
2
3
4
5
String &operator()(const QString &str)
{
append(str);
return *this;
}

通过String类重载()操作符后我还可以这样做:

1
String("1")("2")("3")("4")("5");

头铁君向我投来得瑟的目光,看得我瑟瑟发抖

头铁君啊,你别老是这么头铁,看来我要放大招了。你那做法那里是流畅接口的精神啊,看到那么多括号都怕了,你别说这是给别人用?!

头铁君,来看看我的做法吧。

1
2
3
4
5
String &operator<<(const QString str)
{
append(str);
return *this;
}

通过重载<<操作符做出类似管道效果的流畅接口。

1
2
String str;
str << "1" << "2" << "3" << "4" << "5";

头铁君看了不感惊叹,还真是比我那堆括号好用啊。就是看起来有些眼熟的呀。哦,对了。原来和qDebug()原理相似的呀。

1
qDebug() << "1" << "2" << "3" << "4" << "5";

头铁君出了名是头铁,还有其他的应用场合吗?

来来来,别急。我们再看下QString的arg()接口的使用。

1
2
3
4
5
6
7
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类来进行操作。并可以通过它来体现流畅接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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对象。

1
2
3
4
5
Network::builder().ip("192.168.1.1")
.mask("255.255.255.0")
.gateway("192.168.1.1")
.dns("8.8.8.8")
.build();

现实意义:

  • 通过只读对象来限定接口使用者的乱用行为;
  • 明确确定设置参数后需要调用build接口生效,这就意味着可以提醒接口调用者生效了那些参数。
  • 可以体现出清晰明确的编程。
  • 让接口调用者用来顺手(流畅)。

文章首发微信公众号Qt君。