- Что не так с процедурным программированием
- Зачем нам ооп и что это такое
- Деструкторы и финализация[править]
- Другие свойства[править]
- Инкапсуляция (private и public)[править]
- Классы и наследование[править]
- Классы, объекты и ссылки[править]
- Множественное наследование и интерфейсы[править]
- Мусорная корзина[править]
- Обработка исключений[править]
- Определение новых классов[править]
- Позднее связывание (и полиморфизм)[править]
- Файлы, модули и пакеты[править]
- Заключение: языки и программное окружение[править]
Что не так с процедурным программированием
Процедурное программирование идеально работает в простых программах, где все задачи можно решить, грубо говоря, десятком функций. Функции аккуратно вложены друг в друга, взаимодействуют друг с другом, можно передать данные из одной функции в другую.
Например, вы пишете функцию «Зарегистрировать пользователя интернет-магазина». Внутри неё вам нужно проверить его электронный адрес. Вы вызываете функцию «Проверить email» внутри функции «Зарегистрировать пользователя», и в зависимости от ответа функции вы либо регистрируете пользователя, либо выводите ошибку. И у вас эта функция встречается ещё в десяти местах. Функции как бы переплетены.
Тут приходит продакт-менеджер и говорит: «Хочу, чтобы пользователь точно знал, в чём ошибка при вводе электронного адреса». Теперь вам нужно научить функцию выдавать не просто true — false, а ещё и код ошибки: например, если в адресе опечатка, то код 01, если адрес спамерский — код 02 и так далее. Это несложно реализовать.
Вы залезаете внутрь этой функции и меняете её поведение: теперь она вместо true — false выдаёт код ошибки, а если ошибки нет — пишет «ОК».
И тут ваш код ломается: все десять мест, которые ожидали от проверяльщика true или false, теперь получают «ОК» и из-за этого ломаются.
Теперь вам нужно:
- либо переписывать все функции, чтобы научить их понимать новые ответы проверяльщика адресов;
- либо переделать сам проверяльщик адресов, чтобы он остался совместимым со старыми местами, но в нужном вам месте как-то ещё выдавал коды ошибок;
- либо написать новый проверяльщик, который выдаёт коды ошибок, а в старых местах использовать старый проверяльщик.
Задача, конечно, решаемая за час-другой.
Но теперь представьте, что у вас этих функций — сотни. И изменений в них нужно делать десятки в день. И каждое изменение, как правило, заставляет функции вести себя более сложным образом и выдавать более сложный результат. И каждое изменение в одном месте ломает три других места. В итоге у вас будут нарождаться десятки клонированных функций, в которых вы сначала будете разбираться, а потом уже нет.
Это называется спагетти-код, и для борьбы с ним как раз придумали объектно-ориентированное программирование.
Зачем нам ооп и что это такое
Всем привет.
Неделя статей на хабре посвященная ООП. Последняя статья вызвала у меня кучу эмоций и, к сожалению, очень плохих эмоций. Мне очень не понравилась статья. Почему? Потому что в ней передаются какие-то отрицательные эмоции об использовании ООП. Эмоции вызваны лишь тем, что человек не до конца понимает всю силу ООП и хочет убедить всех в том что ООП зло. Самое печальное что люди начинают прислушиваться и кидаться ужасными доводами, не имеющими ничего общего с действительностью. Я думаю что студентам такие статьи противопоказаны больше чем GoF, которых я бы давал как можно раньше. 🙂
Начнем.
Что такое ООП. ООП — это и ОО программирование и проектирование. Одно без другого бессмысленно чуть более чем полностью. Создано ООП для проектирования/программирования программных продуктов. Не для моделирования процессов. Не для проектирования протоколов, а именно для программных продуктов, для их реализации. Для упрощения системы, которая будет реализовывать протокол или бизнес-процесс или что-то еще.
Когда вы начинаете использовать ООП, первое что вы должны сделать — это начать использовать объектное мышление. Я уже когда-то говорил что это самая большая проблема ООП, научиться мыслить объектно очень сложно. И очень важно учиться это делать как можно раньше (GoF с аналогиями типа мост, конструктор, фасад очень в этом помогут). Используя объектное мышление, вы легко сможете проектировать сложные системыИспользуя объектное мышление вы легко можете решить любую задачу (очень важно что любую задачу проектирования/программирования, если ее в принципе можно решить оперируя объектами и взаимодействием между ними. Т.е. ООП без объектного мышления не позволит вам начать использовать всю силу и мощь ООП.абсолютно любую)
Пойдем дальше. Итак, нам важно мыслить объектно, для того, что бы найти нужные нам абстракции объектов для решения наших задач. Если аналогии и абстракции выбраны удачно, то мы видим очень четкую картину которая позволяет нам быстро разобраться в том, что же происходит в системе. И вот тут мы начинаем вспоминать про наследование и полиморфизм. Эти два инструмента нужны для удобного масштабирования системы без дублирования кода. Но сила этих механизмов зависит от того насколько удачные абстракции и аналогии вы выбрали. Если ваше объектное мышление не позволяет вам сформировать удобную декомпозицию объектов, то наследование и полиморфизм вам не помогут. Т.е. наследование и полиморфизм это ничто иное как инструменты, которые позволяют решить проблему масштабирования системы.
Как же эти инструменты работают? Да проще пареной репы, потому что это все основано на привычных нам вещах. Люблю простые примеры из жизни:
1. Наследование. Есть пекарь. Есть печь электрическая и газовая. Ваша задача смоделировать процесс приготовления пищи пекарем в каждой из печи. Решая задачу в лоб, у нас будет много дублирования кода из-за того, что сам процесс передачи пищи в печь и сама работа с печами идентичны для обеих печей. Но если мы включаем объектное мышление, и вспоминаем про инструмент наследование, то получаем примерно следующее (диаграмму лень рисовать, сорри):
Есть печь (абстрактная печь). У нее есть поведение — включить, выключить, увеличить или уменьшить температуру, положить чего-то, достать чего-то и состояние — температура в печи, включена или выключена. Это отличный пример абстрактного объекта в котором соблюдены принципы инкапсуляции (при реализации я их обязательно буду соблюдать). И есть пекарь, конкретный такой пекарь Иван. Он умеет работать с абстрактной печью. Т.е. смотреть температуру, включать выключать и т.д. вы поняли. Сила наследования в том, что нам не придется переписывать нашего Ивана для каждой из печей, будь то электро или газовая печь. Я думаю всем ясно почему? Получается что инструмент применен правильно.
2. Полиморфизм. Печи ведь по-разному работают. Газовая потребляет газ, электро печь — электричество. Используя полиморфизм мы легко меняем поведение в наследниках абстрактной печи.
3. Инкапсуляция. Основная фишка инкапсуляции в том, что я не должен знать, что происходит внутри моей печи. Допустим, я вызываю не метод включить печь, а меняю ее свойство включена на значение true. Что произойдет в этот момент? Если принцип инкапсуляции не соблюден, то я буду вынужден печи сказать начинай потреблять горючее, т.к. я тебя включил. Т.е. пекарь знает, что печь потребляет горючее, знает, как печь работает. Или, например, мы не можем установить температуру печи ниже или выше определенного уровня. Если не соблюдать принцип инкапсуляции, то мы должны будем говорить печи проверь-ка текущую температуру, пойдет те такая? Т.е. пекарь опять слишком много знает о печи. Геттеры и сеттеры это средства языка, которые помогут нам легко реализовать отслеживание изменений состояния. Все. Если геттеры и сеттеры пустые, значит так надо на моем уровне абстракции. Геттеры и сеттеры — не могут мешать реализации инкапсуляции, криво реализовать инкапсуляцию может проектировщик/программист.
В данном примере уровень абстракции выбран хорошо. Все занимаются своими делами, все три кита ООП работают во славу. Но стоит мне выбрать плохие абстракции, как начинается сущий кошмар. И даже есть стандарты чеклисты, которые помогут понять, хорошо ли вы выбрали абстракции и верна ли ваша декомпозиция в том ли направлении вы идете (SOLID).
Еще стали добавлять абстракцию, как еще один столп ООП. Я думаю, что это скорее верно, но уж очень попахивает КЭПом.
Высказывания про типизацию меня тоже зацепили. Дело в том, что никаких проблем в том, с кем вы сейчас работаете из наследников нет. Если на текущем уровне абстракции вам важно именно использовать печь, то вам не важно какая она. Вы получаете печь? Вы решаете свои задачи? То то и оно… Почему вы считаете что это динамическая типизация мне не понятно. Вы хотели печь? Берите. Вам нужна электрическая? Ну извините, газовая вам уже не подойдет.
Остальные примеры, которые были приведены в зацепившей меня статье, лишь примеры отвратительно выбранной абстракции и аналогии в рамках поставленной задачи. Точка.
Отдельно про DTO. DTO — это паттерн. Он позволяет создать объект, который передаст информацию другому слою, другой системе, короче куда-то чего-то передаст. Почему он не может быть рассмотрен мною как объект для меня вообще загадка. Где противоречие то? Является контейнером только? Ну и что?? Это же объект в рамках рассмотренной мною объектной модели на заданном уровне абстракции, где DTO — объект и часть декомпозиции.
Про языки тоже непонятно чего говорить. Я могу проектировать ПО используя объектный подход независимо от языка. Но если язык не реализует основные инструменты для работы с объектами, то мне будет очень сложно или невозможно реализовать спроектированную мною систему.
Еще говорят что некоторые вещи нельзя представить в виде объектов и их взаимодействия. Я уверен что это не так. Просто необходимо выбрать уровень абстракции верно. Будь то реализация протокола, слоя доступа к БД, подключения плагинов, менеджера задач, бизнес процесса, системы проектирования бизнес процессов т.е. все что угодно можно представить как объекты и их взаимодействие. Все можно реализовать как объекты и взаимодействие между ними. Хорошо это или плохо чаще всего зависит лишь от вашего умения мыслить объектно.
Резюмируя. Если вы не понимаете силу ООП, то скорее всего вам надо развивать объектное мышление.
P.S. В комментах к прошлой статье я явно много перегибал палку при обращении к некоторым людям. Приношу свои извинения.
Деструкторы и финализация[править]
Свойство: Деструктор играет роль противоположную конструктору и обычно вызывается при уничтожении объекта. Если конструктор нужен большинству классов, то только некоторые из них нуждаются в деструкторе. Деструктор в основном используется для освобождения ресурсов, зарезервированных конструктором (или другими методами во время жизни объекта). Эти ресурсы включают память, файлы, базы данных, ресурсы ОС и т. д.
C : деструкторы вызываются автоматически, когда объект выходит из области определения или когда вы удаляете объект, заведенный динамически. У каждого класса есть только один деструктор, который объявляется как ~Class(), где Class — имя класса.
OP: деструкторы похожи на деструкторы C . (Для деструкторов используется ключевое слово destructor [мое примечание — В. К.]) Object Pascal использует стандартный виртуальный деструктор, называемый Destroy. Этот деструктор вызывается стандартным методом Free.
Все объекты динамические, поэтому предполагается, что вы вызовете Free для каждого объекта, созданного вами, если у того нет владельца, отвечающего за его уничтожение. Теоретически вы можете объявить несколько деструкторов, что имеет смысл, если вы можете вызывать деструкторы в своем коде (это не делается автоматически).
Java: В Java нет деструкторов. Объекты, на которые нет ссылок, уничтожаются сборщиком мусора, который работает в виде фоновой задачи и делает это автоматически (как описывалось ранее). Прежде чем уничтожать объект, сборщик мусора должен вызвать метод finalize().
C# Как и в виртуальной машине Java, в CLR используется автоматическая сборка мусора. Как было сказано выше, существует специальный интерфейс для объектов, требующих ручного освобождения ресурсов. В C# также можно создать метод вида ~имя_класса(), который полностью аналогичен методу finalize() в Java.
Другие свойства[править]
Помимо обеспечения объектно-ориентированного программирования, эти языки предлагают другие интересные и мощные характеристики, которые дополняют поддержку ООП. В следующих разделах я просто представлю некоторые из них.
Свойство: В строго типизованных ОО языках компилятор осуществляет весь контроль типов, так что нет особой необходимости хранить информацию о классах и типах в работающей программе. Тем не менее, есть случаи (как, например, динамическое преобразование типов), которые требуют информацию о типе.
C : первоначально не поддерживал RTTI. Это было добавлено позже для динамического преобразования типа (dynamic_cast) и сделало доступной некоторую информацию о типе для классов. Вы можете запросить идентификацию типа для объекта, и проверить, принадлежат ли два объекта одному классу.
OP: поддерживает и требует много RTTI. Доступен не только контроль соответствия и динамическое преобразование типов (с помощью операторов is и as). Классы генерируют расширенную RTTI для своих published свойств, методов и полей.
Фактически это ключевое слово управляет частью генерации RTTI. Вся идея свойств, механизм потоков (файлы форм — DFM), и среда Delphi, начиная с Инспектора Объектов, сильно опирается на RTTI классов. У класса TObject есть (кроме прочих) методы ClassName и ClassType.
Java: как и в Object Pascal, в Java тоже есть единый базовый класс, помогающий следить за информацией о классе. Безопасное преобразование типов (type-safe downcast) встроено в этот язык. Метод getClass() возвращает своего рода метакласс (объект класса, описывающего классы)
, и Вы можете применить функцию getName() для того, чтобы получить строку с именем класса. Вы можете также использовать оператор instanceof. Java включает в себя расширенную RTTI для классов или интроспекцию, которая была введена для поддержки компонентной модели JavaBeans. В Java существует возможность создавать классы во время исполнения программы.
Пример: Вот синтаксис безопасного преобразования типов на всех трех языках. В случае ошибки в Delphi и Java происходит исключение, а в С возвращается нулевой указатель:
// C Dog* MyDog = dynamic_cast <Dog*> (myAnimal);
// Java Dog MyDog = (Dog) myAnimal;
// Object Pascal myDog := myAnimal as Dog;
Инкапсуляция (private и public)[править]
Свойство: Общим элементом всех трех языков является присутствие трех спецификаторов доступа, указывающих на различные уровни инкапсуляции класса: public, protected, и private. Public означает: видимый любым другим классом, protected означает: видимый производными классами, private означает: отсутствие видимости извне. В деталях, однако, есть различия.
C : В C вы можете использовать ключевое слово friend для обхода инкапсуляции. Видимость по умолчанию для класса — private, для структур — public.
OP: В Object Pascal private и protected относятся только к классам других юнитов. В терминах C , класс является дружественным для любого другого класса, определенного в том же юните (или файле исходного кода). В Delphi есть еще один модификатор доступа — published, который генерирует информацию времени выполнения (RTTI) об элементах.
Java: В Java отличие синтаксиса в том, что модификатор доступа повторяется для каждого элемента класса. А конкретнее, по умолчанию в Java используется friendly, это значит, что элемент видим для других классов этого же пакета (или файла исходного кода, как в OP). Подобным образом, protected означает видимость для подклассов.
C#: В CLR используется пять уровней доступа к членам класса, которые в C# обозначаются как private, protected, public, internal, protected internal. Первые три аналогичны соответствующим модификаторам в C , Object Pascal и Java. Модификатор internal ограничивает видимость текущей сборкой (т. е. исполняемым файлом или файлом динамической библиотеки, к которому относится данный класс), protected internal разрешат также доступ в унаследованных классах из других сборок.
Классы и наследование[править]
Свойство: Наследование у классов — одно из оснований ООП. Оно может быть использовано для выражения генерализации или специализации. Основная идея в том, что вы определяете новый тип, расширяя или модифицируя существующий, другими словами, производный класс обладает всеми данными и методами базового класса, новыми данными и методами и, возможно, модифицирует некоторые из существующих методов.
Различные ОО языки используют различные жаргоны для описания этого механизма (derivation, inheritance, sub-classing), для класса, от которого вы наследуете (базовый класс, родительский класс, суперкласс) и для нового класса (производный класс, дочерний класс, подкласс).
C : C использует слова public, protected, и private для определения типа наследования и чтобы спрятать наследуемые методы или данные, делая их приватными или защищёнными. Хотя публичное наследование наиболее часто используется, по умолчанию берётся приватное.
OP: Object Pascal при наследовании использует не ключевые слова, а специальный синтаксис, добавляя в скобках имя базового класса. Этот язык поддерживает только один тип наследования, который в C называется публичным. Как мы увидим позднее, классы OP происходят от одного общего базового класса.
Java: Java использует слово extends для выражения единственного типа наследования, соответствующего публичному наследованию в C . Java не поддерживает множественное наследование. Классы Java тоже происходят от общего базового класса.
C#: C#, как и Java, не поддерживает множественное наследование. Синтаксис наследования несколько отличается от остальных рассматриваемых языков:
Примечание: Конструкторы и инициализация базового класса. В C , C# и Java у конструкторов наследующих классов сложная структура. В Object Pascal за инициализацию базового объекта отвечает программист. Это довольно сложный раздел, поэтому я пропустил его в этой статье.
Классы, объекты и ссылки[править]
Свойство: Так как мы обсуждаем языки ООП, то после этого введения, начнём обсуждать классы и объекты. Я надеюсь, что каждый ясно понимает разницу между этими двумя терминами. В двух словах, класс — это тип данных, а объект — экземпляр типа класс.
C : если у нас есть класс MyClass с методом MyMethod, мы можем написать:
и получить объект класса MyClass с именем Obj. Память для этого объекта обычно выделяется в стеке, и вы можете сразу начать использовать объект, как это сделано во второй строке.
Также возможно выделить память для объекта в куче и оперировать указателем на объект:
Java: подобная инструкция выделяет место только для хэндла объекта, а не для самого объекта:
Прежде чем использовать объект, вы должны вызвать «new» для выделения под него памяти. Конечно, вы можете объявить и проинициализировать объект в одном предложении, избегая использования неинициализированных объектных хэндлов:
Object Pascal: использует подобный подход, но требует отдельных предложений для объявления и инициализации:
В C# работа с объектами классов и структур внешне выглядит похоже:
Но все же есть одно принципиальное отличие — память под объект структуры выделяется статически, поэтому вызов конструктора перед вызовом метода не является обязательным — объект уже существует, хотя его поля могут содержать «мусорные» значения.
Замечание: Если вам кажется, что ссылочно-объектная модель требует большей работы от программиста, обратите внимание на то, что в C вы часто должны использовать указатели и ссылки на объекты. Только используя указатели и ссылки, вы можете добиться полиморфизма. Ссылочно-объектная модель, наоборот, делает использование указателей подразумеваемым, скрывая от программиста сложность этого подхода. В Java, в частности, официально указателей нет, хотя они там повсюду. Только программисты не имеют над ними прямого контроля, и поэтому, из соображений безопасности, не могут попасть в произвольное место памяти.
Множественное наследование и интерфейсы[править]
Свойство: Некоторые ОО языки допускают наследование более чем одному базовому классу. Другие языки позволяют вам наследовать только от одного класса, но дополнительно позволят вам наследовать также от многочисленных интерфейсов или чисто абстрактных классов, то есть классов, состоящих только из виртуальных функций.
C : C — единственный из трех языков, поддерживающий множественное наследование. Некоторые программисты считают положительным фактом, другие — отрицательным, и я не буду вмешиваться сейчас в эту дискуссию. Определенно, что множественное и повторяющееся наследование влечет за собой такие понятия, как виртуальные базовые классы, которые не легко освоить, хотя они предоставляют мощную технику.
Java: Java, как и Object Pascal, не поддерживает множественное наследование, но полностью поддерживает интерфейсы. Методы интерфейсов допускают полиморфизм, и Вы можете использовать объект, осуществляющий интерфейс, когда ожидается интерфейсный объект.
Класс может наследовать или расширить всего лишь один базовый класс, но может осуществить (это — ключевое слово) многочисленные интерфейсы. Хотя это не было спланировано заранее, интерфейсы Java очень хорошо укладываются в модель COM. Вот пример интерфейса:
public interface CanFly { public void Fly();
}
public class Bat extends Animal implements CanFly { public void fly() { /* the bat flies... */ }
}OP: Delphi 3 ввел в Object Pascal понятие, подобное интерфейсам Java, но эти интерфейсы строго соответствуют COM (хотя технически возможно использовать их в обычных не-COM программах). Интерфейсы формируют иерархию, отдельную от классов, но, как и в Java, класс может наследовать одному базовому классу и осуществлять различные интерфейсы.
Отображение методов класса на методы интерфейсов, осуществляемых классом, является одной из наиболее сложных частей языка Object Pascal. Delphi 4 добавляет к этой структуре возможность передать реализацию интерфейса подобъекту, делая эту технику почти такой же эффективной, как и множественное наследование.
Мусорная корзина[править]
Свойство: Если вы создали и не использовали объект, вам нужно уничтожить его, чтобы не занимать неиспользуемую память.
C :
В C уничтожить объект, расположенный в стеке, довольно просто. С другой стороны, уничтожение объектов, созданных динамически, зачастую является сложной проблемой. Есть много решений, включая подсчет ссылок и «интеллектуальные» указатели, но ни один из них не даёт простого решения. Первое впечатление для C программистов, что использование ссылочно-объектной модели сделает ситуацию только хуже.
Java:
Это, конечно, не касается Java, так как виртуальная машина запускает алгоритм сборки мусора (в фоновом процессе, согласно теории Java; или начинает этот процесс после того, как ненадолго остановит программу, как в большинстве реальных JVM).
Сбор мусора происходит без участия программиста, но он может неблагоприятно влиять на эффективность выполнения приложения. Отсутствие явно записываемых деструкторов может приводить к ошибкам в завершающем коде (см. также главу о деструкторах и методе finalize() ниже).
OP:
В Object Pascal, наоборот, нет механизма сбора мусора. Однако компоненты Delphi поддерживают идею владельца (owner) объекта: владелец становится ответственным за уничтожение всех объектов, которыми он владеет. Это делает управление уничтожением объекта очень простым и прямым.
C#:
В CLR (среде выполнения .NET) имеется сборщик мусора, аналогичный используемому в виртуальной машине Java. Поэтому программисту также не приходится заботиться об удалении созданных объектов. Однако, имеется специальный интерфейс IDisposable, которым «обозначаются» объекты, требующие ручной деинициализации (например, закрытия потоков ввода-вывода).
Обработка исключений[править]
Свойство: Основная идея обработки исключений — упростить код обработки ошибок в программе, предоставив стандартный встроенный механизм, с целью сделать программы более устойчивыми. Обработка исключений — это тема, требующая отдельного рассмотрения, поэтому я только очерчу некоторые ключевые элементы и различия.
C : C использует ключевое слово throw для генерации исключения, try для отметки охраняемого блока и catch для записи кода обработки исключения. Исключения — объекты специального класса, которые могут образовывать некоторую иерархию во всех трёх языках.
OP: Object Pascal использует подобные ключевые слова: raise, try, и except и обладает подобными свойствами. Единственное существенное отличие состоит в том, что опустошение стека не производится, просто потому, что в стеке нет объектов. Кроме того, вы можете добавить в конце блока try слово finally, отмечая блок, который должен выполняться всегда, независимо от того, было или нет вызвано исключение. В Delphi классы исключений — производные Exception.
Java: Использует ключевые слова C , но ведёт себя как Object Pascal, включая дополнительное ключевое слово finally. (Это общее свойство всех языков со ссылочно-объектной моделью, оно включено Borland также и в C Builder 3.) Присутствие алгоритма сборки мусора ограничивает использование finally в классе, который распределяет другие ресурсы, кроме памяти.
Также Java строже требует, чтобы все функции, которые могут вызвать исключение, описывали в соответствующем блоке, какие исключения могут быть вызваны функцией. Эти описания исключений проверяются компилятором, что является хорошим свойством, даже если оно подразумевает некоторую дополнительную работу для программиста. В классах Java объекты-исключения должны наследовать класс Throwable.
Определение новых классов[править]
Свойство: Теперь, когда мы рассмотрели, как создавать объекты для существующих классов, мы можем обратиться к определению новых классов. Класс — это просто набор методов, работающих с определёнными локальными данными.
C :
Вот C синтаксис определения простого класса:
А вот определение пользовательского метода-инициализатора:
Java:
Синтаксис Java похож на синтаксис C :
Основная разница состоит в том, что код каждого метода пишется там же, где он объявляется (при этом функции не становятся вставными (inline), как в C ), и в том, что вы можете инициализировать элементы данных класса. Фактически, если вы не сделаете этого, то Java проинициализирует все элементы данных за вас, используя значения по умолчанию.
OP:
В Object Pascal синтаксис определения класса другой, но похожий скорее на C , чем на Java:
Как видите, здесь есть синтаксические отличия: методы определяются с ключевыми словами function и procedure, методы без параметров не используют скобок, методы просто объявляются внутри определения класса, тогда как определяются позже, как это обычно делается в C . Однако Pascal использует нотацию с точкой, а C — оператор :: (недоступный в Object Pascal и Java).
C#
Синтаксис C# очень похож на Java (в данном примере идентичен):
Примечание: Доступ к текущему объекту. В ОО языках методы отличаются от глобальных функций тем, что у них присутствует скрытый параметр, ссылка или указатель на объект, с которым мы работаем. Просто эта ссылка на текущий объект по-разному называется.
Позднее связывание (и полиморфизм)[править]
Свойство: Когда различные классы в иерархии переопределяют некоторый метод, очень полезна возможность ссылаться на общий объект этих классов (благодаря совместимости подклассов) и вызывать этот метод, результатом чего будет вызов метода надлежащего класса.
C : В C позднее связывание доступно только для виртуальных методов (вызов которых становится немного медленнее). Метод, объявленный в базовом классе как виртуальный (virtual), поддерживает это свойство (но только если описания методов совпадают). Обычные, не виртуальные методы не позволяют позднее связывание, как и OP.
OP: В Object Pascal позднее связывание вводится с помощью ключевых слов virtual и dynamic (разница между ними только в оптимизации). В производных классах переопределённые методы должны быть отмечены словом override (это заставляет компилятор проверять описание метода).
Java: В Java все методы используют позднее связывание, если вы не отметите их явно как final. Финальные методы не могут быть переопределены и вызываются быстрее. В Java написание методов с нужной сигнатурой жизненно важно для обеспечения полиморфизма.
Тот факт, что в Java по умолчанию используется позднее связывание, тогда как в C стандартом является раннее связывание, — явный признак разного подхода этих двух языков: C временами жертвует ОО моделью в пользу эффективности, тогда как Java — наоборот
Примечание: Позднее связывание для конструкторов и деструкторов. Object Pascal, в отличие от других двух языков, позволяет определять виртуальные конструкторы. Все три языка поддерживают виртуальные деструкторы.
Файлы, модули и пакеты[править]
Свойство: Важное различие между тремя языками заключается в организации исходного кода в файлах. Все три языка используют файлы в качестве стандартного механизма для запоминания исходного кода классов (в отличие от других ОО языков, таких как Smalltalk), но компилятор C , в отличие от OP или Java, не понимает файлов. Эти же два языка работают с идеей модулей, хотя называют их по-разному.
C : В C программист обычно помещает определение класса в файл объявлений, а определение методов — в отдельный файл кода. Обычно у этих двух файлов одинаковые имена и различные расширения. Компилируемый блок, как правило, ссылается (включает в себя) на свой файл объявлений и на файлы объявлений тех классов (или функций), на которые ссылается код.
Все эти соглашения не утруждают компилятор. Это значит, что линкеру предстоит большая работа, потому что компилятор не может знать, в каком другом модуле может быть определен нужный метод. Однако также существуют пространства имен, позволяющие во многих случаях избежать неоднозначностей. Например, классы и прочие элементы библиотеки STL находятся в пространстве имен std.
OP: В Object Pascal каждый файл исходного кода называется unit, и он делится на две
части: интерфейс и реализация, отмечаемые соответственно ключевыми словами interface и implementation. Секция интерфейса включает в себя определения классов (с объявлениями методов), а секция реализации должна включать в себя определения методов, объявленных в интерфейсе.
uses Windows, Form, MyFile;
Java: В Java каждый файл исходного кода или единица компиляции компилируется отдельно. Затем вы можете отметить группу единиц компиляции как части одного пакета. В отличие от двух других языков, вы пишете весь код методов тут же при объявлении класса. При включении какого-либо файла предложением import, компилятор читает только public объявления, а не весь код:
import where.Myclass; import where.* // все классы
C#: На платформе CLR (.NET) код разделен на сборки, которые могут представлять собой динамически подключаемую библиотеку либо исполняемый файл. Кроме того, для группировки типов с целью сведения к минимуму вероятности конфликта имен, существуют пространства имен, которые, однако, никак не связаны со сборками или файлами исходного кода.
Примечание: Модули как пространство имён. Другим важным отличием Java и OP является их способность читать откомпилированные файлы и извлекать из них определения, как бы извлекая заголовки из скомпилированного кода. С другой стороны, для преодоления отсутствия модулей C включает пространство имен (namespace).
Заключение: языки и программное окружение[править]
Как я упоминал в начале, хотя я пытался исследовать эти языки, только сравнивая синтаксические и семантические характеристики, важно рассмотреть их в соответствующем контексте. Языки нацелены на различные потребности, что означает, что они решают разные проблемы разными способами и используются в очень разных средах программирования.
Цель C — мощность и контроль за счет сложности. Целью Delphi является легкое, визуальное программирование (не отказываясь от мощности) и прочная связь с Windows. Цель Java — мобильность, даже за счет некоторого отказа от скорости, и распределённые приложения или исполняемое содержание WWW (хотя это, конечно, — не Microsoft-овский взгляд на Java!).
Можно определить, что успех этих трех языков зависит не от технических характеристик, которые я включил в эту статью. Финансовый статус Borland, операционная система управления Microsoft, популярность Sun в мире Internet, тот факт, что Java рассматривается как anti-Microsoft-овский язык, будущее браузеров Паутины и Win32 API, роль и признание модели ActiveX (из-за связанной с ней проблемой безопасности) и три уровня архитектуры Delphi — вот показатели, которые могли повлиять на ваш выбор сильнее, чем технические элементы.
Просто имейте в виду, что «модный» становится все более частым словом в компьютерном мире. Как пользователи хотят иметь инструменты этого года (вероятно, по этой причине операционные системы называются по тому году, в котором они выпущены), программисты любят работать с последним языком программирования и первыми овладеть им.
Можно наверняка утверждать, что Java — не последний из языков ООП. Через несколько следующих лет найдется кто-то с новым модным языком, и все прыгнут в этот поезд, думая, что нельзя отставать, и забывая, что большинство программистов в мире всё ещё печатают на клавиатуре на добром старом Cobol! (С последним утверждением можно поспорить.)
- Брюс Эккель. Thinking in Java (4th edition) (Философия Java, 4-е издание).


