1. 程式人生 > 其它 >Fortran 筆記之 繼承和聚合

Fortran 筆記之 繼承和聚合

繼承(類擴充套件)和聚合

參考自Introduction to Modern Fortran for the Earth System Sciences

我們在3.3部分的開頭提到過,OOP正規化通常會導致型別的層次結構。Fortran程式設計師可以使用兩種機制來構造這些層次結構:繼承和聚合。我們將在本節中簡要討論這些問題。

作為一個簡單的展示示例,我們將瞭解如何從上一節(Vec2d)擴充套件DT,以表示3D向量。

繼承

實現型別層次結構的第一種機制是繼承(inheritance)(在Fortran標準中稱為“型別擴充套件”)。這通過表達由型別建模的實體之間的“是”型別關係來實現程式碼重用。例如,在ESS中的植被模型中,我們可以定義一種型別的plant,以收集與所有plant型別的模型相關的屬性(例如反照率)。然後可以為tree, grass等實現專門的型別,它們繼承

了植物的基本特徵,但也添加了一些自己的特徵(使用Fortran術語,我們說tree擴充套件了plant)。我們還說,plant是tree的parent/ancestor型別(或者,相當於,該tree是植物的chlid/descendant)。當然,這種專門化過程可以繼續下去(通過為不同種類的樹木建立子類等),儘管建議不要讓層次結構太“高”(即有太多的繼承層次)。

回到我們的簡單例子,我們使用繼承來定義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_class
48 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 => getMagnitudeVec3d
61 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) ])。