完全隐藏用户的对象是否是好的设计?

时间:2016-01-26 19:05:16

标签: oop fortran fortran90 fortran2003

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

program test
use Timer
call Tic("timername")
! some heavy stuff
call Tac("timername")
end program test

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

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

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

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

虽然这两个选项在技术上有效(我已经实现了两个),但每个选项都有优点和注意事项,而且我对软件设计的规则有些碰撞。我想知道哪种是从“正统”观点来看最好的方法。

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

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

有这两个选项,我应该采用哪种方法来产生最正统的方法?

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

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分: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等)。

答案 2 :(得分: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内容的唯一方法是通过模块contain中的例程SomeModule编辑。

我有一个更具体的例子here