Oop 对用户完全隐藏对象是好的设计吗?
我正在用Fortran 90/2003编写一个简短的模块,它提供了一个简单且用户友好的界面,用于计算程序执行不同部分之间的时间。受Matlab中的Oop 对用户完全隐藏对象是好的设计吗?,oop,fortran,fortran90,fortran2003,Oop,Fortran,Fortran90,Fortran2003,我正在用Fortran 90/2003编写一个简短的模块,它提供了一个简单且用户友好的界面,用于计算程序执行不同部分之间的时间。受Matlab中的tic,tac命令的启发,用户在程序中使用模块的想法如下: program test use Timer call Tic("timername") ! some heavy stuff call Tac("timername") end program test 程序测试 使用定时器 呼叫Tic(“timername”) ! 一些重东西 呼叫Tac(
tic
,tac
命令的启发,用户在程序中使用模块的想法如下:
program test
use Timer
call Tic("timername")
! some heavy stuff
call Tac("timername")
end program test
程序测试
使用定时器
呼叫Tic(“timername”)
! 一些重东西
呼叫Tac(“timername”)
结束程序测试
现在,我知道如何使用Fortran内部函数实现这个结果。我的问题是我应该怎么做。我这样做是为了学习良好的设计实践,而不是Fortran语法
我定义了一个名为Timer
的用户定义变量,它是我用来实现功能的主要对象。但是,有(至少)两种不同的方法可以使用此对象让用户使用多个计时器:
a) 我可以将用户定义的变量Timer
public,然后强制用户手动创建timers对象。用户必须根据需要创建尽可能多的计时器,然后使用方法来处理它们
b) 我可以通过将其设置为私有来隐藏此类型。然后,为了存储不同的计时器,我在模块中创建了一个Timer
对象数组作为全局变量,尽管对于模块来说是私有的,并且每次用户调用子例程Tic
,都会在此数组中定义一个新的计时器。在上面的示例中,用户使用的是按照后一种方法实现的模块(请注意,程序根本不使用关键字type
)
虽然这两个选项在技术上是可行的(我已经实现了这两个选项),但每一个选项都有优点和注意事项,而且我所知道的关于软件设计的规则在某种程度上是相互冲突的。我想知道从“正统”的角度来看,哪一个是最好的方法
选项a)具有更好地遵循OOP的优点:用户显式地创建对象并使用它进行操作。它不使用任何全局变量
备选方案b)的优点是“封装性”更强。我的意思是,用户甚至不需要知道什么是计时器,甚至不需要知道它的存在。此外,为与定时器
对象交互而提供的接口只是一个简单的字符串,这使得整个模块对于用户来说更加不透明,用户不需要特意定义定时器
变量。他/她只使用模块提供的两个子例程,接受字符串作为输入。这就是全部。问题是,我觉得这种基于为整个模块定义的数组的设计违背了避免全局变量的规则。它不是一个真正的全局变量,因为它是私有的,但仍然是
有了这两个选择,我应该选择哪一个来产生最正统的方法
PS:可能还有第三个选项,允许用户间接创建对象,而无需访问用户定义的类型(即,不只是像解决方案b中那样定义现有数组中的元素)。我不知道是否可以在运行时创建变量。这方面的任何想法都是非常受欢迎的。虽然我对OOP知之甚少,但我想没有什么比“最正统的方法”(因为Fortran允许OOP,但不强制执行)。选择似乎还取决于是否需要使用相同的字符串创建多个计时器实例(例如,并行运行?)。在这种情况下,选项(a)可能更为方便,而选项(b)似乎更为方便。通过允许用户显式创建计时器对象,同时提供方便的tic()/toc()例程,在模块内自动创建/操作必要的对象,也可以合并这两种方法。通常,一个好的设计是向用户隐藏实现的细节。这是封装
这意味着您有一些“对象”,您不公开其内部状态的详细信息,而只公开如何使用此类对象的一些方法
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 Timers, only: Timer
type(Timer) :: t
call t%tic()
! some heavy stuff
call t%tac()
您可以看到,最现代的方法肯定会向用户公开计时器类型
当您公开类型时,您可以将组件设置为私有的,并且只使用构造函数和其他与mani相关联的过程(可选的类型绑定)
module SomeModule
implicit none
private
public :: SomeType
type SomeType
private
integer :: n
real :: x
end type SomeType
end module SomeModule
use SomeModule, only: SomeType
type(SomeType) :: st