C++ 流畅接口 设计模式

流畅接口(Fluent Interface),第一次见是在看 RapidJSON 的 wiki 时看见的。

意为返回自己的引用,这样可以不间断地调用一个函数多次。

何为流畅接口?先上代码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。

作者:

喜欢围棋和编程。

 
发布于 分类 编程标签

发表评论

电子邮件地址不会被公开。