C++编程实践——链式调用的实践

一、链式调用

在前面的文章中,已经对链式调用有了不少的了解。比如“ this指针”和“设计模式”以及C++23中显式this等文章中都多少有些涉及。但实际上,C++对链式调用的支持并不多给力。如果有Java特别是Kotlin语言开发经验的,对链式调用应该是非常容易理解的。
所谓链式调用,就是像链表一样,将函数的调用连接起来,即可以连续调用多个函数。它让代码看起来更直白、易维护。不过如果调用链太深,反而让开发者有点感觉到莫名其妙。这也是事物的两面性吧。链式调用更符合自然语言,所以在一些函数编程语言以及高级语言中应用非常广泛。C++中的链式调用应用并没有其它语言那么广泛,这也是C++语言本身的一些特点和应用场景限制的。

二、运行机制和原理

链式调用的原理本质是对对象引用或指针的控制和处理。链式调用需要在每次完成后继续调用相关的函数,就必须得到函数所在的对象,进而才能够进行下一步的函数调用。所以链式调用的核心机制就是保证必须能够通过上一个函数调用返回应用对象的引用或指针,从而确保连续调用的函数影响作用到对象的同一实例(某些扩展实现可能不是这种情况)。

三、实现方式

实现链式调用对于C++这类语言来说并不复杂,但也并如函数式语言那么简单。其主要的实现方式包括:

  1. 对象引用和指针的操作
    在函数的返回值中通过返回指针(this)或 引用(*this)的方式来传回同一对象实例。这是最常用的方法:
class Demo {
public:
    Demo* setColor(int v) {
        color_ = v;
        return this;
    }

    Demo* setHigh(int v) {
        high_ = v;
        return this;
    }

private:
    int color_ = 0;
    int high_ = 160;
};
int main(){
    Demo d;
    d.setColor(255)->setHigh(180);

    return 0;
}

  1. 流畅接口实现(Fluent Interface)
    流畅式接口的设计实现其实更倾向于从逻辑上对链式调用的实现,实际实现并未脱离链式调用实现的基本方法。以一个电商的操作为例:
#include <iostream>
#include <string>
#include <vector>

class OnShoppingCart {
private:
    std::vector<std::string> itemName_;
    std::string userName_;
    
public:
    OnShoppingCart& getUser(const std::string& userName) {
        userName_ = userName;
        return *this;
    }
    
    OnShoppingCart& addItem(const std::string& itemName) {
        itemName_.push_back(itemName);
        return *this;
    }
};

int main() {
    OnShoppingCart cart;
    cart.getUser("iPad")
        .addItem("iPhone")
        .addItem("iWatch");
    
    return 0;
}
  1. 运算符重载实现
    这种实现非常常见,比如std::cout中对<<操作符的重载,看下面的简单例子:
#include <string>
class Demo {
public:
    Demo* setColor(int v) {
        color_ = v;
        return this;
    }

    Demo* setHigh(int v) {
        high_ = v;
        return this;
    }
    Demo& operator<<(const std::string&msg){
        msg_ += msg;
        return *this;
    }

private:
    int color_ = 0;
    int high_ = 160;
    std::string msg_= "";
};
int main(){
    Demo d;
    d<<"hello "<<"world!";

    return 0;
}
  1. 模板中的CRTP实现
    这个在前面有专门的论述,可参看相关“CRTP”的文章,下面看例子:
template <typename ConcretePrinter>
class Printer
{
public:
    Printer(std::ostream& pstream) : stream_(pstream) {}

    template <typename T>
    ConcretePrinter& print(T&& t)
    {
        stream_ << t;
        return static_cast<ConcretePrinter&>(*this);
    }

    template <typename T>
    ConcretePrinter& println(T&& t)
    {
        stream_ << t << std::endl;
        return static_cast<ConcretePrinter&>(*this);
    }
private:
    std::ostream& stream_;
};
enum  Color
{
red,blue,green
};
class CoutPrinter : public Printer<CoutPrinter>
{
public:
    CoutPrinter() : Printer(std::cout) {}

    CoutPrinter& SetConsoleColor(Color c)
    {
        return *this;
    }
};
void TestChain()
{
    CoutPrinter().print("Hello ").SetConsoleColor(Color::red).println("Printer!");
}

int main()
{
    TestChain();
    return 0;
}

CRTP对于大多数的开发者可能觉得有点陌生,不想深入学习模板技术的可以只知道有这么一回事即可,不必深究。

四、应用场景

链式调用的应用场景其实也不算少,主要有:

  1. 设计模式中的应用
    比如常见的建造者模式、流畅接口模式等。
  2. 异步调用
    在异步调用中可以使用链式调用来处理回调,让代码更简洁和方便
  3. 发布-订阅机制
    通过事件驱动消息的链式调用发送

链式调用优点明显但也有不少的缺点,典型的就是链式调用过程中出现异常的处理比较复杂,另外一个就是调试过程中复杂的来回跳转,增加了调试中的困难。这些大家要根据情况自行评估应用。

五、例程

在上面学习的基础上,看一个比较典型的观察者模式中对事件通知的处理:

#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>

class Observer {
public:
    virtual ~Observer() = default;
    virtual void onEvent(const std::string& event, const std::string& task) = 0;
};

class TaskObserver : public Observer {
private:
    std::string runnerName_;
    
public:
    TaskObserver(const std::string& name) : runnerName_(name) {}
    
    void onEvent(const std::string& event, const std::string& task) override {
        std::cout << runnerName_ <<"Event: " << event << ", task: " << task << std::endl;
    }
};

class EventControl {
private:
    std::vector<std::shared_ptr<Observer>> observers_;
    
public:
    EventControl& insertObserver(std::shared_ptr<Observer> ob) {
        observers_.push_back(ob);
        return *this;
    }
    
    EventControl& delObserver(std::shared_ptr<Observer> ob) {
        auto it = std::remove(observers_.begin(), observers_.end(), ob);
        observers_.erase(it, observers_.end());
        return *this;
    }
    
    EventControl& notify(const std::string& event, const std::string& task = "") {
        for (const auto& ob : observers_) {
            ob->onEvent(event, task);
        }
        return *this;
    }
    
    EventControl& clear() {
        observers_.clear();
        return *this;
    }
};

int main() {
    EventControl control;
    
    auto workderA = std::make_shared<TaskObserver>("workderA");
    auto workderB = std::make_shared<TaskObserver>("workderB");
    
    control.insertObserver(workderA)
           .insertObserver(workderB)
           .notify("task1", "start eating...")
           .notify("task2", "start eating the soup...")
           .delObserver(workderA)
           .notify("finish", "all finished!")
           .clear();
    
    return 0;
}

六、总结

链式调用作为C++中一种比较优雅的设计方式对于提高项目整体设计和开发的简洁性有着很重要的帮助,同时其良好的维护性和可扩展性也为后续的开发提供了方便的接口实现。但其本身所固有的一些问题也是比较突出的,这就需要设计和开发者根据自己的实际需求进行权衡应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值