Руководство разработчика

Открытые и закрытые части объектов

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

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

Особенно важно использовать закрытые части объектного типа, когда значения полей должны быть в согласованом состоянии. При свободном открытом доступе к полям любая функция может изменить одно поле и не изменить другие зависимые поля. В этом случае говорят, что инвариант объекта нарушен, т.е. нет логической целостности его внутреннего состояния. Закрытые поля доступны только методам самого объекта так, что на эти методы ложится ответственность за сохранение инварианта. Для проверки соблюдения инварианта в методах можно пользоваться утверждениями.

Например, у объекта есть два поля, являющихся массивами строк. Логика работы объекта требует, чтобы в массивах было одинаковое количество элементов. При открытых полях внешняя функция может добавить элемент в первый массив и забыть - во второй. Если поля закрыты, то добавить элементы сможет только специальный метод, который принимает две строки и добавляет по одному элементу в каждый массив. Если метод добавления элементов - открытый, то им смогут пользоваться любые функции. Так исключается опасность нарушения инварианта объекта из внешнего кода.

Начальное значение полей объектного типа может указываться в константе объектного типа или в списке инициализации. При конструировании объектного значения не разрешается указывать значения закрытых полей. Следовательно, закрытые поля принимают значения по умолчанию, указанные при их объявлении. Так обеспечивается соблюдение инварианта объекта в его начальном состоянии.

Синтаксис объявления открытых и закрытых частей объектного типа использует ключевые слова ОТКРЫТО и ЗАКРЫТО. Начало секции отмечается указанием одного из этих ключевых слов и следующего за ним символа двоеточия. Количество и порядок следования секций с открытыми и закрытыми объявлениями не ограничивается. Если не указано обратного, то за открывающейся квадратной скобкой объявления объектного типа начинается открытая секция.

Синтаксис:

ОТКРЫТО:
ЗАКРЫТО:

Пример 1

Пример иллюстрирует согласованное изменение внутреннего состояния объекта и проверку соблюдения инварианта.

ТИП СписокЗаметок
[
ОТКРЫТО:
  ФУНКЦИЯ Добавить(ДАТА: дата; СТРОКА: заметка)
    ПЕРЕМ кол = #даты

    // проверка инварианта до изменения состояния
    УТВ(#заметки = кол) 

    // выполнение согласованного изменения
    даты[кол + 1] = дата
    заметки[кол + 1] = заметка

    // проверка постусловия изменения
    УТВ(#даты = кол + 1)

    // проверка инварианта после изменения состояния
    УТВ(#заметки = #даты)
  КОНЕЦ_ФУНКЦИИ
  
  ФУНКЦИЯ Очистить
    СБРОС(даты)
    СБРОС(заметки)
    УТВ(#даты = 0)
    УТВ(#заметки = #даты)
  КОНЕЦ_ФУНКЦИИ
  
  ФУНКЦИЯ СТРОКА: Найти(ДАТА: дата)
    // проверка инварианта в неизменяющем состояние методе
    УТВ(#заметки = #даты)
    ЦИКЛ ИНДЕКС (д = даты[инд])
      ЕСЛИ дата = д ТО
        ЕСЛИ #РЕЗУЛЬТАТ > 0 ТО
          РЕЗУЛЬТАТ += ", "
        КОНЕЦ_ЕСЛИ 
        // здесь полагаемся, что индексы в массивах согласованы
        РЕЗУЛЬТАТ += заметки[инд]
      КОНЕЦ_ЕСЛИ
    КОНЕЦ_ЦИКЛА
  КОНЕЦ_ФУНКЦИИ

ЗАКРЫТО:
  ДАТА: даты[];
  СТРОКА: заметки[];
]

ВЫЧИСЛИТЬ
  ПЕРЕМ СписокЗаметок: список
  список.Добавить(ДАТА_ТЕК, "Хорошая погода") // ОК
  список.Добавить(ДАТА_ТЕК, "Сдача отчета")   // ОК
  список.заметки[1] = "Дождь!"                // ошибка - обращение к закрытому полю 
  ОТЛАДКА(список.Найти(ДАТА_ТЕК))
КОНЕЦ

Пример 2

Пример иллюстрирует соблюдение инварианта объекта при конструировании.

ТИП Письмо
[
ОТКРЫТО:
  СТРОКА: текст;

  // функции доступа на чтение к закрытым полям
  ФУНКЦИЯ ЛОГИКА: Шаблон РЕЗУЛЬТАТ = шаблон КОНЕЦ_ФУНКЦИИ
  ФУНКЦИЯ ЛОГИКА: Ответ РЕЗУЛЬТАТ = ответ КОНЕЦ_ФУНКЦИИ
  ФУНКЦИЯ СТРОКА: Тема РЕЗУЛЬТАТ = тема КОНЕЦ_ФУНКЦИИ
  
  ФУНКЦИЯ УстТему(СТРОКА: новая_тема)
    тема = новая_тема
    // согласованное изменение вычисляемых полей
    шаблон = ПОЗ("template", тема) > 0
    ответ = ПОЗ("re:", тема) = 1
  КОНЕЦ_ФУНКЦИИ
 
ЗАКРЫТО:
  // начальные значения полей устанавливаются в соответствии с дизайном типа
  СТРОКА: тема = "re: template";
  ЛОГИКА: шаблон = ДА;
  ЛОГИКА: ответ = ДА;
]

ВЫЧИСЛИТЬ
  ПЕРЕМ Письмо: п1 = [ текст = "Здравствуйте!" ] // ОК

  ПЕРЕМ Письмо: п2 = [ 
                       текст = "Здравствуйте!", 
                       тема = "к сведению"       // ошибка - не доступа к закрытому полю
                     ]                           // в константе объектного типа

  ПЕРЕМ Письмо: п3 = [ текст = "Здравствуйте!" ] // ОК
  п3.УстТему("re: вопрос")                       // ОК - согласованное изменение в методе 

  ПЕРЕМ Письмо: п4 = [ 
                       текст = "Привет!",
                       тема = п3.Тема           // ошибка - не доступа к закрытому полю
                     ]                          // в списке инициализации
КОНЕЦ

См. также: