游戏之作: Restful命令处理对象
我们的产品有几十条Restful命令, 凭心而林, 命令太多, 不正交. 更大的问题是命令的api更杂乱.
对上层代码而言, 屏蔽Restful命令差异的方式当然是封装接口函数. 但是为每一个函数封装一个接口, 又有大量的样板代码. 虽然前期我是这么做的, 但是随着接口越来越多, 也越来越让人感到厌烦.
比如, 下面是一条命令的实现:
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
| bool KaryoServices::modifyCaseInfo(const QString &case_id, const QVariantMap &info) { QJsonObject rsp;
QJsonObject param = QJsonObject::fromVariantMap(info); TRACE() << param;
QString path = QString("/api/case/%1").arg(case_id); try { rsp = _impl->_api->putSync(path, QJsonDocument(param)); } catch (const std::exception& e) { _impl->_errorString = QString(tr("modify case info failed: %1")).arg(e.what()); TRACE() << _impl->_errorString << "path: " << path; emit errorMsg(_impl->_errorString); return false; }
if(rsp["code"].toInt() != 200) { _impl->_errorString = QString(tr("modify case info error: %1")).arg(rsp["message"].toString()); TRACE() << _impl->_errorString << "path: " << path; emit errorMsg(_impl->_errorString); } TRACE() << "修改病历信息成功" << path; return true; }
|
换一个命令, 其实现也都是的大同小异.
当然, 一种做法是将从try开始的部分都提取到一个公共函数里面. 我们对查询命令也是这么做的. 那么, 有没有一种更好的做法呢?
今天又新增了一条修改命令, 突发奇想, 将每条命令实现为一个函数对象如何? 这样才像是C++的样子嘛:-).
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| enum class ApiCmdType { modifyCase, modifySlide, modifyMitosis, };
template <ApiCmdType cmd> class DbModifier { public: template<typename... Ts, ApiMethodType=ApiMethodType::putMethod> DbModifier(KaryoServices* service, Ts... args) : _service{service} { QString result ; if constexpr (cmd==ApiCmdType::modifyCase) { result = "api/case/"; } else if constexpr (cmd==ApiCmdType::modifySlide) { result = "api/slide/"; } else if constexpr (cmd==ApiCmdType::modifyMitosis) { result = "api/mitosis/"; } auto h = [](const auto& x){ return QString("%1/").arg(x); }; _path = (result + ... + h(args)); } DbModifier& operator() (const QString& name, QVariant value){ _params.insert(name, value); return *this; } tl::expected<bool, QString> operator()() { QJsonObject rsp; QJsonObject param = QJsonObject::fromVariantMap(_params); try { rsp = _service->api()->putSync(_path, QJsonDocument(param)); } catch (const std::exception& e) { QString errmsg = QString("command %1 failed: %2").arg(_path).arg(e.what()); return tl::unexpected(errmsg); } if(rsp["code"].toInt() != 200) { QString errmsg = QString("command %1 failed: %2").arg(_path).arg(rsp["message"].toString()); return tl::unexpected(errmsg); } return true; } const QString path() const{ return _path; } const QVariantMap& data() const{ return _params; } private: KaryoServices* _service; QString _path; QVariantMap _params; };
|
这里的operator()
纯属给自己找不自在, 完全可以起一个正常点的函数名”-).
下面是使用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| TRACE() << "开始更新Case信息..."; karyolib::services::DbModifier<karyolib::services::ApiCmdType::modifyCase> caseModifier(_services, _impl->_context._case_id); auto ret = caseModifier("id", _impl->_context._case_id) ("sufferer", result.value("name")) ("age", result.value("age")) ("age_unit", result.value("ageUnit")) ("sex", result.value("gender")) ("identity", result.value("identity")) ("case_no", result.value("caseNo")) ("summary", result.value("diagnosis")) ("admission_no", result.value("admissionNo")) ("bed_no", result.value("bedNo")) ("department", result.value("department")) (); TRACE() << QString("更新case信息%1").arg(ret.has_value() ? "成功":"失败:"+ret.error());
|
这里主要利用了C++17的const if和variadic两个特性, 个人认为这是C++17中最有价值的特性. 其实, 相对来说, C++20的特性对我们这种普通人来说, 反倒没有太多惊艳的地方: 最惊艳的concept
和module
, 前者我几乎不用元编程, 后者在Qt下, 想用也几乎用不了.