© А.И. Легалов

"Все могло бы быть совсем не так..."

Казалось бы, что такого в ООП? Ну, свалили в одну упаковку данные и процедуры, а потом начали обертывать этой "бумажкой" экземпляры коробок и использовать для доступа к содержимому название коробки. Ведь почти то же самое можно сделать и с процедурой. Запихнуть в нее другие процедуры и данные. Однако, существует один маленький нюанс: процедура после завершения ее вызова теряет свое состояние (за исключением статических переменных, которые не могут относиться к разным экземплярам), а экземпляр класса (объект) продолжает существовать в пассивном состоянии, периодически нарушаемом вызовами его методов.

Но, процедура - это частный случай процесса, который, наряду с выполнением операций может переходить в состояние ожидания. Сравним жизненные циклы класса и элементарного процесса (рис. 2). Их практическая эквивалентность говорит о том, что процесс может делать все то же, что и класс. А уж сымитировать с его помощью модель класса - вообще нет проблем. При этом процесс, в общем случае, обеспечивает такую динамику поведения, которая не "снилась" ни одному другому программному объекту (достаточно почитать классиков [Хоар, Языки]).

Примечание. Впервые тема эквивалентности классов и процессов обсуждалась нами примерно в 1991 году после ознакомления с Turbo C++ v.1.0. Это происходило в Институте проблем вычислительной техники АН СССР (Ярославль). Но тогда нас профессионально интересовали архитектуры параллельных вычислительных систем и языки параллельного программирования. Поэтому, мы быстро отошли от этих рассуждений и не обратили внимания на наступление новой эры. Интересна история появления Turbo C++ v.1.0. Это единственная версия компилятора, которую я ставил без дистрибутива. Легенда гласит, что ребята из Borland привезли компилятор на какую-то выставку. В обеденный перерыв они отсоединили от компьютера клавиатуру и унесли с собой в столовую. Думали, наверное, что в СССР PC-клавиатуры не найдется! А клавиатура нашлась. Поэтому компилятор быстро упаковали, а потом и распространили (продаю, за что купил:). А.Л.

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

Пример кода на C++

Пример кода на P++

// Абстрактный класс, описывающий
// обобщенную геометрическую фигуру.
// Базовый для классов прочих фигур
class shape {
public:
 virtual void In()=0; // ввод
 virtual void Out()=0; // вывод
 // Вычисление площади фигуры
 virtual double Area() = 0; 
};

// Класс - прямоугольник
class rectangle: public shape
{
 int x, y; // ширина, высота
public:
 // Переопределяем интерфейс класса
 void In();
 void Out();
 double Area();
 // Конструкторы класса
 rectangle(int _x, int _y);
 rectangle() {}
};

// Методы класса - прямоугольника
...
// Вычисление площади прямоугольника
double rectangle::Area() {
 // возврат значения
 // переход в пассивное состояние
 return x * y;
}

...

// Использование классов
void main()
{
  // автоматическое создание класса
 rectangle r;
 // использование методов класса
 r.In();
 double a = r.Area();
 // динамическое создание класса
 shape *s new rectangle(3, 4);
   ...
 // удаление динамического класса
 delete s;
}
// автоматически созданный
// класс удалился
// Абстрактный процесс, описывающий
// обобщенную геометрическую фигуру.
// Базовый для процессов прочих фигур
process shape {
public:
 virtual void In()=0; // ввод
 virtual void Out()=0;// вывод
 // Вычисление площади фигуры
 virtual double Area() = 0;
};

// Процесс - прямоугольник
process rectangle: public shape
{
 int x, y; // ширина, высота
public:
 // Переопределяем интерфейс процесса
 void In();
 void Out();
 double Area();
 // Конструкторы процесса
 rectangle(int _x, int _y);
 rectangle() {} 
};

// Методы процесса - прямоугольника
...
// Вычисление площади прямоугольника
double rectangle::Area() {
 // возврат значения
 // переход в состояние ожидания
 wait x * y;
}

...

// Использование процессов
void main()
{
 // автоматический запуск процесса
 rectangle r;
 // использование методов процесса
 r.In();
 double a = r.Area();
 // запуск динамического процесса
 shape *s new rectangle(3, 4);
  ...
 // завершение динамического процесса
 delete s;
}
// автоматически запущенный 
// процесс завершился

Возникает вопрос: во что выльется техническая реализация таких процессов и возможна ли она? Насколько я понимаю, вряд ли могут возникнуть какие-то проблемы.

Начнем с того, что создание и поддержка процессов осуществляется достаточно давно и различными способами. Практически все операционные системы в настоящее время поддерживают мультипрограммный и мультипроцессорный режимы, как на уровне процессов, так и на уровне ветвей (потоков, нитей). Поэтому, вряд ли могут возникнуть проблемы по порождению процессов только из-за того, что они описаны в стиле, напоминающем объектный. Более того, поддержка процессов и задач осуществляется даже на аппаратном уровне практически во всех современных микропроцессорах. Всем нам известные 32-х разрядные процессоры для ПК фирмы Intel имеют такую возможность с момента своего появления [Шагурин]. Глобальность этого механизма, сочетание виртуальной и страничной адресации позволяют использовать все возможности современных операционных систем.

Можно возразить, что применение процессов вместо классов значительно увеличит объем используемой оперативной памяти. А кто сказал, что эти расходы увеличатся намного? А кто подсчитал, что расходы памяти увеличатся? А кто вообще сейчас считает память?

Традиционно процессы организованы так, что обеспечивают запуск из одной точки с одним вариантом начальных значений. Альтернативные ветви, выходящие из точки ожидания обычно порождаются путем выполнения условных операторов. Поэтому, могут появиться сомнения по поводу наличия нескольких различных точек запуска процессов, эквивалентных конструкторам класса. Червь сомнения может глодать нас и при обсуждении возможности наличия у процесса внутренних методов, отвечающих за отдельные ветви, эквивалентные методам класса. Перефразируя одну печально известную фразу можно сказать: "Оставь сомнения всяк, в процесс входящий"! Множество точек входов и ранее встречалось, даже в процедурах. Достаточно вспомнить PL/1 [Фролов]. А реализация оболочки процесса может быть такой же, как и у компонента используемого в COM [Роджерсон]. Эта двоичная оболочка, фиксирующая только указатель на таблицу функций, позволяет интерпретировать себя как угодно. Кстати, большинство реализаций класса используют точно такую же структуру. Поэтому, вряд ли у процессов возникнут проблемы с инкапсуляцией, полиморфизмом и наследованием.

Итак, технических проблем вроде бы не видно. Но остаются морально-этические. Спрашивается: "А зачем все это надо"? Разве не достаточно, для полного счастья, объектно-ориентированного подхода? Конечно, можно было бы сказать, что это моя месть за процедурное и параллельное программирование. Однако вряд ли такой аргумент можно считать убедительным. Скорее всего, это попытка показать, что после объединения процедур и данных в единую оболочку не существует однозначного толкования и интерпретации возникшего винегрета. На уровне реализации это может объект, а может - процесс. Поэтому, не стоит серьезно относиться и к словам Гради Буча о том, что объектная декомпозиция имеет несколько чрезвычайно важных преимуществ перед алгоритмической. И алгоритмическая декомпозиция позволяет учитывать модель состояний, если опирается на декомпозицию процессов. И в перспективе она может использовать инкапсуляцию, наследование и полиморфизм. Поэтому, вряд ли стоит ставить крест на структурном анализе и проектировании, начинающемся с описания бизнес процессов.

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