继承(类扩展)和聚合
参考自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(<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() ! 直接使用对象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) ])。