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

一、链式调用

在前面的文章中,已经对链式调用有了不少的了解。比如" this指针"和"设计模式"以及C++23中显式this等文章中都多少有些涉及。但实际上,C++对链式调用的支持并不多给力。如果有Java特别是Kotlin语言开发经验的,对链式调用应该是非常容易理解的。

所谓链式调用,就是像链表一样,将函数的调用连接起来,即可以连续调用多个函数。它让代码看起来更直白、易维护。不过如果调用链太深,反而让开发者有点感觉到莫名其妙。这也是事物的两面性吧。链式调用更符合自然语言,所以在一些函数编程语言以及高级语言中应用非常广泛。C++中的链式调用应用并没有其它语言那么广泛,这也是C++语言本身的一些特点和应用场景限制的。

二、运行机制和原理

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

三、实现方式

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

  1. 对象引用和指针的操作
    在函数的返回值中通过返回指针(this)或 引用(*this)的方式来传回同一对象实例。这是最常用的方法:
c 复制代码
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)
    流畅式接口的设计实现其实更倾向于从逻辑上对链式调用的实现,实际实现并未脱离链式调用实现的基本方法。以一个电商的操作为例:
c 复制代码
#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中对<<操作符的重载,看下面的简单例子:
c 复制代码
#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"的文章,下面看例子:
c 复制代码
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. 发布-订阅机制
    通过事件驱动消息的链式调用发送

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

五、例程

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

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

相关推荐
2301_810160957 分钟前
C++中的访问者模式高级应用
开发语言·c++·算法
郝学胜-神的一滴8 分钟前
走进计算机图形学的浪漫宇宙 | GAMES101 开篇课程全解析
c++·算法·图形渲染·计算机图形学
m0_5180194810 分钟前
C++中的享元模式
开发语言·c++·算法
我带你来这儿就是为了告诉你我15 分钟前
C++23新特性前瞻
开发语言·c++·算法
2501_9083298530 分钟前
C++安全编程指南
开发语言·c++·算法
计算机安禾32 分钟前
【C语言程序设计】第39篇:预处理器与宏定义
c语言·开发语言·c++·vscode·算法·visual studio code·visual studio
m0_5698814743 分钟前
C++中的装饰器模式变体
开发语言·c++·算法
weixin_421922691 小时前
C++与边缘计算
开发语言·c++·算法
2401_831920741 小时前
C++编译期数组操作
开发语言·c++·算法
Trouvaille ~1 小时前
【优选算法篇】哈希表——空间换时间的极致艺术
c++·算法·leetcode·青少年编程·蓝桥杯·哈希算法·散列表