1

我正在用 Fortran 90/2003 编写一个简短的模块,它提供了一个简单且用户友好的界面,用于计算程序执行的不同部分之间的时间。受 Matlab 中的tic,tac​​ 命令的启发,其想法是用户在程序中使用该模块,如下所示:

程序测试
使用定时器
调用 Tic("timername")
!一些沉重的东西
调用 Tac("timername")
结束程序测试

现在,我知道如何使用Fortran 内在函数实现该结果。我的问题是我应该怎么做。我这样做是为了学习好的设计实践,而不是 Fortran 语法。

我已经定义了一个名为 的用户定义变量Timer,它是我用来实现该功能的主要对象。然而,有(至少)两种不同的方式来使用这个对象让用户使用多个计时器:

a)我可以将用户定义的变量Timer公开,然后强制用户手动创建计时器对象。用户必须根据需要创建尽可能多的计时器,然后使用方法来处理它们。

b)我可以通过将其设为私有来隐藏此类型。然后,为了存储不同的计时器,我在模块中创建了一个对象数组Timer作为全局变量,虽然对于模块来说是私有的,并且每次用户调用子例程Tic时,都会在这个数组中定义一个新的计时器。在上面的示例中,用户正在使用按照后一种方法实现的模块(请注意,该程序根本不使用关键字type)。

尽管这两个选项在技术上都有效(我已经实现了),但每个选项都有优点和警告,而且我所知道的关于软件设计的规则不知何故发生了冲突。从“正统”的角度来看,我想知道哪种方法最好。

选项 a) 具有更好地遵循 OOP 的优点:用户显式创建对象并使用它进行操作。它不使用任何全局变量。

选项 b) 的优点是被更强烈地“封装”。我的意思是用户甚至不需要知道 aTimer是什么,甚至不需要知道它的存在。此外,提供的与Timer对象交互的接口只是一个简单的字符串,使得整个模块对用户来说更加不透明,不需要特意定义Timer变量。他/她只使用模块提供的两个接受字符串作为输入的子例程。就这样。问题是我觉得这种基于为整个模块定义的数组的设计违背了避免全局变量的规则。它不是真正的全局变量,因为它是私有的,但仍然如此。

那么有这两种选择,我应该选择哪一种来产生最正统的方法?

PS:也许还有第三种选择,它允许用户间接创建对象,而无需访问用户定义的类型(即不仅仅是在现有数组中定义元素,就像在解决方案 b 中所做的那样)。我不知道这是否可以在运行时创建变量。在这个方向上的任何想法也非常受欢迎。

4

3 回答 3

1

虽然我对 OOP 了解不多,但我想没有什么比得上“最正统的方法”(因为 Fortran 允许 OOP 但不强制执行)。选择似乎还取决于创建具有相同字符串的多个 Timer 实例的需要(例如,并行运行?)。在这种情况下,选项 (a) 可能更方便,而选项 (b) 似乎更方便。通过允许用户显式创建 Timer 对象,同时提供方便的 tic()/toc() 例程来自动创建/操作模块内的必要对象,也可以合并这两种方法。

于 2016-01-26T21:25:13.053 回答
1

通常,一个好的设计是向用户隐藏实现的细节。这是封装。

这意味着您有一些“对象”,您不会公开有关其内部状态的详细信息,而只会公开一些如何使用此类对象的方法。

1.模块作为对象

在 Fortran 90/95 中,OOP 受到了一些限制。一种方法是有一个模块就是这个“对象”。模块变量是内部状态,模块过程实现功能并使用模块的内部状态。在此设计中,如果不需要,您不会公开模块变量。问题是你总是可以只有一个对象的实例——模块。

这将是:

use Timers, only: Tic, Tac
call Tic()
! some heavy stuff
call Tac()

2. 派生类型作为对象

另一种经典方式是使用派生类型,其中包含其组件中的状态。在这种情况下,您可以拥有“对象”的多个实例 - 具有对象类型的多个变量。当您对对象执行任何操作时,您会从定义对象的模块中调用模块过程,并且始终将对象实例作为参数传递 - 通常作为第一个参数。

use Timers, only: Timer, Tic, Tac
type(Timer) :: t

call Tic(t)
! some heavy stuff
call Tac(t)

你的问题代码

use Timers, only: Tic, Tac

call Tic("timername")
! some heavy stuff
call Tac("timername")

或一些变化,如

use Timers, only: Tic, Tac

call Tic(1)
! some heavy stuff
call Tac(1)

功能相似,但很奇怪。为什么实现功能的模块也要存储状态?从更多地方使用此模块时不会发生冲突吗?我肯定会让用户自己创建实例。

3. Fortran 2003

在这个非常简单的示例中,如果您已经公开了类型,那么 Fortran 2003 不会发生太大变化。同样,状态位于派生类型的组件中。但是您可以将使用该类型的过程直接绑定到该类型,而不必单独导入它们。你只是use类型和每个功能,重载的运算符和类似的,都附带它:

use Timers, only: Timer
type(Timer) :: t

call t%tic()
! some heavy stuff
call t%tac()

您可以看到,最现代的方法肯定会Timer向用户公开类型。


当您公开类型时,您可以将组件设为私有并仅使用构造函数和其他相关过程(可选类型绑定)来操作它们(getter/setter 等)。

于 2016-01-27T11:49:35.237 回答
1

是的,数据封装和隐藏被认为是软件设计中的良好实践。我们可以在 Fortran 中通过创建派生类型来实现这一点,这样该类型的实例(对象)是不透明的。考虑以下模块

module SomeModule

  implicit none

  private
  public :: SomeType

  type SomeType
     private
     integer :: n
     real :: x
  end type SomeType

end module SomeModule

请注意,它SomeType被声明为,public而类型的内容是private. 现在,当我可以创建一个类型的对象时SomeType

use SomeModule, only: SomeType
type(SomeType) :: st

该对象st是不透明的 - 我可以创建它,传递它,但不能访问它的内容。我可以编辑其内容的唯一方法是通过模块中st的例程编辑。containSomeModule

这里有一个更具体的例子。

于 2016-01-27T16:06:11.240 回答