Как норвежский оборонный проект случайно произвёл революцию в программировании навсегда
Позвольте рассказать вам историю о том, как в начале 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. Изменять внутреннюю реализацию, не затрагивая внешних пользователей
Это был сдвиг парадигмы в том, как программисты думали об организации кода.
Визуализация эволюции концепций ООП
Симуляции Монте-Карло"] -->|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();
