Как норвежский оборонный проект случайно произвёл революцию в программировании навсегда

Позвольте рассказать вам историю о том, как в начале 1960-х годов несколько блестящих норвежских исследователей решили задачу, которая в корне изменила всю сферу разработки программного обеспечения. Предупреждаю: они даже не подозревали, что создают одну из самых влиятельных парадигм программирования в истории.

Рождение идеи: симуляции Монте-Карло и ночные разочарования

В конце 1950-х годов Кристен Nygaard работал над симуляциями Монте-Карло в Норвежском научно-исследовательском институте оборонной промышленности. Представьте себе: исследователи вручную выполняли сложные вычисления, иногда используя ранние компьютеры, имевшие вычислительную мощность современного калькулятора. Это было медленно, подвержено ошибкам и, откровенно говоря, бюрократическим кошмаром.

Nygaard столкнулся с фундаментальной проблемой, которая сегодня знакома любому разработчику: как описать сложные системы так, чтобы их понимали люди и могли выполнять компьютеры? Речь шла не просто о вычислениях — нужно было моделировать дискретные события, изменения состояния и взаимодействия систем на более высоком уровне абстракции.

Решение? Simula — сокращение от «Simulation Language». Но вот в чём загвоздка: то, что начиналось как специализированный язык для симуляций, стало прародителем объектно-ориентированного программирования в том виде, в каком мы его знаем сегодня.

Появление динамического дуэта: Даль и Nygaard

В 1963 году Nygaard объединился с Оле-Йоханом Далем, блестящим специалистом по компиляторам. Вместе они столкнулись с фундаментальным ограничением: существующий язык программирования ALGOL не был предназначен для того, что они хотели создать. Управление памятью в ALGOL на основе стека (Last In, First Out) было подобно попытке построить дом только из строительных блоков, которые автоматически выпадают — невозможно поддерживать постоянные структуры данных, существующие дольше вызова функции.

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

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

Первый объектно-ориентированный язык (ы)

Simula I (1962–1965) был завершён в январе 1965 года и стал первым объектно-ориентированным языком в скомпилированном виде. Он представил классы, объекты и важнейшую концепцию инкапсуляции — возможность объединять данные с операциями, которые можно выполнить с ними.

Но настоящим переломным моментом стал Simula 67 (1967), который представил механизм наследования. Именно тогда ООП превратился из «интересного инструмента для симуляций» в «всеобъемлющую парадигму программирования». Вдруг появились классы, которые наследовали от других классов, можно было переопределить поведение в подклассах (называемых виртуальными процедурами в Simula) и организовать код иерархически.

Вот в чём дело: компиляторы Simula начали появляться на всём, от UNIVAC до IBM и DEC в начале 1970-х годов. Это набирало обороты, но мир программирования ещё не осознавал, какой золотой рудник он обретает.

Философские основы: объектно-ориентированное проектирование

Прежде чем мы продолжим, давайте поймём, что сделало Simula революционным с философской точки зрения:

Традиционный процедурный подход:
1. Разделить данные на структуры данных
2. Написать функции для обработки этих данных
3. Надеяться, что ничего не сломается при изменении логики функции
Объектно-ориентированный подход:
1. Группировать связанные данные и поведение вместе
2. Инкапсулировать их как автономные единицы (объекты)
3. Определить, как объекты взаимодействуют друг с другом
4. Изменять внутреннюю реализацию, не затрагивая внешних пользователей

Это был сдвиг парадигмы в том, как программисты думали об организации кода.

Визуализация эволюции концепций ООП

graph TD A["Норвежские оборонные исследования
Симуляции Монте-Карло"] -->|1957-1963| B["Идентификация проблемы
Потребность в языке описания систем"] B -->|1963-1965| C["Simula I
Классы, объекты,
Инкапсуляция"] C -->|1967| D["Simula 67
Наследование
Виртуальные процедуры"] D -->|Начало 1970-х| E["Xerox PARC Smalltalk
Интерактивное ООП
Графические пользовательские интерфейсы"] D -->|1980-е| F["C с классами
Позже C++
Коммерческое ООП"] E --> G["Современные языки ООП
Java, C#, Python и др."] F --> G

Smalltalk: интерактивность и визуализация ООП

Пока Simula незаметно влиял на учёных в Скандинавии, на западном побережье Америки происходило нечто волшебное. Группа Алана Кея из Xerox PARC взяла концепции Simula и задала радикальный вопрос: что, если сделать объектно-ориентированное программирование интерактивным и визуальным?

Результатом стал Smalltalk — впервые выпущенный в 1970-х годах. Smalltalk был революционным по нескольким причинам:

  • Динамическая типизация: не нужно объявлять типы заранее, язык определяет их во время выполнения.
  • Интерпретируемый, а не компилируемый: код можно было модифицировать и тестировать мгновенно.
  • Унифицированная объектная модель: всё было объектом, даже сами классы.

Вот простой пример Smalltalk, показывающий передачу сообщений (способ вызова методов в Smalltalk):

"Создание объекта и отправка сообщений ему"
account := BankAccount new.
account deposit: 1000.
account withdraw: 250.
balance := account balance.

Заметьте, как Smalltalk читается почти как английский? «Отправить сообщение ‘deposit: 1000’ объекту счёта». Это была революционная разработка пользовательского интерфейса — язык был ориентирован как на читаемость человеком, так и на выполнение машиной.

Революция прагматика: C++ и коммерческий успех

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

В Bell Laboratories в конце 1970-х годов Бьёрн Страуструп столкнулся с конкретной проблемой: диссекция ядра UNIX для распределения по локальной сети. Вызов? Как смоделировать сложные системы и облегчить коммуникацию между компонентами в C, который был быстрым, но низкоуровневым?

В 1979 году Страуструп начал работать над «C с классами» — первоначально реализованным как препроцессор под названием Cpre, расширяющий C структурами классов, подобными Simula. Он взял блестящие идеи из Simula и Smalltalk и объединил их со скоростью и эффективностью C.

К 1981 году он представил новаторские функции:

  • встроенные функции;
  • аргументы по умолчанию;
  • перегрузка оператора распределения.

Эта эволюция в конечном итоге стала C++, и вот что сделало её особенной: она стала первым объектно-ориентированным языком, получившим широкое коммерческое распространение.

Давайте посмотрим на практический пример C++, показывающий, как работают концепции ООП:

#include <iostream>
using namespace std;
// Базовый класс, определяющий общее поведение
class Animal {
protected:
    string name;
public:
    Animal(string n) : name(n) {}
    // Виртуальный метод позволяет подклассам переопределять
    virtual void speak() {
        cout << name << " издаёт звук" << endl;
    }
    virtual ~Animal() {}
};
// Производный класс — Dog наследуется от Animal
class Dog : public Animal {
public:
    Dog(string n) : Animal(n) {}
    // Переопределение метода speak
    void speak() override {
        cout << name << " лает: Гав! Гав!" << endl;
    }
};
// Производный класс — Cat наследуется от Animal
class Cat : public Animal {
public:
    Cat(string n) : Animal(n) {}
    void speak() override {
        cout << name << " мяукает: Мяу!" << endl;
    }
};
int main() {
    // Демонстрация полиморфизма
    Dog myDog("Buddy");
    Cat myCat("Whiskers");
    // Даже при вызове speak() для разных типов объектов,
    // каждый выполняет свою версию
    myDog.speak();  // Вывод: Buddy лает: Гав! Гав!
    myCat.speak();