余建華 趙曉蓮
摘? 要: 模板方法是設計模式中一種典型的設計范式,傳統(tǒng)上是利用面向對象編程技術的多態(tài)屬性實現(xiàn)的,文章應用泛型編程技術來實現(xiàn)模板方法設計模式。這種實現(xiàn)方式舍棄了面向對象編程技術中基于繼承的基類,派生類的虛函數機制,免去了在程序運行過程中虛函數二次尋址的代價。在保留設計模式中程序修改擴展彈性的同時,降低了程序運行負載,提高運行速度,展現(xiàn)出一種泛型編程技術不同于傳統(tǒng)的應用場景。
關鍵詞: 面向對象編程; 泛型編程; 模板函數; 模板方法; 設計模式
中圖分類號:TP311.1? ? ? ? ? 文獻標志碼:A? 文章編號:1006-8228(2019)05-41-05
Abstract: The template method is a representative pattern in design patterns. Traditionally, it is implemented by polymorphic attributes of object oriented programming technology. This paper introduces a method that implements the template method pattern by generic programming. This method of implementation abnegates the inheritance based virtual function mechanism which is the pivotal conception in object oriented programming, and avoids the expense for the second function addressing of virtual function in program running. The template method pattern implemented by generic programming not only reserves the flexibility for program extending and amending, but also improves the speed of program executing, demonstrating another scene of application of generic programming which is different from the traditional.
Key words: object oriented; generic programming; template function; template method pattern; design patterns
0 引言
泛型編程技術及在其上發(fā)展的模板元編程是C++編程領域近年來最為關注的課題。模板是實現(xiàn)泛型編程技術的關鍵性C++構件[1]。模板第一次具有里程碑意義的應用是標準模板程序庫STL,其通過泛型容器、迭代器、泛型算法實現(xiàn),富有彈性,包含數據結構和算法的可復用組件庫可擴展,淋漓盡致地向我們展現(xiàn)了模板的使用藝術,以至于我們一提到模板,首先想到的就是實現(xiàn)泛型容器的技術。實際上模板技術的魅力遠不止于此,利用其具有的編譯期計算能力而發(fā)展的模板元編程技術,以及其與設計模式的結合,為我們打開了泛型編程領域的一片新天地。 設計模式是早先C++編程領域的專家為幫助應用開發(fā)者針對一些常見的,特定的應用問題而歸納總結的一組可復用的設計范式[3]。模板方法是設計模式中一種典型的行為型模式。本文首先介紹傳統(tǒng)利用面向對象編程技術的多態(tài)屬性實現(xiàn)的方式,然后應用泛型編程技術實現(xiàn)模板方法設計模式,最后闡述兩種實現(xiàn)的差異。
1 面向對象技術實現(xiàn)模板方法
模板方法是一種行為型的設計模式,主要思想是在基類中定義功能單元或算法的骨架,而這個骨架使用的細節(jié)操作延遲到派生類實現(xiàn)。在不改變骨架的條件下,通過某些細節(jié)操作步驟的變更,我們可以方便地維護,擴展功能單元或算法。
例如我們想計算封閉幾何圖形的面積與周長比率,以此來發(fā)現(xiàn)何種圖形的單位周長圍成的面積最大。
首先定義一個幾何圖形基類:
class Geometrics
{ public:
float CalculateRatio0Area2Circularity(void);
private:
virtual float CalculatePerimeter(void)=0;
virtual float CalculateArea(void)=0;
};
基類的計算面積與周長比率的公有成員函數用來實現(xiàn)算法的骨架,其主要是調用計算周長和面積的純虛函數來算出幾何圖形的周長和面積,然后算出比率。
float Geometrics::CalculateRatio0Area2Circularity(void)
{ float fPerimeter=1.0,fArea=1.0;
fPerimeter=CalculatePerimeter();
fArea=CalculateArea();
return(fArea/fPerimeter);
}
定義一個圓的子類,用以實際計算圓的面積與周長比率。
class Circularity: public Geometrics
{ public:
Circularity(int iRadius):m_iRadius(iRadius){};
private:
float CalculatePerimeter(void);
float CalculateArea(void);
private:
int m_iRadius;
};
圓形子類實現(xiàn)基類的計算周長和面積的純虛函數,用以具體計算圓形的周長和面積。
float Circularity::CalculatePerimeter()
{ return 2*PI*m_iRadius; }
float Circularity::CalculateArea()
{ return PI*m_iRadius*m_iRadius; }
同樣定義一個矩形的子類,用以實際計算矩形的面積與周長比率
class Rectangle:public Geometrics
{ public:
Rectangle(int iLength, int iWidth):m_length(iLength),
m_width(iWidth) {};
private:
float CalculatePerimeter(void);
float CalculateArea(void);
private:
int m_length;
int m_width;
};
矩形子類實現(xiàn)基類的計算周長和面積的純虛函數,用以具體計算矩形的周長和面積
float Rectangle::CalculatePerimeter()
{ return 2*(m_length+m_width); }
float Rectangle::CalculateArea()
{ return m_length * m_width; }
主函數調用基類對象使用計算圓和矩形面積與周長比率的功能。注意生成的是派生類對象,指針卻是基類對象類型。
int main()
{ class Geometrics *pGeometrics01=new Circularity(3);
class Geometrics *pGeometrics02=new Rectangle(3,3);
std::cout<
std::cout< return 0; } 使用模板方法,我們可以方便地擴展或修改。如通過增加其他圖形的子類,計算其他圖形的面積與周長比率,并不需要再定義面積與周長比率的計算方法;或者修改某一圖形面積與周長的計算方法,也不影響其他已有圖形面積與周長的計算。 2 泛型編程技術實現(xiàn)模板方法 通過泛型編程技術也能實現(xiàn)模板方法設計模式的功能。 定義一個函數模板,用以實現(xiàn)計算面積與周長比率的算法骨架。 template float CalculateRatio0Area2Circularity(const T& tGeometrics) { float fPerimeter=1.0,fArea=1.0; fPerimeter=tGeometrics.CalculatePerimeter(); fArea=tGeometrics.CalculateArea(); return(fArea/fPerimeter); } 定義一個圓形類,用以實際計算圓的面積與周長比率,注意這里不再有繼承關系。 class Circularity { public: Circularity(int iRadius):m_iRadius(iRadius){}; float CalculatePerimeter(void) const; float CalculateArea(void) const; private: int m_iRadius; }; 圓形類實現(xiàn)計算周長和面積的成員函數,用以具體計算圓形的周長和面積 float Circularity::CalculatePerimeter() const { return 2*PI*m_iRadius; } float Circularity::CalculateArea() const { return PI*m_iRadius*m_iRadius; } 定義一個矩形類,用以實際計算矩形的面積與周長比率。 class Rectangle { public: Rectangle(int iLength, int iWidth):m_length(iLength), m_width(iWidth) {}; float CalculatePerimeter(void) const; float CalculateArea(void) const; private: int m_length; int m_width; }; 矩形類實現(xiàn)計算周長和面積的成員函數,用以具體計算矩形的周長和面積 float Rectangle::CalculatePerimeter() const { return 2*(m_length + m_width); } float Rectangle::CalculateArea() const { return m_length * m_width; } 主函數調用函數模板使用計算圓和矩形面積與周長比率的功能。 int main() { class Circularity stCircularity01(3); class Rectangle stRectangle01(3,3); std::cout<<"Circularity:" < std::cout<<"Rectangle:" < return 0; } 3 面向對象技術與泛型編程技術的復合應用 我們舉例的這個計算幾何圖形的面積與周長比率的問題,在實現(xiàn)算法上分成了兩個層次。第一層是利用計算出來的面積與周長相除,計算比率。第二層是計算具體幾何圖形的面積與周長。第一層是共性操作,第二層是特性操作。 實現(xiàn)這個算法的第一個方法是設計模式中的模板方法。將第一層共性操作放到基類成員函數中作為實現(xiàn)方法的骨架,將第二層特性操作放到派生類成員函數,實現(xiàn)這個骨架使用的細節(jié)操作。這種基于C++繼承和虛擬函數機制的變化由用戶使用基類類型的指針來調用虛擬函數來支持。正是因為不管實際上生成哪一個派生類對象,在使用形式上都是基類指針,編譯期無法決定實際調用對象的成員函數,此任務由執(zhí)行期獲得對象的實際派生類型后決定,因此這種機制的彈性變化稱之為動態(tài)多態(tài)[2]。 實現(xiàn)這個算法的第二個方法是泛型編程的模板技術。通過模板函數的表達式及具體操作,我們在模板和將要用以具現(xiàn)模板的參數間約定了一組接口,所有具現(xiàn)模板的參數類型都要實現(xiàn)這些接口。這些接口就是抽象出來的所有參數類型的共性操作。把這些共性操作放置于模板中,把細節(jié)實現(xiàn)的特性操作放置于具體類型的成員函數中,我們同樣實現(xiàn)了解決方法模型的兩層結構,由此獲得同樣的設計彈性。由于具體使用的對象類型在編譯期具現(xiàn)化模板函數時就已確定,因此這種基于模板技術實現(xiàn)的彈性變化稱之為靜態(tài)多態(tài)[5]。 現(xiàn)在我們來個“動”,“靜”結合,把設計模式中的模板方法和泛型編程中的模板技術復合起來產生一個解決問題的方法模型用以解決更復雜的問題,同時擁有更高的彈性。為此我們把舉的例子修改一下,有計算基礎幾何圖形的面積與周長比率改為組合幾何圖形的面積與周長比率。這種組合有兩種方式,凸型和凹型,如圖1所示。 我們解決問題的算法模型結構分為三個層次。如圖2所示。 第一層是計算幾何圖形的面積與周長比率,如果比率的算法有修改需求,將在這里修改。注意這里的修改將影響所有圖形比率的計算,這是共性層。我們用函數模板實現(xiàn)這一層。 template float CalculateRatio0Area2Circularity(T& tGeometrics) { float fPerimeter=1.0,fArea=1.0; fPerimeter=tGeometrics.CalculatePerimeter(); fArea=tGeometrics.CalculateArea(); return(fArea/fPerimeter); } 第二層是計算“凸” 或 “凹” 組合圖形的面積與周長。我們分別定義兩個基類管理計算。如果想要擴展計算其他組合方式的組合圖形,可以在這一層擴展新基類,這是偏特性層(借用泛型編程術語)。 凸組合圖形基類的定義: class Convexity { public: float CalculatePerimeter(void); float CalculateArea(void); private: virtual float CalculatePerimeterPolygonA(void)=0; //const; virtual float CalculatePerimeterPolygonB(void)=0; virtual float CalculateAreaPolygonA(void)=0; virtual float CalculateAreaPolygonB(void)=0; }; 凸組合圖形計算面積與周長: float Convexity::CalculatePerimeter(void) { float fPerimeterPolygonA, fPerimeterPolygonB; fPerimeterPolygonA=CalculatePerimeterPolygonA(); fPerimeterPolygonB=CalculatePerimeterPolygonB(); return fPerimeterPolygonA + fPerimeterPolygonB; } float Convexity::CalculateArea(void) { float fAreaPolygonA, fAreaPolygonB; fAreaPolygonA=CalculateAreaPolygonA(); fAreaPolygonB=CalculateAreaPolygonB(); return fAreaPolygonA+fAreaPolygonB; } ⑴ 凹組合圖形基類的定義與凸組合圖形基類類似; ⑵ 凹組合圖形計算周長的方法與凸組合圖形基類相同,凹組合圖形計算面積的方法與凸組合圖形基類類似,前者組成組合圖形的子圖形相減,后者是相加,此處不再列出; ⑶ 第三層是計算具體組合圖形的面積與周長,也是面積與周長的實際計算點。如果“凸” 或 “凹” 組合圖形由其他基礎圖形構成,可在這一層擴展,這是特性層。我們用派生類來管理計算。 由圓與矩形組成的凸組合圖形類定義: class ConvexityConcreteA: public Convexity { public: ConvexityConcreteA(int iRadius, int iLength): m_iRadius(iRadius), m_iLength(iLength) {}; private: virtual float CalculatePerimeterPolygonA(void); virtual float CalculatePerimeterPolygonB(void); virtual float CalculateAreaPolygonA(void); virtual float CalculateAreaPolygonB(void); private: int m_iRadius; int m_iLength; }; 由圓與矩形組成的凸組合圖形面積與周長的計算: float ConvexityConcreteA::CalculatePerimeterPolygonA(void) { return 2*(m_iLength + m_iRadius); } float ConvexityConcreteA::CalculatePerimeterPolygonB(void) { return PI*m_iRadius; } float ConvexityConcreteA::CalculateAreaPolygonA(void) { return 2*m_iRadius * m_iLength; } float ConvexityConcreteA::CalculateAreaPolygonB(void) { return 0.5*PI*pow(m_iRadius, 2); } 由圓與三角形組成的凸組合圖形類定義: class ConvexityConcreteB: public Convexity { public: ConvexityConcreteB(int iHigh, int iLength, int iWidth):m_iHigh(iHigh), m_iLength(iLength), m_iWidth(iWidth){}; private: virtual float CalculatePerimeterPolygonA(void); virtual float CalculatePerimeterPolygonB(void); virtual float CalculateAreaPolygonA(void); virtual float CalculateAreaPolygonB(void); private: int m_iHigh; int m_iLength; int m_iWidth; }; 由圓與三角形組成的凸組合圖形面積與周長的計算: float ConvexityConcreteB::CalculatePerimeterPolygonA(void) { return 2*m_iLength + m_iWidth; } float ConvexityConcreteB::CalculatePerimeterPolygonB(void) { return 2*sqrt(pow(0.5*m_iWidth, 2)+pow(m_iHigh, 2)); } float ConvexityConcreteB::CalculateAreaPolygonA(void) { return m_iLength * m_iWidth; } float ConvexityConcreteB::CalculateAreaPolygonB(void) { return 0.5*m_iWidth*m_iHigh; } 由圓與矩形組成的凹組合圖形類定義與相應的凸組合圖形定義類似,其組成的凹組合圖形面積與周長的計算與相應的凸組合圖形面積與周長計算類似; 由圓與三角形組成的凹組合圖形類定義與相應的凸組合圖形定義類似,其組成的凹組合圖形面積與周長的計算與相應的凸組合圖形面積與周長計算類似,此處不再列出。 在主函數中,我們用模板函數來獲取各個組合圖形的面積與周長比率。 int main() { class ConvexityConcreteA stConvexityConcreteA(2,4); class ConvexityConcreteB stConvexityConcreteB(2,4,4); class ConcavityConcreteA stConcavityConcreteA(2,4); class ConcavityConcreteB stConcavityConcreteB(2,4,4); std::cout<<"ConvexityConcreteA:" < std::cout<<"ConvexityConcreteB:" < std::cout<<"ConcavityConcreteA:" < std::cout<<"ConcavityConcreteB:" < return 0; } 4 總結 模板方法是設計模式中一種典型的行為型設計范式[6],在設計模式中是利用面向對象編程技術中基于繼承的基類,派生類的虛函數機制來實現(xiàn)的。本文應用泛型編程技術來實現(xiàn)模板方法設計模式。這種實現(xiàn)方式是利用的是模版函數與實現(xiàn)參數間的約定接口[4],在接口的定義域實現(xiàn)過程中獲得與設計模式同樣的設計彈性,同時省去了虛函數運行期多態(tài)的動態(tài)開銷,降低了運行負載,展現(xiàn)出一種泛型編程技術不同于傳統(tǒng)的應用場景。 參考文獻(References): [1] Stanley B·Lippman,Josée Lajoie . C++ Primer 中文版(第三版)[M].電子工業(yè)出版社,2013. [2] Bruce Eckel.C++編程思想(第二版)[M].機械工業(yè)出版社,2002. [3] Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides.設計模式 可復用面向對象軟件的基礎[M].機械工業(yè)出版社,2000. [4] Andrei Alexandrescu. Modern C++ Design[M].Addison-Wesley Professional,2003. [5] David Vandevoorde,Nicolai M.Josuttis. C++Templates中文版[M].人民郵電出版社,2008. [6] 結城浩.圖解設計模式[M].人民郵電出版社,2016.