整个下午,我一直在撞墙,试图弄清楚这个问题,所以我希望有人可以帮助我。
我有一个名为base_model
的抽象基类(比如说),它在Fortran2003中看起来像:
type, abstract :: base_model
contains
procedure(initMe), pass(this), deferred :: init ! constructor
procedure(delMe), pass(this), deferred :: delete ! destructor
procedure(solveMe), pass(this), deferred :: solve
end type base_model
显然,抽象过程initMe
,delMe
和solveMe
是使用抽象接口块定义的。然后我有三个派生类,称为model1
,model2
和model3
(比如说):
type, extends(base_model) :: model1
double precision :: x,y
contains
procedure :: init => init_model1
procedure :: delete => delete_model1
procedure :: solve => solve_model1
end type model1
type, extends(base_model) :: model2
contains
procedure :: init => init_model2
procedure :: delete => delete_model2
procedure :: solve => solve_model2
end type model2
type, extends(base_model) :: model3
contains
procedure :: init => init_model3
procedure :: delete => delete_model3
procedure :: solve => solve_model3
end type model3
然后我有一个名为control
的“控制”对象(比如说),它扩展了一个抽象的base_control
:
type, abstract :: base_control
class(base_model), allocatable :: m1
class(base_model), allocatable :: m2
class(base_model), allocatable :: m3
contains
procedure(initMe), pass(self), deferred :: init
procedure(delMe), pass(self), deferred :: delete
procedure(runMe), pass(self), deferred :: run
end type base_control
type, extends(base_control) :: control
contains
procedure :: init => init_control
procedure :: delete => delete_control
procedure :: run => run_control
end type control
可以将对象m1
,m2
和m3
分配到任何模型中:model1
,model2
或model3
,以及根据用户请求的“控制”,以任何特定顺序“解决”。
三个可分配对象(m1
,m2
和m3
)需要在它们之间传递数据。鉴于它们是“控制”对象的成员,我可以为每个模型定义一个“getter”,然后将所需的数据传递到每个模型中。但是,特定模型在编译时是未知的,因此,“控制”对象不知道要获取什么数据,实际上,模型不知道要接收哪些数据!
例如,如果我allocate(model1::m1)
(即,将m1分配为类型model1
),则它将包含两位数据double precision :: x,y
。然后,如果m2
被分配为类型model2
(allocate(model2::m2)
),则可能需要x
但是如果它被分配为类型model3
({ {1}})然后allocate(model3::m2)
可能需要y
。因此,鉴于“控制”对象无法知道分配的m1
类型,如何从m2
获取必要的数据以传递到m1
?
另一个复杂因素是模型之间的相互作用通常是循环的。也就是说,m2
需要来自m1
的数据,m2
需要来自m2
的数据等等。此外,所需数据通常不仅特定于模型,而且在类型和数量上也是可变的。
不幸的是,数据m1
和x
不是y
的成员,因此,将base_model
作为参数传递给m1
也不会有效。
所以我有以下问题:
有没有更好的方法来设计这些对象,以便我可以轻松地在它们之间传递数据?在这里展望,有一些建议,最好的办法是重新设计对象,使它们之间的交互不是循环的。但是,这是必要的!
我是否必须为每个可能在对象之间共享的数据写一个“getter”?这似乎是很多编码(我有很多可能共享的数据)。然而,这似乎也相当复杂,因为“getter”(特定于一段数据)也必须满足抽象接口。
在像Python这样的高级语言中,这很简单,因为我们可以简单地创建一个新的数据类型作为模型的组合,但据我所知,这在Fortran中是不可能的。
提前致谢。非常感谢任何帮助。
编辑:在下面与francescalus讨论后,m2
是一个选项。实际上,在上面给出的简单示例中,select type
将是一个不错的选择。但是,在我的实际代码中,这将导致大型嵌套select type
,所以如果有办法在不使用select type
的情况下执行此操作,我会更喜欢它。感谢francescalus指出我关于select type
的错误。
答案 0 :(得分:3)
回答你的两个问题:
有更好的设计方法吗?
我不太清楚为什么你的设计中有这么多的约束,但简而言之是。您可以为模型使用上下文管理器。我建议您查看以下答案:Context class pattern
您是否必须在每个型号上编写一个getter方法?
不完全是,如果您在此特定问题上使用上下文策略,那么您需要在每个模型上实现的唯一方法是在模型之间共享数据的setter方法。
我在Python中为这个场景实现了一个有效的解决方案。代码比言辞更响亮。我避免使用Python的任何特殊功能,让您清楚地了解如何在这种情况下使用上下文。
from abc import ABC, abstractmethod
import random
class BaseModel(ABC):
def __init__(self, ctx):
super().__init__()
self.ctx = ctx
print("BaseModel initializing with context id:", ctx.getId())
@abstractmethod
def solveMe():
pass
class BaseControl(object):
# m1 - m3 could be replaced here with *args
def __init__(self, m1, m2, m3):
super().__init__()
self.models = [m1, m2, m3]
class Control(BaseControl):
def __init__(self, m1, m2, m3):
super().__init__(m1, m2, m3)
def run(self):
print("Now Solving..")
for m in self.models:
print("Class: {} reports value: {}".format(type(m).__name__, m.solveMe()))
class Model1(BaseModel):
def __init__(self, x, y, ctx):
super().__init__(ctx)
self.x = x
self.y = y
ctx.setVal("x", x)
ctx.setVal("y", y)
def solveMe(self):
return self.x * self.y
class Model2(BaseModel):
def __init__(self, z, ctx):
super().__init__(ctx)
self.z = z
ctx.setVal("z", z)
def solveMe(self):
return self.z * self.ctx.getVal("x")
class Model3(BaseModel):
def __init__(self, z, ctx):
super().__init__(ctx)
self.z = z
ctx.setVal("z", z)
def solveMe(self):
return self.z * self.ctx.getVal("y")
class Context(object):
def __init__(self):
self.modelData = {}
self.ctxId = random.getrandbits(32)
def getVal(self, key):
return self.modelData[key]
def setVal(self, key, val):
self.modelData[key] = val
def getId(self):
return self.ctxId
ctx = Context()
m1 = Model1(1,2, ctx)
m2 = Model2(4, ctx)
m3 = Model3(6, ctx)
# note that the order in the arguments to control defines behavior
control = Control(m1, m2, m3)
control.run()
输出
python context.py
BaseModel initializing with context id: 1236512420
BaseModel initializing with context id: 1236512420
BaseModel initializing with context id: 1236512420
Now Solving..
Class: Model1 reports value: 2
Class: Model2 reports value: 4
Class: Model3 reports value: 12
<强>解释强>
简而言之,我们创建了一个上下文类,它具有可以在不同模型之间共享的字典。此实现非常特定于您提供的原始数据类型(I.E. x,y,z)。如果您需要在模型之间共享数据之前计算数据,您仍然可以通过将solveMe()
的返回值替换为延迟的承诺来使用此模式。
答案 1 :(得分:2)
FWIW,下面是基于键/值(*)访问其他对象的字段的类似尝试。为简单起见,主程序从child1_t
对象获取一个整数,并将复数值设置为child2_t
对象(两者都是parent_t
的扩展类型)。
parent.f90:
module parent_m
implicit none
type, abstract :: parent_t !(abstract is optional)
contains
procedure :: set
procedure :: get
procedure :: show
endtype
type composite_t
class(parent_t), allocatable :: pa, pb
endtype
contains
subroutine set( this, key, val ) ! key-based setter
class(parent_t), intent(inout) :: this
character(*), intent(in) :: key
integer, intent(in) :: val
endsubroutine
subroutine get( this, key, val ) ! key-based getter
class(parent_t), intent(in) :: this
character(*), intent(in) :: key
integer, intent(out) :: val
endsubroutine
subroutine show( this ) ! print contents
class(parent_t), intent(in) :: this
endsubroutine
end module
child.f90:
module child_m
use parent_m, only: parent_t
implicit none
type, extends(parent_t) :: child1_t
integer :: n1 = 777 ! some property
contains
procedure :: get => child1_get
procedure :: show => child1_show
endtype
type, extends(parent_t) :: child2_t
complex :: c2 = ( 0.0, 0.0 ) ! another property
contains
procedure :: set => child2_set
procedure :: show => child2_show
endtype
contains
subroutine child1_get( this, key, val )
class(child1_t), intent(in) :: this
character(*), intent(in) :: key
integer, intent(out) :: val
select case( key )
case ( "num", "n1" ) ; val = this % n1 ! get n1
case default ; stop "invalid key"
end select
end subroutine
subroutine child1_show( this )
class(child1_t), intent(in) :: this
print *, "[child1] ", this % n1
endsubroutine
subroutine child2_set( this, key, val )
class(child2_t), intent(inout) :: this
character(*), intent(in) :: key
integer, intent(in) :: val
select case( key )
case ( "coeff", "c2" ) ; this % c2 = cmplx( real(val), 0.0 ) ! set c2
case default ; stop "invalid key"
end select
end subroutine
subroutine child2_show( this )
class(child2_t), intent(in) :: this
print *, "[child2] ", this % c2
endsubroutine
end module
main.f90时:
program main
use parent_m, only: composite_t
use child_m, only: child1_t, child2_t
implicit none
type(composite_t) :: c
integer itmp
allocate( child1_t :: c % pa )
allocate( child2_t :: c % pb )
print *, "initial state:"
call c % pa % show()
call c % pb % show()
call c % pa % get( "num", itmp ) ! get an integer value from pa
call c % pb % set( "coeff", itmp ) ! set a complex value to pb
print *, "modified state:"
call c % pa % show()
call c % pb % show()
end
编译&amp;结果:
$ gfortran parent.f90 child.f90 main.f90
initial state:
[child1] 777
[child2] (0.00000000,0.00000000)
modified state:
[child1] 777
[child2] (777.000000,0.00000000)
虽然上面的代码只处理整数和复数的“数据传输”,但是可以在select case
构造中类似地添加其他类型的数据(不为每个添加新的getter / setter方法)。此外,如有必要,我们可以通过get()
关键字为不同类型的set()
(例如value
和set_int()
)重载set_real()
和generic
(在parent_t
类型中),并在扩展类型中覆盖它们。同样也适用于数组类型的value
,也许......
如果扩展类型之间的数据传输(复制)很昂贵(例如,大型数组),我想getter可以返回指向它们的指针,以便childX_t
可以以紧密耦合的方式进行通信(没有知道他们的实施。)
(但我想可能有一个比上面做的更简单的方法,包括问题中的第1点(例如,重新设计程序本身)。另外,如果我们手头有一些字典类型,字典的使用(如the other answer)似乎对我更有吸引力。)