一般而言,程式碼的立即運算是比較符合人們想法直覺的演算法,也就是依據上一個運算的結果來決定後續的演算邏輯。但對較複雜的程式邏輯而言,立即運算常會形成多重運算路徑的歧路而使程式的開發與維護變得更繁複,面對這種狀況也許我們需要採用另一種典範來簡化設計;也就是延後運算的處理,等到需要得到答案時才一次求解演算出結果,省卻許多繁複的條件判斷或迴圈的控制。在這裡同人想發表一系列文章分享四種不同主題延遲運算,它們都是以 C++ 語言實作的延遲運算,包括訊息拆解與組合、拆解命令列、資料表的查詢、以及搜尋 Xml 找到符合的資料。
首先,這一篇文章我們先來談談延遲運算在訊息拆解與組合的應用。考慮開發需要拆解或組合符合某些格式的字串,一個簡單的作法是編寫共通的函式來拆解與組合字串。
void parseString(const char* msg_buffer, string& str, size_t pos = 0) { if (pos < strlen(msg_buffer)) { str.assign(msg_buffer + pos, str.size()); pos += str.size(); } } void parseChar(const char* msg_buffer, char& c, size_t pos = 0) { if (pos < strlen(msg_buffer)) { c = *(msg_buffer + pos); pos += sizeof(c); } } void tramsformString(string &response, const string& str) { response.append(str); } void transformChar(string& response, const char& c) { response.append(1, c); } size_t pos = 0; string field1(6, ' '); char field2; string msg = "str123x"; parseString(msg.c_str(), field1, pos); parseChar(msg.c_str(), field2, pos); // do something string response; transformString(response, field1); transformChar(response, field2);
我們發現上面的程式可以把處理拆解和組合訊息的中間變數,封裝成拆解和組合訊息這兩種抽象概念中,於是產生了 MessageParsing 和 MessageTransformation 這兩個 class,然後運用 Builder 樣式,拆解和組合訊息的程式碼就可以寫得再簡單一點:
MessageParsing::MessageParsing(const char* msg) : _msg_buffer(msg), _pos(0) {} MessageParsing::MessageParsing(const MessageParsing& parsing) { this->_msg_buffer = parsing._msg_buffer; this->_pos = parsing._pos; } MessageParsing::~MessageParsing() { } MessageParsing& MessageParsing::parse(string& str) { return this->parse(str, str.size()); } MessageParsing& MessageParsing::parse(string& str, size_t n) { if (this->_pos < strlen(this->_msg_buffer)) { str.assign(this->_msg_buffer + this->_pos, n); this->_pos += str.size(); } return *this; } MessageParsing& MessageParsing::parse(char& c) { if (this->_pos < strlen(this->_msg_buffer)) { c = *(this->_msg_buffer + this->_pos); this->_pos += sizeof(c); } return *this; } const char* MessageParsing::getRemain() const { return this->_msg_buffer + this->_pos; } MessageTransformation::MessageTransformation() : _result(EMPTY_STR) { } MessageTransformation::MessageTransformation( const MessageTransformation& trnfrmtn) { this->_result = trnfrmtn._result; } MessageTransformation::~MessageTransformation() { } MessageTransformation& MessageTransformation::transform( const string& str) { this->_result.append(str); return *this; } MessageTransformation& MessageTransformation::transform( const char& c) { this->_result.append(1, c); return *this; } string MessageTransformation::str() const { return this->_result; } string field1(6, ' '); char field2; bool parseOk = MessageParsing("str123x") .parse(field1).parse(field2).getRemain() == '\0'; // do something string response = MessageTransformation() .transform(field1).transform(field2).str();
到這裡,我們看到不管訊息拆解或是訊息組合,都是依序按照欄位形態及長度存取訊息字串,它顯示出訊息格式的概念。那麼藉由訊息格式,如果可以把拆解和組合訊息的演算抽離開來,這樣訊息格式就可以被訊息拆解及組合重複使用了。當然我們可以運用 visitor 樣式來達到這樣的目的,那麼使用情境就會長得像下面這一段程式碼這樣:
bool done = MessageFormat() .append(new Field<string>(model.fieldA, 9)) .append(new Field<char>(model.fieldB)) .with(MessageParse("123456789ABC")) .execute(); bool done = MessageFormat() .append(new Field<string>(model.fieldA, 9)) .append(new Field<char>(model.fieldB)) .with(MessageTransform()) .execute();
請注意,以上程式碼在 with() 所傳入的 FieldVisitor 並不只限一個,同人構想的這個設計是可以容納多個 FieldVisitor,而且也不限於 MassageParse 及 MessageTransform 兩種 FieldVisitor。例如使用情境可能是要檢核訊息語法正確後再解析欄位、甚至在解析欄位後會將訊息轉換成不同的媒體,因此 MessageFormat 在加入欄位後傳回自己的參考,讓呼叫端可以視需要可以接受多個 FieldVisitor,它的界面大致長像是這樣:
template <typename T> class Field; class FieldVisit { protected: bool _valid; public: FieldVisit() : _valid (true) { } virtual ~FieldVisit() { } public: virtual void operator() (Field<string>& field) = 0; virtual void operator() (Field<char>& field) = 0; virtual void operator() (Field<int>& field) = 0; virtual void operator() (Field<double>& field) = 0; virtual operator bool() { return _valid; } }; class FieldBase { public: virtual void operator() (FieldVisit& visit) = 0; }; template <typename T> class Field : public FieldBase { private: T& _ref; size_t _len; public: Field(T& ref, size_t len = 1) : _ref(ref), _len(len) { } ~Field() { } public: T& getRef() const { return _ref; } size_t getSize() const { return _len; } public: void operator() (FieldVisit& visit) { visit(*this); } }; template <> class Field<double> : public FieldBase { private: double& _ref; size_t _width; size_t _precision; public: Field(double& ref, size_t width, size_t precision) : _ref(ref), _width(width), _precision(precision) {} ~Field() {} public: double& getRef() const { return _ref; } size_t getWidth() const { return _width; } size_t getPrecision() const { return _precision; } public: void operator() (FieldVisit& visit) { visit(*this); } };
實作 FieldVisitor 的類別利用函數運算元來實作訊息欄位的存取,並且實作布林運算子判斷是否已發生錯誤,以中斷訊息格式的處理。這樣一來,MessageFormat 的處理就並不會很複雜,由呼叫者依序加入訊息的欄位並且指定如何處理訊息,然後再呼叫 execute() 來做一次性的處理,它的基本概念正是延遲運算,如此就可以簡單而有彈性地實作出 MessageFormat:
class MessageFormat { private: list<shared_ptr<FieldBase> > _fields; list<FieldVisit*> _visitlist; public: MessageFormat(); virtual ~MessageFormat(); public: MessageFormat& append(FieldBase* field) { _fields.push_back(shared_ptr<FieldBase>(field)); return *this; } MessageFormat& with(FieldVisit& visit) { _visitlist.push_back(&visit); return *this; } bool execute() { list<FieldVisit*>::iterator vst; for (vst = _visitlist.begin(); vst != _visitlist.end(); vst ++) { list<shared_ptr<FieldBase> >::iterator fld; size_t pos; for (pos = 0, fld = _fields.begin(); fld != _fields.end(); fld ++) { (*fld->get())(**vst); if (*(*vst)) { return false; } } } return true; } };
接下來把訊息拆解及組合的實作搬到 MessageParse 及 MessageTransform 的函數運算元,並且加入其它不同的資料型態,我們就可以依照特定格式包括欄位形態、長度及順序正常拆解和組合訊息。
MessageParse::MessageParse(const char* source) : _source(source), _pos(0) {} MessageParse::~MessageParse() {} void MessageParse::operator() (Field<string>& field) { if (_pos < _source.size()) { field.getRef().replace(0, field.getSize(), _source.substr(_pos, field.getSize())); _pos += field.getSize(); } else { _valid = false; } } void MessageParse::operator() (Field<char>& field) { if (_pos < _source.size()) { field.getRef() = _source.at(_pos); _pos ++; } else { _valid = false; } } void MessageParse::operator() (Field<int>& field) { if (_pos < _source.size()) { field.getRef() = atoi(_source.substr(_pos, field.getSize()).c_str()); _pos += field.getSize(); } else { _valid = false; } } void MessageParse::operator() (Field<double>& field) { if (_pos < _source.size()) { double fldval = atof(_source.substr(_pos, field.getWidth()).c_str()); if (field.getPrecision() > 0) { fldval /= pow(10.0, field.getPrecision()); } field.getRef() = fldval; _pos += field.getWidth(); } else { _valid = false; } } bool MessageParse::hasDone() const { return ((!_valid || _pos < _source.size()) ? false : true); } MessageTransform::MessageTransform() : _str(EMPTY_STR) {} MessageTransform::~MessageTransform() {} void MessageTransform::operator() (Field<string>& field) { _str.append(field.getRef(), 0, field.getSize()); } void MessageTransform::operator() (Field<char>& field) { _str.append(1, field.getRef()); } void MessageTransform::operator() (Field<int>& field) { ostringstream oss; oss.width(field.getSize()); oss.fill('0'); oss << right << field.getRef(); _str.append(oss.str()); } void MessageTransform::operator() (Field<double>& field) { ostringstream oss; oss.width(field.getWidth()); oss.precision(field.getPrecision()); oss.fill('0'); oss << fixed << right << field.getRef(); _str.append(oss.str()); } string MessageTransform::str() const { return _str; }
將來我們可以視實際需要,加入訊息處理的其它 FieldVisitor。從這樣的設計我們可以體會到延遲運算的妙用,除了讓程式更簡潔及更具有彈性外,更重要的是它帶來更不同設計典範的思維,不是思考系統要如何做,而是從系統要做什麼而出發,從元件的使用就清楚看到設計概念的布局,而不浪費心力去照顧繁複的實作細節,這正是利用延遲運算掌握住設計較高抽象層面的實例之一呀。
自動引用通知: 查詢的宣告式語意 « 同人的生活派對
自動引用通知: 更簡單的條件查詢設計 « 同人的生活派對