Хороший дизайн ООП, чтобы избежать копирования/вставки в Fortran

Учитывая приведенный ниже минимальный рабочий пример, я хотел бы изменить его, чтобы избежать копирования/вставки вызовов

call func_some_calc1(par)
call func_some_calc2(par)

как в main_func_problem1, так и в main_func_problem2. В идеале я хочу иметь одну функцию main_func, которая ведет себя по-разному для входных параметров типа t_parameters_problem1 и t_parameters_problem2. Я мог бы объявить его параметр par базового типа class(t_parameters_base), но тогда наличие переключателя внутри этой функции в зависимости от фактического типа аргумента (с использованием select type) архитектурно не очень хорошо.

Чтобы решить эту проблему, я попытался создать procedure в типе t_parameters_base, который вызывает эти подпрограммы, чтобы реализовать что-то вроде этого (синтаксис C++):

class t_parameters_base {
  virtual void main_func() {
    func_some_calc1(this)
    func_some_calc2(this)
  }
}
class t_parameters_problem1: public t_parameters_base {
  virtual void main_func() {
    t_parameters_base::main_func();
    func_some_calc3_problem1(this);
  }
}

Но проблема в том, что эти подпрограммы используют входной параметр этого типа, что приводит к циклической зависимости. Как можно решить эту проблему?

Обновление: обратите внимание, что я действительно хочу сохранить реализацию func_some_calc1 и func_some_calc2 в разных файлах (модуль/классы), поскольку они реализуют очень разную логику, используя некоторые частные функции из своих классов.

module parameters_base
  type, public :: t_parameters_base
    integer :: n
  end type t_parameters_base
end module parameters_base

module parameters_problem1
  use parameters_base
  implicit none

  type, extends(t_parameters_base), public :: t_parameters_problem1
    integer :: p1
  end type t_parameters_problem1
end module parameters_problem1

module parameters_problem2
  use parameters_base
  implicit none

  type, extends(t_parameters_base), public :: t_parameters_problem2
    integer :: p2
  end type t_parameters_problem2
end module parameters_problem2

module some_calc1
  use parameters_base
  implicit none
contains
  subroutine func_some_calc1(par)
    class(t_parameters_base) :: par
  end subroutine func_some_calc1
end module some_calc1

module some_calc2
  use parameters_base
  implicit none
contains
  subroutine func_some_calc2(par)
    class(t_parameters_base) :: par
  end subroutine func_some_calc2
end module some_calc2

module some_calc3_problem1
  use parameters_problem1
  implicit none
contains
  subroutine func_some_calc3_problem1(par)
    type(t_parameters_problem1) :: par
    print*, par%p1
  end subroutine func_some_calc3_problem1
end module some_calc3_problem1

module some_calc3_problem2
  use parameters_problem2
  implicit none
contains
  subroutine func_some_calc3_problem2(par)
    type(t_parameters_problem2) :: par
    print*, par%p2
  end subroutine func_some_calc3_problem2
end module some_calc3_problem2

module main_problem1
  use parameters_problem1
  use some_calc1
  use some_calc2
  use some_calc3_problem1
  implicit none
contains
  subroutine main_func_problem1(par)
    type(t_parameters_problem1) :: par

    call func_some_calc1(par)
    call func_some_calc2(par)
    call func_some_calc3_problem1(par)
  end subroutine main_func_problem1
end module main_problem1

module main_problem2
  use parameters_problem2
  use some_calc1
  use some_calc2
  use some_calc3_problem2
  implicit none
contains
  subroutine main_func_problem2(par)
    type(t_parameters_problem2) :: par

    call func_some_calc1(par)
    call func_some_calc2(par)
    call func_some_calc3_problem2(par)
  end subroutine main_func_problem2
end module main_problem2

program module_test
  use parameters_problem1
  use parameters_problem2
  use main_problem1
  use main_problem2

  implicit none

  type(t_parameters_problem1) :: par1
  type(t_parameters_problem2) :: par2

  par1%p1 = 1
  par2%p2 = 2

  call main_func_problem1(par1)
  call main_func_problem2(par2)
end program module_test

person Vitaliy    schedule 22.06.2015    source источник
comment
Почему бы вам не создать одну процедуру с переключателем. Я предлагаю вместо общих func_some_calc1, ... давать короткие конкретные имена, так все будет более понятно.   -  person Zeus    schedule 22.06.2015
comment
Какой именно переключатель вы имеете в виду? Если вы хотите объявить аргумент базового типа, а затем преобразовать его внутри подпрограммы в фактически переданный унаследованный тип, используя тип select, то это не очень хорошая архитектура.   -  person Vitaliy    schedule 22.06.2015
comment
Как насчет использования t_parameters и передачи дополнительного пока отдельно.   -  person Zeus    schedule 22.06.2015
comment
Да, я мог бы добавить еще одну функцию main_func_common, которая принимает переменную par t_parameters_base и вызывает общий блок, если вы это имеете в виду. Но тогда размещение этой функции в отдельном модуле кажется мне не очень хорошим стилем ООП.   -  person Vitaliy    schedule 22.06.2015
comment
Вы передаете его как второй аргумент. Предполагая, что у вас не так много дополнительных аргументов, все будет в порядке. Я предпочитаю иметь базовый тип и другие аргументы, передаваемые отдельно.   -  person Zeus    schedule 22.06.2015
comment
Нет, много лишних параметров (пока около 10, а может быть и больше).   -  person Vitaliy    schedule 22.06.2015
comment
Если дополнительные аргументы являются взаимоисключающими, для них можно создать два конкретных типа. Я внимательно изучаю вашу проблему и пока не нашел решения, хотя предлагаемое упрощение может оказаться более полезным.   -  person Zeus    schedule 22.06.2015
comment
Вы запрашиваете переключение между процедурами во время выполнения в зависимости от фактического типа некоторого объекта. Это можно сделать только с помощью двух конструкций: либо с помощью предложения SELECT TYPE, либо путем привязки процедуры к типу объекта.   -  person Zeus    schedule 22.06.2015
comment
Последнее решение предполагает привязку процедур func_some_calc к (абстрактному) базовому типу t_parameters_base в форме отложенной процедуры с абстрактным интерфейсом. Это должно сработать, но это как бы подразумевает, что подпрограммы вторичны по отношению к объекту параметра.   -  person Zeus    schedule 22.06.2015
comment
У вас есть два основных варианта. Выберите тип внутри или спроектируйте все как связанные с типом процедуры данного типа.   -  person Vladimir F    schedule 22.06.2015


Ответы (1)


Я думаю, что вы стремитесь к полиморфным типам с процедурами, связанными с типом. Ниже вы найдете рабочий пример. Для простоты я не добавлял никаких данных к типам, но, конечно, это можно легко сделать. Подпрограмма invokeCalc12 определена только в базовом типе, но может быть вызвана из производных типов, и поскольку в Фортране все методы виртуальны, она будет вызывать правильные методы.

module calc_base
  implicit none

  type, abstract :: CalcBase
  contains
    procedure(calcInterface), deferred :: calc1
    procedure(calcInterface), deferred :: calc2
    procedure :: invokeCalc12
  end type CalcBase

  interface
    subroutine calcInterface(self, ii)
      import :: CalcBase
      class(CalcBase), intent(inout) :: self
      integer, intent(in) :: ii
    end subroutine calcInterface
  end interface

contains

  subroutine invokeCalc12(self, ii)
    class(CalcBase), intent(inout) :: self
    integer, intent(in) :: ii
    call self%calc1(ii)
    call self%calc2(ii)
  end subroutine invokeCalc12

end module calc_base


module some_calc
  use calc_base
  implicit none

  type, extends(CalcBase) :: SomeCalc
  contains
    procedure :: calc1
    procedure :: calc2
    procedure :: calc3
  end type SomeCalc

contains

  subroutine calc1(self, ii)
    class(SomeCalc), intent(inout) :: self
    integer, intent(in) :: ii
    print *, "SomeCalc1:calc1", ii
  end subroutine calc1

  subroutine calc2(self, ii)
    class(SomeCalc), intent(inout) :: self
    integer, intent(in) :: ii
    print *, "SomeCalc1:calc2", ii
  end subroutine calc2

  subroutine calc3(self, ii)
    class(SomeCalc), intent(inout) :: self
    integer, intent(in) :: ii
    call self%%invokeCalc12(ii)
  end subroutine calc3

end module some_calc


program test
  use some_calc
  implicit none

  type(SomeCalc) :: mySimulation

  call mySimulation%calc3(42)

end program test

Примечание. Я видел, что аналогичный вопрос был опубликован на comp. lang.fortran, но на данный момент я не нашел там рабочего примера, поэтому выкладываю сюда.

person Bálint Aradi    schedule 23.06.2015
comment
Привет Балинт, спасибо за ваш ответ, я смотрю на него. Просто хотел сказать, что я никогда не публиковал свой вопрос на comp.lang.fortran, и я очень удивлен, почему кому-то действительно нужно это делать... - person Vitaliy; 23.06.2015
comment
@Виталий Тоже не уверен, почему он там появился. В любом случае, я надеюсь, что приведенный выше пример поможет вам найти правильное решение. :-) - person Bálint Aradi; 23.06.2015
comment
Здравствуйте, Балинт, в вашем примере вы перенесли реализацию функций calc1() и calc2() в расширенный класс, в то время как эти функции используют только параметр базового класса и ведут себя одинаково для задачи1 и задачи2. Так что мне кажется, что вы ответили на другой вопрос. Кроме того, я хотел сохранить реализацию calc1() и calc2() в разных файлах (модуль/классы), поскольку они реализуют очень разную логику, используя некоторые частные функции из своих классов, хотя я не упомянул явно в своем вопросе - я буду Добавь это. - person Vitaliy; 23.06.2015