【问题标题】:Is it good design to completely hide objects from the user?对用户完全隐藏对象是一个好的设计吗?
【发布时间】:2016-01-26 19:05:15
【问题描述】:

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

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

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

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

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

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

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

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

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

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

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

【问题讨论】:

  • 始终使用标签fortran。如果合适,使用版本标签指定特定版本。请注意,Fortran 90 中没有真正的 OOP,只有一些基本技术是可能的。 True OOP 仅随 Fortran 2003 提供。您确定要将答案限制为仅 Fortran 90 (25 年的语言,比这里的许多人还老!)。
  • 您可以将您的模块变量 (b) 放入公共数据类型并传递它。这将允许您拥有多个此类对象,同时保持在其中轻松创建计时器的可能性。这会将负担从创建单个时序对象转移到仅创建一次时序上下文。在这种情况下,您仍然可以使用计时上下文对象的模块变量并将其用作例程中的默认值。
  • @VladimirF 90 和 2003 之间的差异在继承或多态等更高级的特性中变得明显。在这个小模块中,我认为 90 和 2003 没有区别,而让我提出问题的疑惑独立于这个细节层面。因此,我将编辑问题,不要将其限制为 fortran 90。感谢您的提示。
  • @haraldkl 如果我理解你,你描述的场景是 a)。现在我的问题是使用场景 b) 是否更可取,其中模块被更强烈地封装,用户甚至不需要知道 Timer 对象的存在。他/她可以按需创建新的计时器,而无需在程序开始时明确定义它们。从用户的角度来看,选项 b) 更好,但对于使用全局变量可能不是很正统。两者都在技术上工作,但一般来说哪个是最好的设计?这是我的问题。

标签: oop fortran fortran90 fortran2003


【解决方案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 和其他)。

【讨论】:

  • 好的,现在我意识到我正在做的是将模块用作对象。与您所说的细微不同的是,我的模块实际上包含一个动态的计时器数组,这些计时器是通过调用 Tic 而设置的,而不仅仅是一个计时器。好的,所以我想将状态与功能分开是“正统”的方法。那我就去吧,确实我会使用 Fortran 2003 版本。谢谢。
【解决方案2】:

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

【讨论】:

  • 这些书籍是严重依赖 Fortran 2003 的技术示例。
  • 我删除了关于“科学软件设计”的信息(因为它不在F90内)。我希望有人给出更有用的答案,以便我们了解选择(实际上,我也在尝试制作类似的 Timer 东西......)
  • 我认为没有必要删除它。
  • @VladimirF 好的,那么这里是link。 AFAIK 这本书经常被推荐,所以它可能值得一看(我买了它,但还没有读完,所以不能确定内容......)
  • @Onturenio 嗨,我所说的非类型绑定过程是指像“call Tic(str)”这样的子例程,即类似于您的示例代码中使用的那些。我们可以按照 Vlad 的回答(F03 版本)中的说明创建通常的 OO 内容,但如果用户不想声明对象,这不是很方便。在这种情况下,我们可以提供像 Tic() 这样的非类型绑定服务例程来处理模块内的所有事情(包括对象创建),这样用户就不需要声明 Timer 对象。但付出的代价是实施变得更加繁琐。
【解决方案3】:

是的,数据封装和隐藏被认为是软件设计中的良好做法。我们可以在 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 内容的唯一方法是通过模块SomeModule 中的例程contain-ed。

我有一个更具体的例子here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多