我整个下午都在努力解决这个问题,所以我希望有人能帮我。
我有一个名为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“,然后将所需的数据传递给每个模型。但是,在编译时不知道具体的模型,因此,“控制”对象不知道要获取什么数据,实际上,模型不知道要接收什么数据!
例如,如果I allocate(model1::m1) (即将m1分配为model1类型),那么它将包含两位数据double precision :: x,y。然后,如果m2被分配为model2 (allocate(model2::m2))类型,那么它可能需要x,但是如果它被分配为model3 (allocate(model3::m2))类型,那么它可能需要来自m1的y。因此,既然“控制”对象无法知道分配了什么类型的m2,那么它如何从m1获得必要的数据以传递给m2。
另一个复杂的问题是,模型之间的相互作用通常是圆形的。也就是说,m1需要来自m2的数据,m2需要来自m1的数据等等。此外,所需的数据一般不仅是特定于模型,而且在类型和数量上都是可变的。
不幸的是,数据x和y不是base_model的成员,因此,将m1作为参数传递给m2也是行不通的。
因此,我有以下问题:
在Python这样的高级语言中,这很容易,因为我们可以简单地创建一个新的数据类型作为模型的组合,但据我所知,这在Fortran中是不可能的。
提前谢谢。任何帮助都是非常感谢的。
编辑:在下面与弗朗西夫特讨论之后,select type是一种选择。实际上,在上面给出的简单示例中,select type将是一个不错的选择。然而,在我的实际代码中,这将导致大量嵌套的select type,因此如果有一种不使用select type的方法,我会更喜欢它。感谢弗朗西夫斯指出我在select type上的错误。
发布于 2018-03-05 07:37:18
来回答你的两个问题:
有更好的设计方法吗?
我不太明白为什么你的设计中有这么多违禁品,但简而言之,是的。您可以为模型使用上下文管理器。我建议您查看以下答案: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解释
简而言之,我们创建了一个上下文类,它有一个可以在不同模型之间共享的字典。此实现非常特定于您提供的原始数据类型(即x、y、z)。如果您需要在数据跨模型共享之前计算数据,则仍然可以使用此模式,将solveMe()的返回替换为延迟承诺。
发布于 2018-03-05 09:26:41
下面是基于键/值(*)访问另一个对象的字段的类似尝试。为了简单起见,主程序从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 modulechild.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 modulemain.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汇编和结果:
$ 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()关键字(在parent_t类型中)重载不同类型的value (例如set_int()和set_real()),并在扩展类型中重写它们。数组类型的value也是如此,也许.
如果扩展类型之间的数据传输(副本)很昂贵(例如,大数组),我猜getter可以返回指向它们的指针,以便childX_t的数据可以以一种紧密耦合的方式进行通信(而不知道它们的实现)。
(但我想可能有一种比做上述事情更简单的方法,包括问题中的第1点(例如,程序本身的重新设计)。此外,如果我们手头有一些字典类型,字典的使用(如在the other answer中)似乎对我更有吸引力。
https://stackoverflow.com/questions/48933614
复制相似问题