Fortran 筆記之 繼承和聚合
繼承(類擴充套件)和聚合
參考自Introduction to Modern Fortran for the Earth System Sciences
我們在3.3部分的開頭提到過,OOP正規化通常會導致型別的層次結構。Fortran程式設計師可以使用兩種機制來構造這些層次結構:繼承和聚合。我們將在本節中簡要討論這些問題。
作為一個簡單的展示示例,我們將瞭解如何從上一節(Vec2d)擴充套件DT,以表示3D向量。
繼承
實現型別層次結構的第一種機制是繼承(inheritance)(在Fortran標準中稱為“型別擴充套件”)。這通過表達由型別建模的實體之間的“是”型別關係來實現程式碼重用。例如,在ESS中的植被模型中,我們可以定義一種型別的plant,以收集與所有plant型別的模型相關的屬性(例如反照率)。然後可以為tree, grass等實現專門的型別,它們繼承
回到我們的簡單例子,我們使用繼承來定義Vec3d:
1 ! File: dt_composition_inheritance.f90 2 ! Purpose: Demonstrate how more complex types can be constructed using 3 ! inheritance (="type-extension" in Fortran); specifically, we look at 4 ! how a 'Vec3d'-type may be constructed from a 'Vec2d'-type. 5 6 module Vec2d_class 7 implicit none 8 private 9 10 type, public :: Vec2d ! DT explicitly declared "public" 11 private ! Make internal data "private" by default. 12 real :: mU = 0., mV = 0. 13 contains 14 private ! Make methods "private" by default. 15 procedure, public :: getMagnitude => getMagnitudeVec2d 16 procedure, public :: getU => getUVec2d 17 procedure, public :: getV => getVVec2d 18 end type Vec2d 19 20 ! Generic IFACE, for type-overloading 21 ! (to implement user-defined CTOR) 22 interface Vec2d 23 module procedure createVec2d 24 end interface Vec2d 25 26 contains 27 type(Vec2d) function createVec2d( u, v ) ! CTOR 28 real, intent(in) :: u, v 29 createVec2d%mU = u 30 createVec2d%mV = v 31 end function createVec2d 32 33 real function getMagnitudeVec2d( this ) result(mag) 34 class(Vec2d), intent(in) :: this 35 mag = sqrt( this%mU**2 + this%mV**2 ) 36 end function getMagnitudeVec2d 37 38 real function getUVec2d( this ) ! Accessor-method (GETter). 39 class(Vec2d), intent(in) :: this 40 getUVec2d = this%mU ! Direct-access IS allowed here. 41 end function getUVec2d 42 43 real function getVVec2d( this ) ! Accessor-method (GETter). 44 class(Vec2d), intent(in) :: this 45 getVVec2d = this%mV ! Direct-access IS allowed here. 46 end function getVVec2d 47 end module Vec2d_class48 49 module Vec3d_class 50 use Vec2d_class 51 implicit none 52 private 53 54 type, public, extends(Vec2d) :: Vec3d 55 private 56 real :: mW = 0. 57 contains 58 private 59 procedure, public :: getW => getWVec3d 60 procedure, public :: getMagnitude => getMagnitudeVec3d61 end type Vec3d 62 63 interface Vec3d 64 module procedure createVec3d 65 end interface Vec3d 66 67 contains 68 ! 子類的自定義建構函式 Custom CTOR for the child-type. 69 type(Vec3d) function createVec3d( u, v, w ) 70 real, intent(in) :: u, v, w 71 createVec3d%Vec2d = Vec2d( u, v) ! Call CTOR of parent. 72 createVec3d%mW = w 73 end function createVec3d 74 75 ! 覆蓋父類方法 Override method of parent-type. 76 ! (to compute magnitude, considering 'w' too) 77 real function getMagnitudeVec3d( this ) result(mag) 78 class(Vec3d), intent(in) :: this 79 ! this%Vec2d%getU() is equivalent, here, with this%getU() 80 mag = sqrt( this%Vec2d%getU()**2 + this%getV()**2 + this%mW**2 ) 81 end function getMagnitudeVec3d 82 83 ! 專屬於子類的方法 Method specific to the child-type. 84 ! (GETter for new component). 85 real function getWVec3d( this ) 86 class(Vec3d), intent(in) :: this 87 getWVec3d = this%mW 88 end function getWVec3d 89 end module Vec3d_class 90 91 program test_driver_inheritance 92 use Vec3d_class 93 implicit none 94 type(Vec3d) :: X 95 96 X = Vec3d( 1.0, 2.0, 3.0 ) 97 write(*, '(4(a,f6.3))') "X%U = ", X%getU(), ", X%V = ", X%getV(), & 98 ", X%W = ", X%getW(), ", X%magnitude = ", X%getMagnitude() 99 end program test_driver_inheritance
Listing 3.34 src/Chapter3/dt_composition_inheritance.f90 (excerpt)
這是關於繼承的幾個值得注意的點:
- 父型別需要用 extends(<ParentTypeName>) 說明符來表示(第54行)。
- 繼承會自動為子型別提供父型別的成員,以父型別命名。在我們的例子中,通過呼叫父級的自定義建構函式就可以清楚地看到這一點(第71行)。我們使用%來獲得這個分量。因此,createVec3d%Vec2d(第71行)和this%Vec2d(第80行)本身就是Vec2d型別的物件。子型別還可以直接訪問父類的公共資料和方法(在我們的例子中,沒有public的資料,但我們在第80行通過繼承的方法getU和getV可以訪問資料)。
- 可以覆蓋(override)父物件的方法(在子型別中)。我們使用它來定義getMagnitude的新版本(第75-81行),它正確地考慮了額外的分量w。但是,請注意,在覆蓋時,方法的介面需要保持不變(除了傳遞物件的虛參的型別,它顯然需要不同)。例如,我們不允許用一個函式重寫getMagnitude,該函式接受兩個引數而不是一個引數(假設我們需要)。
新的派生型別可以被呼叫,如下:
91 program test_driver_inheritance 92 use Vec3d_class 93 implicit none 94 type(Vec3d) :: X 95 96 X = Vec3d( 1.0, 2.0, 3.0 ) 97 write(*, '(4(a,f6.3))') "X%U = ", X%getU(), ", X%V = ", X%getV(), & 98 ", X%W = ", X%getW(), ", X%magnitude = ", X%getMagnitude() 99 end program test_driver_inheritance
Listing 3.34 src/Chapter3/dt_composition_inheritance.f90 (excerpt)
在結束我們對繼承的介紹時,請注意,在Fortran術語中,class關鍵字表示“型別的類(class of types)”(或繼承層次結構(inheritance hierarchy))。這與其他OOP語言不同,“class”表示資料type(Fortran中的type)。此外,與其他語言不同,Fortran不允許多重繼承(譯者注:從多個父類繼承?)(Metcalf等人. [Metcalf, M., Reid, J., Cohen, M.: Modern Fortran Explained. Oxford University Press, Oxford (2011)])。
聚合
實現型別層次結構的第二種機制是聚合(aggregation),它對型別之間的“has-A”關係進行建模,這對一些程式設計師來說可能更自然。我們還可以使用這種方法實現Vec3d類的另一個版本:
6 module Vec2d_class 7 implicit none 8 private 9 10 type, public :: Vec2d ! DT explicitly declared "public" 11 private ! Make internal data "private" by default. 12 real :: mU = 0., mV = 0. 13 contains 14 private ! Make methods "private" by default. 15 procedure, public :: getMagnitude => getMagnitudeVec2d 16 procedure, public :: getU => getUVec2d 17 procedure, public :: getV => getVVec2d 18 end type Vec2d 19 20 ! Generic IFACE, for type-overloading 21 ! (to implement user-defined CTOR) 22 interface Vec2d 23 module procedure createVec2d 24 end interface Vec2d 25 26 contains 27 type(Vec2d) function createVec2d( u, v ) ! CTOR 28 real, intent(in) :: u, v 29 createVec2d%mU = u 30 createVec2d%mV = v 31 end function createVec2d 32 33 real function getMagnitudeVec2d( this ) ! Method to compute magnitude. 34 class(Vec2d), intent(in) :: this 35 getMagnitudeVec2d = sqrt( this%mU**2 + this%mV**2 ) 36 end function getMagnitudeVec2d 37 38 real function getUVec2d( this ) ! Accessor-method (GETter). 39 class(Vec2d), intent(in) :: this 40 getUVec2d = this%mU ! Direct-access IS allowed here. 41 end function getUVec2d 42 43 real function getVVec2d( this ) ! Accessor-method (GETter). 44 class(Vec2d), intent(in) :: this 45 getVVec2d = this%mV ! Direct-access IS allowed here. 46 end function getVVec2d 47 end module Vec2d_class 48 49 module Vec3d_class 50 use Vec2d_class 51 implicit none 52 private 53 54 type, public :: Vec3d 55 private 56 type(Vec2d) :: mVec2d ! DT-aggregation 57 real :: mW = 0. 58 contains 59 private 60 procedure, public :: getU => getUVec3d 61 procedure, public :: getV => getVVec3d 62 procedure, public :: getW => getWVec3d 63 procedure, public :: getMagnitude => getMagnitudeVec3d 64 end type Vec3d 65 66 interface Vec3d 67 module procedure createVec3d 68 end interface Vec3d 69 70 contains 71 ! 對於聚合類的使用者建構函式 Custom CTOR for the aggregate-type. 72 type(Vec3d) function createVec3d( u, v, w ) 73 real, intent(in) :: u, v, w 74 createVec3d%mVec2d = Vec2d( u, v ) ! 呼叫分量建構函式 Call CTOR of component. 75 createVec3d%mW = w 76 end function createVec3d 77 78 real function getMagnitudeVec3d( this ) 79 class(Vec3d), intent(in) :: this 80 getMagnitudeVec3d = sqrt( this%getU()**2 + this%getV()**2 + this%mW**2 ) 81 end function getMagnitudeVec3d 82 83 real function getUVec3d( this ) 84 class(Vec3d), intent(in) :: this 85 getUVec3d = this%mVec2d%getU() ! 引用分量的方法 forward work to component 86 end function getUVec3d 87 88 real function getVVec3d( this ) 89 class(Vec3d), intent(in) :: this 90 getVVec3d = this%mVec2d%getV() ! 引用分量的方法 forward work to component 91 end function getVVec3d 92 93 real function getWVec3d( this ) 94 class(Vec3d), intent(in) :: this 95 getWVec3d = this%mW 96 end function getWVec3d 97 end module Vec3d_class 98 99 program test_driver_aggregation 100 use Vec3d_class 101 implicit none 102 type(Vec3d) :: X 103 104 X = Vec3d( 1.0, 2.0, 3.0 ) 105 write(*, '(4(a,f6.3))') "X%U = ", X%getU(), ", X%V = ", X%getV(), & 106 ", X%W = ", X%getW(), ", X%magnitude = ", X%getMagnitude() 107 end program test_driver_aggregation
Listing 3.36 src/Chapter3/dt_composition_aggregation.f90 (excerpt)
這只是簡單地使用不太複雜的型別作為分量(第56行)。通常的訪問控制機制指出,在Vec3d的實現中可以引用Vec2d的資料和方法(除了現在我們需要用分量名mVec2d來獲得訪問許可權)。由於該實現沒有其他顯著的特性,我們在這裡省略了對這些方法的討論。
使用繼承還是聚合
細心的讀者可能會注意到,派生型別之間“is a”和“has a”關係的區別有時可能是主觀的。實際上,按照我們前面的示例,基於這兩種方法,相同型別的Vec3d使用相同的功能實現。這可能會使在實踐中在兩者之間進行選擇變得混亂。一個粗略的經驗法則是,如果問題中存在明顯的型別層次結構,則使用繼承,這將使子級對父方法的直接繼承有益(無需重新實現它們,或定義“包裝器方法(wrapper methods)”)。然而,如果子類經常需要重寫父類的方法(或者更糟的是,如果父類的方法對子類沒有意義!),聚合是首選的合成方法(見Rouson等人 [Rouson, D., Xia, J., Xu, X.: Scientific Software Design: The Object-OrientedWay. Cambridge University Press, Cambridge (2011) ])。