Лекция
Это продолжение увлекательной статьи про класс.
...
нужно применять рекурсивно.
Итак, нет ничего загадочного и запутанного в определении цели любого вызова, несмотря на всю относительность и рекурсивность правил. Что действительно является удивительным, так это мощь компьютеров, которую мы используем, выступая в роли учеников чародея. Мы создаем относительно небольшой текст заклинания - ПО, и затем выполняем его, в результате чего создаются объекты и выполняются вычисления, и число этих объектов и вычислений столь велико, что кажется почти бесконечным по меркам человеческого сознания.
Системы
Эта лекция акцентирует внимание на классах - элементах конструкции ОО-ПО. Для получения исполняемого кода классы необходимо скомпоновать в систему.
Определение системы вытекает из предшествующего обсуждения. Для построения системы необходимы три вещи:
Для получения системы эти элементы должны удовлетворять критерию целостности. Каждый класс, прямо или косвенно необходимый корневому, должен быть частью множества CS. Это условие замыкания системы (system closure) .
Понятие необходимости следует уточнить, как это обычно делается при построении замыкания:
Теперь можно дать определение замкнутой системы.
Определение: замкнутая система
Система является замкнутой, если и только если множество ее классов содержит все классы, необходимые корневому классу.
Специализированная программа, например компилятор, может обработать все классы замкнутой системы, начиная с корневого. Рекурсивное обращение к необходимым классам будет происходить по мере того, как встретится упоминание о них. В результате будет сформирован исполняемый код, соответствующий системе в целом.
Этот процесс называется компоновкой или сборкой (assembly) системы и является завершающим этапом разработки.
Неоднократно подчеркивалось, что системы, разработанные с помощью ОО-подхода, не используют понятия основной программы. Не впускаем ли мы основную программу с черного хода, вводя определение корневого класса и корневой процедуры?
Не совсем. В традиционном понятии основной программы объединены две не связанные концепции:
Первое условие, безусловно, необходимо. Выполнение любой системы должно начинаться с вполне определенной позиции. В ОО-системах эта позиция определяется корневым классом и корневой процедурой. В случае параллельных, а не последовательных вычислений можно определить несколько начальных точек - по одной для каждой независимой нити или потока (Thread) вычислений.
Концепция вершины уже достаточно обсуждалась ранее и не требует дополнительных комментариев.
Нет никаких оснований для объединения столь разных понятий. Нельзя приписывать особую роль точке начала выполнения кода в архитектуре системы. Типичным примером может служить инициализация операционной системы, выполняемая процедурой загрузки. Этот небольшой и незначительный компонент безусловно нельзя считать центральным в архитектуре операционной системы. Объектная технология исходит из прямо противоположной предпосылки, считая, что важнейшими свойствами системы являются входящий в нее ансамбль классов, функциональные возможности этих классов и их взаимосвязь. В таком контексте выбор корневого класса играет второстепенную роль и при необходимости его можно легко изменить.
Ранее уже указывалось, что необходимо отказаться на раннем этапе разработки системы от вопроса, - "где основная программа?". Если строить архитектуру системы на основе ответа на этот вопрос, то нельзя обеспечить расширяемость и повторное использование кода. Другой подход - готовые к повторному использованию классы, реализации АТД. Программные системы в этом случае представляют собой перестраиваемые ансамбли таких компонент.(О критике функциональной декомпозиции см. "Функциональная декомпозиция", )
Не всегда конечной целью разработки является создание систем. Важным приложением метода является разработка библиотек классов для повторного использования. Библиотека это не система и она не имеет корневого класса. В процессе разработки библиотеки часто создают несколько систем, но такие системы используются только для отладки и не являются частью завершенной версии библиотеки. Окончательный продукт является набором классов, который другие разработчики будут использовать для разработки своих систем или своих библиотек.
Как практически реализовать процесс компоновки системы?
Допустим, что операционная система использует обычный способ хранения исходных текстов классов в файлах. Инструментальному средству компоновки (компилятор, интерпретатор) необходима следующая информация:
Эта информация не должна содержаться непосредственно в исходных текстах классов. Идентификация класса как корневого в его исходном тексте (А1) нарушает принцип отсутствия основной программы. Включение в исходные тексты классов информации о местонахождении соответствующих файлов означало бы жесткую привязку к файловой системе и, очевидно, является неприемлемым решением. Если размещение изменить, то использование таких классов становится невозможным.
Из этих рассуждений следует, что для сборки системы необходима информация, размещенная вне исходных текстов классов. Для обеспечения такой информацией будем использовать небольшой управляющий язык под названием Lace. Рассмотрим процесс сборки, но сразу отметим, что детали Lace совершенно несущественны в контексте ОО-подхода. Язык Lace просто конкретный пример управляющего языка, позволяющего сохранить автономность и возможность повторного использования классов, используя некий механизм для сборки файлов системы.
Рассмотрим типичный документ Lace, так называемый файл Ace:
system painting root
GRAPHICS ("painting_application")
cluster
base_library: "\ library\ base";
graphical_library: "\ library\ graphics";
painting_application: "\ user\ application"
end -- system painting
Предложение cluster определяет генеральную совокупность файлов, содержащих тексты классов. Оно содержит список кластеров. Кластер - это группа связанных классов, представляющих подсистему или библиотеку. (Модель кластеров обсуждается в курса "Основы объектно-ориентированного проектирования")
Операционные системы, такие как Windows, VMS или Unix, содержат удобный механизм поддержки кластеров - подкаталоги. Их файловые системы имеют древовидную структуру. Конечные узлы дерева (листья), называемые "обычными файлами", содержат непосредственно информацию, а промежуточные узлы, подкаталоги, содержат наборы файлов, состоящие из обычных файлов и подкаталогов.

Рис. 7.7. Структура каталогов
Можно ассоциировать каждый кластер с подкаталогом. В Lace используется следующее соглашение: каждый кластер, например base_library, имеет связанный с ним подкаталог, имя которого дано в двойных апострофах - "\ library\ base". Такое соглашение об именах файлов используется в Windows (\dir1\dir2\ ... ) и здесь приведено только ради примера. Соответствующие имена Unix получаются заменой символов обратной косой черты на обычную.
| Можно использовать иерархию подкаталогов для определения иерархии кластеров. Кроме того, Lace поддерживает понятие субкластера, что позволяет определить логическую структуру иерархии вложенных кластеров независимо от их физического положения в файловой системе. |
Каталоги, перечисленные в предложении cluster, могут содержать файлы всех типов. Для работы с генеральной совокупностью процессу компоновки системы необходима информация о том, какие из файлов содержат тексты классов. Используем простое соглашение - текст некоторого класса с именем NAME размещается в файле name.e (нижний регистр). В этом случае, генеральная совокупность представляет собой набор файлов с именами вида name.e в каталогах, перечисленных в предложении cluster.
Предложение root Lace служит для задания корневого класса системы. В данном случае корневым является класс GRAPHICS и он находится в кластере painting_application. Если только один класс в генеральной совокупности называется GRAPHICS, то нет необходимости указывать кластер.
Предположим, что компилятор начинает создание системы, описанной в приведенном файле Ace. Далее предположим, что ни один из файлов системы еще не откомпилирован. Компилятор находит текст корневого класса GRAPHICS в файле graphics.e кластера painting_application, который размещается в каталоге \user\application. Анализируя текст класса GRAPHICS, компилятор находит имена классов, которые необходимы GRAPHICS и ведет поиск файлов с соответствующими именами в каталогах трех кластеров. Далее этот поиск повторяется до тех пор, пока не будут обнаружены все классы, прямо или косвенно необходимые корневому классу GRAPHICS.
Важнейшей особенностью этого процесса является возможность его автоматизации. Разработчику ПО не нужно составлять списки зависимых модулей, известных как "Make-файлы", или указывать в каждом файле имена файлов, необходимых для его компиляции ("директивы Include" в C и C++). Кроме своей утомительности процесс создания этой информации вручную является потенциальным источником ошибок. Единственное, что самостоятельно не сможет определить ни одна утилита - это имя корневого класса и размещение необходимых классов в файловой системе.
Для дальнейшего упрощения работы программиста хороший компилятор должен уметь создавать шаблоны файлов Ace, предложение cluster которых включает базовые библиотеки (ядро, фундаментальные структуры данных и алгоритмы, графика и т. д.) и указание на текущий каталог. В этом случае разработчику остается только указать имя системы и ее корневого класса без необходимости глубокого знания синтаксиса Lace.
Конечным продуктом процесса компиляции является исполняемый файл, имя которого совпадает с именем системы в файле Ace, в данном примере - painting.
Язык содержит ряд других простых конструкций, поддерживающих управление действиями инструментальных средств компоновки, в частности директив компилятора и уровней контроля утверждений. При дальнейшем изучении ОО-метода некоторые из них будут использованы. Уже отмечалось, что Lace поддерживает понятие логического субкластера и может использоваться для описания комплексных структур, включая подсистемы и многоуровневые библиотеки.
Использование независимого от языка разработки языка описания системы аналогичного Lace позволяют классам оставаться системно независимыми. Классы являются компонентами ПО, аналогичными электронным микросхемам, и система собрана из конкретного набора классов подобно компьютеру, собранному из определенного набора микросхем.
Повторное использование замечательная вещь, но иногда надо решить очень простую задачу, например вывести строку. Интересно, как написать такую "программу". После введения понятия системы можно ответить и на этот животрепещущий вопрос.
Следующий маленький класс содержит процедуру, выводящую строку:
class SIMPLE creation
make
feature
make is
-- Вывод строки.
do
print_line ("Hello Sarah!")
end
end
Процедура print_line с параметром некоторого типа выводит значение соответствующего объекта, в данном случае строки. Другая процедура с именем print делает то же самое, но без перехода на новую строку. Обе процедуры доступны во всех классах и унаследованы от универсального предка GENERAL, обсуждаемого далее. (О классе GENERAL см. "Универсальные классы", )
Для получения системы, которая будет выводить данную строку необходимо сделать следующее:
В результате на консоли появится сообщение:
Hello Sarah!
Общую картину процесса построения ПО ОО-методом мы уже знаем. Нам также известно, как восстановить цепочку событий, связанную с выполнением некоторой операции. Рассмотрим операцию:
[A]
x.g (u, v, ...)
присутствующую в тексте подпрограммы r класса C и предположим, что x это атрибут. Как и когда она будет выполняться? Класс C должен быть включен в систему, скомпонованную затем с помощью соответствующего файла Ace. Далее следует запустить выполнение системы, которое начнется с создания экземпляра корневого класса. Корневая процедура создания должна выполнить одну или более операций, которые прямо или косвенно создадут объект C_OBJ - экземпляр класса C, а затем выполнят вызов:
[B]
a.r (...)
где a присоединено к C_OBJ. Далее вызов [A] выполнит g с заданными аргументами, используя в качестве цели объект, присоединенный к полю x объекта C_OBJ.
Итак, теперь мы знаем, как восстановить точную последовательность событий, происходящих в процессе выполнения системы. Подразумевается, что мы видим систему целиком. Текст одного класса, естественно, не позволяет определить порядок, в котором клиенты будут вызывать его подпрограммы. В этом случае единственная доступная для обозрения последовательность событий это порядок, в котором выполняются инструкции в теле данной подпрограммы.
Даже на уровне системы структура настолько децентрализована, что задача точного определения порядка операций, безусловно разрешимая, практически оказывается очень сложной. Важно то, что это и не очень интересно. Необходимо помнить, что корневой класс является весьма поверхностным свойством системы. Это частный выбор, сделанный уже после формирования набора классов. Всегда есть возможность достаточно просто изменить выбор корневого класса.
Этот уход от упорядочения является частью объектной технологии и стимулирует создание децентрализованной архитектуры систем. В центре внимания не "порядок выполнения программы", а функциональные возможности набора классов. "Порядок", в котором эти возможности будут реализованы в процессе выполнения конкретной системы, является вторичным свойством. (См. "Преждевременное упорядочение", )
Данные наблюдения позволяют рассматривать роль программиста как пиротехника или человека, разжигающего огромный костер. Он складывает дрова, следя за тем, чтобы все компоненты были готовы для компоновки и необходимые связи присутствовали. Далее он зажигает спичку и следит за огнем. Если структура правильно подготовлена, то нет необходимости стараться предсказать последовательность возгораний. Достаточно знать, что каждая часть, которая должна вспыхнуть, загорится и это произойдет не раньше положенного времени.
В заключение данной лекции имеет смысл рассмотреть обоснования и альтернативы некоторых принятых решений, связанных с разработкой метода и нотации. Аналогичными разделами завершаются все лекции, в которых вводятся новые понятия.
Отточим наши критические навыки вначале на чем-либо не столь существенном. Поэтому начнем с синтаксиса. Рассмотрим нотацию, используемую при объявлении компонентов. В отличие от многих языков мы не использовали для подпрограмм ключевых слов procedure или function. Форма объявления компонента позволяет отличить, будет ли он атрибутом, процедурой или функцией. Любое объявление компонента всегда начинается с его имени:
f ...
Тем самым сохраняется возможность дальнейшего определения компонента любого типа. Если далее присутствует список параметров
g (a1: A; b1: B; ...) ...
то понятно, что g подпрограмма, которая может быть процедурой или функцией. Далее может следовать:
f: T ...
g (a1: A; b1: B; ...): T ...
В первом примере все еще есть выбор - f может быть либо атрибутом, либо функцией без аргументов. Во втором случае неопределенность заканчивается и g может быть только функцией. Для f неопределенность разрешается в зависимости от того, что следует за T. Если ничего, то f это атрибут, как в следующем примере:
my_file: FILE
Но если далее присутствует ключевое слово is, а за ним тело подпрограммы (do или варианты once и external, рассматриваемые позже), как в примере:
f: T is
-- ...
do ... end
то f - функция. Еще один вариант
f: T is some_value
определяет f как атрибут-константу (constant attribute), значение которой равно some_value. (Атрибуты-константы обсуждаются в )
Такой синтаксис позволяет легко распознавать различные виды компонентов, подчеркивая в то же время их фундаментальные общности. Само понятие компонента, объединяющее подпрограммы и атрибуты лежит в русле принципа унифицированного доступа. Общность в объявлениях атрибутов основана на тех же принципах.
Рассмотрим подробнее следствия принципа унифицированного доступа и объединения атрибутов и подпрограмм под общим заголовком - компоненты. (См."Унифицированный доступ", . См. данную лекцию.)
Принцип декларирует, что клиенты модуля обращаются ко всем его сервисам идентичным образом независимо от способа их реализации. В данном случае в роли сервисов выступают компоненты класса, и для клиентов имеет значение только доступность соответствующих компонентов, независимо от того, как они реализованы атрибутами или функциями.
Рассмотрим класс PERSON, содержащий компонент типа INTEGER без параметров. Если автор клиентского класса записывает выражение
Isabelle.age
то единственно важным будет то, что age возвращает целое число - значение возраста экземпляра PERSON, который во время выполнения присоединен к сущности Isabelle. Компонент age может быть как атрибутом, так и функцией, вычисляющей результат, используя значение атрибута birth_date и текущую дату. Автору клиентского класса нет необходимости знать, какое из этих решений выбрал автор PERSON.
Нотация для доступа к атрибуту идентична вызову подпрограммы, а нотации для объявления этих видов компонентов одинаковы настолько, насколько это концептуально возможно. Если в дальнейшем автор класса заменит реализацию функции на атрибут или наоборот, то это никак не отразится на клиентах данного класса.
Различие в точках зрения поставщика и клиента на атрибут представлено на и , использованных для определения понятия компонента. иллюстрирует разницу между подпрограммами и атрибутами - это внутреннее представление с позиций реализации, используемое поставщиком. в качестве первичного критерия использует разницу между командами и запросами - это внешнее представление клиента.
Решение рассматривать атрибуты и функции без параметров как эквивалентные для клиентов имеет два важных следствия, рассматриваемые подробно в последующих лекциях:
- Первое следствие касается программной документации. Стандартная документация класса для клиента, известная как краткая форма класса, составляется так, чтобы отсутствовала разница в описаниях атрибутов и функций без параметров. (См. "Использование утверждений в документации: краткая форма класса", )
- Второе следствие связано с наследованием, как основным способом адаптации программных элементов к новым условиям без разрушения существующего ПО. Если некий класс содержит компонент, представляющий собой функцию без аргументов, то вполне допустимо в классах-потомках переопределить его как атрибут. (См. "Предопределение функции в качестве атрибута", )
В завершение предшествующей дискуссии необходимо обсудить вопрос об экспорте атрибутов. Рассмотренный в этой лекции класс POINT имеет атрибуты x и y и экспортирует их клиентам, также как и функции rho и theta. Для получения значения атрибута некоторого объекта используется обычная нотация для вызова компонентов в виде my_point.x или my_point.theta.
Эта возможность экспорта атрибутов отличается от соглашений, принятых во многих ОО-языках. Типичным примером является Smalltalk, в котором только подпрограммы (методы) могут быть экспортированы классом, а прямой доступ к атрибутам (свойствам) запрещен.
Следуя подходу Smalltalk, доступ к атрибуту можно обеспечить только с помощью небольшой экспортированной функции, возвращающей значение атрибута. В примере класса POINT назовем атрибуты internal_x, internal_y и добавим функции abscissa и ordinate. Лаконичный синтаксис Smalltalk допускает присваивание одинаковых имен атрибуту и функции, избавляя от необходимости придумывать специальные имена для атрибутов.
class POINT feature
-- Общедоступные компоненты:
abscissa: REAL is
-- Горизонтальная координата
do Result := internal_x end
ordinate: REAL is
-- Вертикальная координата
do Result := internal_y end
... Другие компоненты аналогичны предыдущей версии ...
feature {NONE}
-- Компоненты недоступные клиентам:
internal_x, internal_y: REAL
end
Этот подход имеет два недостатка:
- Он побуждает авторов классов писать много маленьких функций, аналогичных abscissa и ordinate. Несмотря на то, что такие функции будут очень короткими, автор класса будет тратить на их написание дополнительные усилия, а их присутствие затрудняет восприятие исходного текста.
- Существенное снижение производительности, так как каждое обращение к полю объекта требует вызова функции. Ничего удивительного в том, что объектная технология в некоторых кругах заработала репутацию неэффективной. Можно конечно разработать оптимизирующий компилятор, осуществляющий подстановку вместо вызова функций, но тогда какова роль таких функций?
Подход, обсуждаемый в данной лекции, представляется предпочтительным. Он избавляет от необходимости загромождать исходные тексты многочисленными крошечными функциями и предоставляет возможность экспорта, где это необходимо. Такая практика не мешает скрытию информации, а фактически является непосредственной реализацией этого принципа, как и принципа унифицированного доступа.
Эта методика удовлетворяет требованиям унифицированного доступа (преимущество для клиентов), упрощает восприятие исходных текстов (преимущество для поставщиков) и повышает эффективность (преимущество для всех).
Экспорт атрибута с использованием рассмотренной техники делает его доступным клиентам только для чтения в виде my_point.x. Модификация атрибута путем присваивания не разрешается. Следующая синтаксическая конструкция недопустима для атрибутов (Внимание: недопустимая конструкция - только для иллюстрации.):
my_point.x := 3.7
Действует простое правило. Если attrib является атрибутом, то a.attrib является выражением, а не сущностью. Следовательно, ему нельзя присвоить значение, как нельзя присвоить значение выражению a + b.
Возможность модификации attrib достигается добавлением экспортируемой процедуры вида:
set_attrib (v: G) is
-- Установка значения attrib равным v.
do
attrib := v
end
Вместо этого можно было бы представить следующий синтаксис для разграничения прав доступа пользователей (Внимание: не поддерживаемая нотация. Только для обсуждения.)
class C feature [AM]
...
feature [A]{D, E}
...
здесь A обозначает возможность чтения, а M - модификации. Это устранило бы потребность в частом написании процедур аналогичных set_attrib.
Помимо неоправданных дополнительных языковых сложностей такой подход не слишком гибок. Во многих случаях может потребоваться специфический способ модификации атрибута. Например, некоторый класс экспортирует счетчик, значения которого нельзя изменять произвольно, а только с шагом +1 или -1:
class COUNTING feature
counter: INTEGER
increment is
-- Увеличение значения счетчика
do
counter := counter + 1
end
decrement is
-- Уменьшение значения счетчика
do
counter := counter - 1
end
end
Аналогичным образом клиенты класса POINT не имеют возможности непосредственно изменять координаты точки x и y. Для этой цели служат экспортированные процедуры translate и scale.
При изучении утверждений мы рассмотрим еще одну принципиальную причину недопустимости непосредственных присваиваний a.attrib := some_value. Причина в том что не любые значения some_value могут быть допустимыми. Можно определить процедуру
set_polygon_size (new_size: INTEGER) is
-- Установить новое значение числа вершин многоугольника
require
new_size >= 3
do
size := new_size
end
параметр которой может равен 3 или больше. Прямое присваивание не позволяет учесть это условие и в результате получается некорректный объект.
Эти рассуждения показывают, что автор класса имеет в своем распоряжении пять возможных уровней предоставления прав доступа клиентов к атрибутам ().

Рис. 7.8. Возможные варианты прав доступа клиентов к атрибутам
Уровень 0 соответствует полному отсутствию доступа к атрибуту. На уровне 1 открыт доступ только для чтения. На уровне 2 разрешена модификация с помощью специальных алгоритмов. На уровне 3 новое значение может быть присвоено, только если удовлетворяет определенным условиям, как в примере для многоугольника. На уровне 4 ограничения снимаются.
Решение, описанное в данной лекции, следует из приведенного анализа. Экспорт атрибута дает клиентам право доступа только для чтения (уровень 1). Разрешение на модификацию обеспечивается написанием и экспортом соответствующих процедур. Они предоставляют ограниченные права, как в примере для счетчика (уровень 2), право модификации при соблюдении определенных условий (3) и неограниченный доступ (4).
Это решение является развитием идей, существующих в различных ОО-языках:
Данная дискуссия иллюстрирует два важных принципа построения языка: не создавать без необходимости дополнительных проблем программисту и не вводить лишних языковых конструкций.
На уровнях 2 и 3 неизбежно использование явных вызовов процедуры подобных my_polygon.set_size (5) для изменения значения атрибута. Существует опасение, что использование такого стиля на уровне 4 негативно скажется на производительности. Тем не менее компилятор может создавать для вызова my_point.set_x (3.7) код столь же эффективный, как и для my_point.x := 3.7, если бы такое присваивание было бы разрешено.
| Компилятор ISE добивается этого путем общего механизма непосредственного встраивания кода подпрограмм с подстановкой соответствующих параметров и необходимость вызовов устраняется. |
Встраивание кода подпрограмм является одним из преобразований, которое должен обеспечивать оптимизирующий компилятор ОО-языка. Модульный стиль разработки, поощряемый объектной технологией, сопряжен с наличием большого числа небольших подпрограмм. Программисты не должны беспокоиться, что соответствующие вызовы приведут к снижению производительности. Они должны заботиться о последовательном соблюдении принципов объектной архитектуры, а не об особенностях выполнения.
В некоторых языках программирования, особенно в Ada и C++, разработчики могут отметить, какие подпрограммы они хотели бы встраивать. По ряду причин предпочтительно, чтобы эта работа выполнялась в режиме автоматической оптимизации.
Современная концепция разработки ПО подразумевает, что утомительную, автоматизируемую и тонкую работу по оптимизации нужно возлагать на соответствующие утилиты, а не на человека. Это обстоятельство является одной из причин принципиальной критики C++ и Ada. Мы вернемся к этому вопросу при обсуждении двух других ключевых моментов объектной технологии - управления памятью и динамического связывания. (См. "Требования к сборщику мусора", , и "Подход C++ к связыванию", )
Селективный экспорт это не просто удобство, а неотъемлемая часть ОО-архитектуры. Он позволяет группе концептуально связанных классов обеспечить друг другу доступ ко всем своим компонентам, скрыв их от остального мира в соответствии с принципом скрытия информации. Кроме того, это ключ к пониманию вопроса о том, нужны ли вообще модули более высокого уровня, чем классы.
Без селективного экспорта единственным решением будет введение нового типа модулей, представляющего собой группу классов. Такие супермодули - аналоги пакетов Ada и Java - будут осуществлять скрытие информации и экспорт по своим правилам. Добавление в элегантную структуру, основанную на классах, нового и частично несовместимого модульного уровня приведет к усложнению и увеличению объема языка.
Лучшим решением является использование в качестве супермодулей самих классов. Такой подход реализован в Simula, допускающем вложение классов. Однако он не дает ощутимых преимуществ.
Простота объектной технологии в значительной степени базируется на использовании простой концепции модулей. Поддержка классами повторного использования основана на возможности их извлечения из контекста, сохраняя лишь их логические зависимости. Существует риск потери этих преимуществ, если ввести супермодули. В частности, становится невозможным непосредственное повторное использование класса, являющегося частью пакета. Придется либо полностью импортировать весь пакет, либо делать копию класса. Явно непривлекательная форма повторного применения.
Необходимость объединения классов в структурированные коллекции сохраняется. В данной книге она реализована через понятие кластера ( курса "Основы объектно-ориентированного проектирования"). Однако понятие кластера относится к области управления и организации. Если включить его в качестве языковой конструкции, то это угроза потери простоты ОО-подхода и его поддержки модульности.
Если необходима группа классов, в которой каждый наделен специальными привилегиями, то нет нужды в супермодулях. Простое решение обеспечивается за счет селективного экспорта, что позволяет сохранить классам свой независимый статус.
В исходных текстах классов, в предложениях feature, перечислены компоненты, доступные другим классам. Почему бы, в свою очередь, не включать списки компонентов, полученных от других классов? Язык Modula-2 поддерживает, например, объявление import.
Тем не менее, при ОО-подходе это ничего не дает
продолжение следует...
Часть 1 7. Статические структуры: классы
Часть 2 Объектно-ориентированный стиль вычислений - 7. Статические структуры: классы
Часть 3 Программа main отсутствует - 7. Статические структуры: классы
Часть 4 Присваивание функции результата - 7. Статические структуры: классы
Исследование, описанное в статье про класс, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое класс, класс как структура данных и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Объектно-ориентированное программирование ООП
Комментарии
Оставить комментарий
Объектно-ориентированное программирование ООП
Термины: Объектно-ориентированное программирование ООП