Хорошо ли полностью скрывать объекты от пользователя?

Я пишу короткий модуль на Фортране 90/2003, который предоставляет простой и удобный интерфейс для подсчета времени между различными частями выполнения программы. Вдохновленный командами tic, tac в Matlab, идея состоит в том, что пользователь использует модуль в программе следующим образом:

program test
use Timer
call Tic("timername")
! some heavy stuff
call Tac("timername")
end program test

Теперь я знаю, как могу добиться такого результата с помощью встроенных функций Fortran. Мой вопрос заключается в том, как мне должен это сделать. Я делаю это, чтобы изучить хорошие методы проектирования, а не синтаксис Fortran.

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

а) Я могу сделать определяемую пользователем переменную Timer общедоступной, а затем заставить пользователя создать объект таймера вручную. Пользователь должен создать столько таймеров, сколько ему нужно, а затем использовать методы для их обработки.

б) Я могу скрыть этот тип, сделав его приватным. Затем для хранения разных таймеров я создаю массив объектов Timer в модуле как глобальную переменную, хотя и приватную для модуля, и каждый раз, когда пользователь вызывает подпрограмму Tic, в этом массиве определяется новый таймер. В приведенном выше примере пользователь использует модуль, реализованный в соответствии со вторым подходом (обратите внимание, что программа вообще не использует ключевое слово type).

Хотя эти два варианта технически работают (я уже реализовал оба), каждый из них имеет свои преимущества и недостатки, а известные мне правила проектирования программного обеспечения каким-то образом противоречат друг другу. Интересно, какой подход является лучшим с «ортодоксальной» точки зрения.

Вариант а) имеет то преимущество, что лучше следует ООП: пользователь явно создает объект и работает с ним. Он не использует глобальную переменную.

Вариант б) имеет то преимущество, что он более сильно «инкапсулирован». Под этим я подразумеваю, что пользователю даже не нужно знать, что такое Timer, и даже о его существовании. Кроме того, интерфейс, предоставляемый для взаимодействия с Timer объектами, представляет собой простую строку, что делает весь модуль более непрозрачным для пользователя, которому не нужно специально определять Timer переменные. Он/она просто использует две подпрограммы, предоставленные модулем, которые принимают строку в качестве входных данных. Это все. Проблема в том, что у меня такое ощущение, что этот дизайн, основанный на массиве, определенном для всего модуля, противоречит правилу избегания глобальных переменных. Это не настоящая глобальная переменная, так как она частная, но все же.

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

PS: возможно, есть третий вариант, который позволяет пользователю создавать объекты косвенно, не имея доступа к определяемому пользователем типу (т.е. не просто определять элементы в существующем массиве, как это сделано в решении б). Я не знаю, возможно ли вообще создавать переменные во время выполнения. Любая идея в этом направлении также очень приветствуется.


person Onturenio    schedule 26.01.2016    source источник
comment
Всегда используйте тег fortran. Используйте теги версии, чтобы указать конкретную версию, если это необходимо. Имейте в виду, что в Fortran 90 нет настоящего ООП, возможны только некоторые рудиментарные методы. Истинное ООП поставляется только с Fortran 2003. Вы уверены, что хотите ограничить ответы только Fortran 90 (язык 25-летней давности, старше многих здесь!).   -  person Vladimir F    schedule 27.01.2016
comment
Вы можете поместить свою переменную модуля (b) в общедоступный тип данных и передать ее. Это позволит вам иметь несколько таких объектов, сохраняя при этом возможность легко создавать таймеры внутри него. Это перенесет нагрузку с создания отдельных объектов синхронизации на однократное создание контекста синхронизации. В этом сценарии вы все еще можете использовать переменную модуля объекта контекста синхронизации и использовать ее по умолчанию в своих подпрограммах.   -  person haraldkl    schedule 27.01.2016
comment
@VladimirF различия между 90 и 2003 годами становятся очевидными в более продвинутых функциях, таких как наследие или полиморфизмы. В этом небольшом модуле, я думаю, нет различий между 90 и 2003 годами, и сомнения, которые заставили меня поднять мой вопрос, не зависят от этого уровня детализации. Таким образом, я собираюсь отредактировать вопрос, чтобы не ограничивать его фортраном 90. Спасибо за подсказки.   -  person Onturenio    schedule 27.01.2016
comment
@haraldkl, если я вас понял, сценарий, который вы описываете, это а). Теперь у меня вопрос, не будет ли предпочтительнее использовать сценарий б), где модуль более сильно инкапсулирован и пользователю даже не нужно знать о существовании объектов Timer. Он/она может создавать новые таймеры по запросу, не определяя их явно в начале программы. Вариант б) лучше с точки зрения пользователя, но, возможно, не очень ортодоксален для использования глобальной переменной. Оба работают технически, но какой дизайн в целом лучше? Это мой вопрос.   -  person Onturenio    schedule 27.01.2016


Ответы (3)


Обычно хороший дизайн заключается в том, чтобы скрыть детали реализации от пользователя. Это инкапсуляция.

Это означает, что у вас есть какой-то «объект», вы не раскрываете подробности о его внутреннем состоянии, а только некоторые способы использования такого объекта.

1. Модуль как объект

В Fortran 90/95 ООП был несколько ограничен. Один из подходов заключался в том, чтобы иметь модуль, который был бы этим «объектом». Переменные модуля были внутренним состоянием, а процедуры модуля реализовывали функциональность и использовали внутреннее состояние модуля. В этом дизайне вы не раскрываете переменные модуля, если в этом нет необходимости. Проблема в том, что у вас всегда может быть только один экземпляр объекта — модуль.

Это было бы:

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. Фортран 2003

В этом очень простом примере Fortran 2003 не сильно изменится, если вы уже выставили тип. Опять же, состояние находится в компонентах производного типа. Но вы можете привязать процедуры, которые работают с типом, к этому типу напрямую, и вам не нужно импортировать их отдельно. Вы просто use тип и все функции, перегруженные операторы и тому подобное, приходят с ним:

use Timers, only: Timer
type(Timer) :: t

call t%tic()
! some heavy stuff
call t%tac()

Вы можете видеть, что самый современный подход определенно предоставляет пользователю тип Timer.


Когда вы предоставляете тип, вы можете сделать компоненты закрытыми и использовать только конструктор и другие связанные процедуры (необязательно привязанные к типу) для управления ими (геттеры/сеттеры и другие).

person Vladimir F    schedule 27.01.2016
comment
Хорошо, теперь я понимаю, что я использую модуль как объект. Небольшая разница с тем, что вы говорите, заключается в том, что мой модуль на самом деле содержит динамический массив таймеров, которые устанавливаются с помощью вызова Tic, а не только один таймер. Итак, я полагаю, что отделение состояния от функциональности является ортодоксальным подходом. Тогда я пойду на это, и действительно, я буду использовать версию Fortran 2003. Спасибо. - person Onturenio; 27.01.2016

Хотя я мало что знаю об ООП, я думаю, что нет ничего похожего на «самый ортодоксальный подход» (потому что Фортран допускает ООП, но не навязывает его). Выбор также, по-видимому, зависит от необходимости создания нескольких экземпляров Timer с одной и той же строкой символов (например, при параллельном запуске?). В этом случае вариант (а) может быть более удобным, а вариант (б) в противном случае представляется более удобным. Объединение двух подходов также возможно за счет разрешения явного создания пользователем объектов Timer и предоставления удобных подпрограмм tic()/toc(), которые автоматически создают/манипулируют необходимыми объектами в модуле.

person roygvib    schedule 26.01.2016
comment
Книги являются примером методов, которые в значительной степени зависят от Fortran 2003. - person Vladimir F; 27.01.2016
comment
Я удалил информацию о Scientific Software Design (потому что ее нет в F90). Я надеюсь, что кто-то даст более полезный ответ, чтобы мы могли узнать о выборе (на самом деле, я также пытался сделать подобную вещь с таймером...) - person roygvib; 27.01.2016
comment
Не думаю, что нужно было это удалять. - person Vladimir F; 27.01.2016
comment
@VladimirF Хорошо, тогда вот ссылка ссылка. Насколько мне известно, эту книгу часто рекомендуют, так что, возможно, ее стоит проверить (я купил ее, но еще не читал, поэтому не могу точно сказать о содержании...) - person roygvib; 27.01.2016
comment
Разрешение явного создания экземпляров Timer плюс предоставление подпрограмм Tic/Tac на самом деле является вариантом а). На мой взгляд, это более канонично следует за ООП. Однако это противоречит правилу инкапсуляции, которому более строго следуют в варианте б). Я знаю, что мой вопрос немного некорректен и допускает разные ответы, но моя цель состоит в том, чтобы найти кого-то опытного в ООП с Fortran, который поможет мне достичь наиболее стандартного/ортодоксального/современного/надежного/надежного дизайна. Таким образом, хотя ваш ответ полезен, я хотел бы получить дополнительные мнения. Спасибо. - person Onturenio; 27.01.2016
comment
Всего одно замечание: я думаю, что лучше дать более четкое определение инкапсуляции в вашем Вопросе, потому что оно кажется немного отличным от обычного. Кроме того, мое последнее предложение в ответе выше - это не вариант а), а гибрид а) и б) (обратите внимание, что процедуры обслуживания Tic/Tac предназначены для процедур без привязки к типу, поэтому его можно использовать так же, как б) со стороны пользователя. Кроме того, мы можем определить привязку к типу (и подпрограммы могут быть повторно использованы там), чтобы разрешить использование на основе экземпляра. Но в любом случае, я надеюсь, что другие люди дадут вам больше полезной информации :) - person roygvib; 27.01.2016
comment
Под инкапсуляцией я подразумеваю создание черного ящика для пользователя. У него есть только косвенный доступ к его содержимому через интерфейс, который в идеале должен быть как можно меньше, и ему не нужно разбираться в коробке, чтобы ею пользоваться. Вариант б) более инкапсулирован, потому что он позволяет пользователю использовать модуль, не зная, что он на самом деле использует определенный пользователем тип и его имя, а интерфейс упрощен до простой строки для маркировки таймера. Я отредактирую вопрос соответственно. - person Onturenio; 27.01.2016
comment
Извините, я не следил за вами, хотя это звучит важно. Что такое процедура без привязки к типу? - person Onturenio; 27.01.2016
comment
@Onturenio Привет, под процедурой без привязки к типу я имел в виду подпрограмму, такую ​​​​как call Tic (str), то есть что-то вроде тех, которые используются в вашем примере кода. Мы можем создать обычный объектно-ориентированный материал, как объяснено в ответе Влада (версия F03), но это не очень удобно, если пользователь не хочет даже объявлять объекты. В этом случае мы можем предоставить служебные процедуры без привязки к типу, такие как Tic(), которые обрабатывают все (включая создание объектов) внутри модуля, так что пользователю не нужно объявлять объекты Timer. Но платой за это является то, что реализация становится более утомительной. - person roygvib; 27.01.2016

Да, инкапсуляция и сокрытие данных считаются хорошей практикой при разработке программного обеспечения. Мы можем добиться этого в Фортране, создав производный тип, чтобы экземпляр типа (объект) был непрозрачным. Рассмотрим следующий модуль

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 только с помощью подпрограммы contain-ed в модуле SomeModule.

У меня есть более конкретный пример здесь.

person pch    schedule 27.01.2016