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        ! 读取w分量的方法
60      procedure, public :: getMagnitude => getMagnitudeVec3d !此处覆盖了Vec2d中的同名方法
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    ! 将方法与Vec3d类型绑定
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     ! 将方法与Vec3d类型绑定
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() 说明符来表示(第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() ! 直接使用对象mVec2d的方法来获取U
 86   end function getUVec3d
 87 
 88   real function getVVec3d( this )
 89     class(Vec3d), intent(in) :: this
 90     getVVec3d = this%mVec2d%getV() ! 直接使用对象mVec2d的方法来获取V
 91   end function getVVec3d
 92 
 93   real function getWVec3d( this )
 94     class(Vec3d), intent(in) :: this
 95     getWVec3d = this%mW            ! 定义获取w分量的方法
 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) ])。