Сравнение ООП языков: Java, C++, Object Pascal (документация)

Автор: marco cantu, wintech italia srl Перевод: Виктор Карев

Введение

Ява - популярный в Интернете язык, c++, возможно, всё ещё самый распространённый язык ООП, а object pascal - язык, используемый фирмой inprise (бывшей borland) внутри delphi. Хотя это и не очевидно, но эти три языка имеют много общего. Цель данной работы - исследовать и сравнить технические аспекты этих трех языков. Я не собираюсь выяснять, какой из языков лучше, потому что это в значительной степени зависит от того, для чего вы хотите его использовать. Для понимания данной статьи требуется минимальное знание об одном из ОО языков или, по крайней мере, базовые знания о концепции ООП в целом. Я буду описывать, почему определённая языковая особенность важна, а затем перейду к её сравнению в упомянутых трёх языках. Я не собираюсь показывать вам, как использовать эти языковые особенности в конкретных примерах. Я не хочу научить вас ООП. Я только хочу сравнить эти языки.

Ключевые черты ООП

Объектно-ориентированное Программирование (ООП) не является новой техникой программирования. Его истоки восходят к Симуле-67, хотя первое полное осуществление относится к smalltalk-80. ООП стало популярным во второй половине 80-х в таких различных языках, как c++, objective-c (другое расширение c), object и turbo pascal, clos (ОО-расширение lisp), eiffel, ada (в её последних воплощениях) и недавно - в Яве. В этой работе я сосредоточусь только на c++, object pascal и java, иногда упоминая и другие языки ООП.

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

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

Классы, наследование и полиморфизм - фундаментальные свойства, требуемые от языка, претендующего называться объектно-ориентированным. (Языки, не имеющие наследования и полиморфизма, но имеющие только классы, обычно называются основанными на классах.)

Необходимо сказать, что различные ОО языки используют совершенно разные подходы. Мы можем различать ОО языки, сравнивая механизм контроля типов, способность поддерживать различные программные модели и какие объектные модели они поддерживают. Затем я перейду к конкретным языковым особенностям.

Контроль во время компиляции и во время выполнения

Свойство: Языки программирования можно оценить по тому, насколько они строги к типам. Контроль типов включает проверку существования вызываемых методов, типов их параметров, проверку границ массивов и т.д. c++, java, и object pascal предпочитают более или менее широкий контроль во время компиляции. С++, возможно, наименее точен в этом отношении, тогда как java использует проверку типов наиболее широко. Причина этого заключается в том, что c++ обеспечивает совместимость с c-языками, которые поддерживают слабую форму проверки типов во время компиляции. Например, c и c++ считают, что все арифметические типы совместимы (хотя присвоение float целой переменной вызовет предупреждение компилятора). В object pascal и java логическое значение не целое, а символ - еще один отличный и несовместимый тип.

Тот факт, что виртуальная машина java "интерпретирует" байтовый код во время выполнения, не означает, что этот язык отказывается от проверки типов во время компиляции. Наоборот, в этом языке проверка наиболее тщательна. Другие ОО языки, такие как smalltalk и clos, наоборот, склонны большинство, если не все проверки типов осуществлять во время исполнения.

Чисто объектно-ориентированные и гибридные языки

Свойство: Ещё одно различие лежит между чистыми и гибридными ОО языками. Чистые ОО языки - это те, которые позволяют использовать только одну модель программирования, объектно-ориентированную. Вы можете объявлять классы и методы, но не можете завести глобальные переменные и обычные функции и процедуры старого типа.

Среди трех наших языков, только java является чистым ОО языком (как eiffel и smalltalk). На первый взгляд, это кажется положительной идеей. Однако она ведет к тому, что вы используете кучу статических методов и статических данных, что не так уж отличается от использования глобальных функций и данных, за исключением более сложного синтаксиса. По моему мнению, чистые ОО языки дают преимущество новичкам в ООП, потому что программист вынужден использовать (и учить) модель ООП. c++ и object pascal, наоборот, - типичные примеры гибридных языков, которые позволяют программистам использовать при необходимости традиционный подход c или pascal.

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

Простая объектная модель и ссылочно-объектная модель

Свойство: Третий элемент, по которому различаются языки ООП - их объектная модель. Некоторые традиционные языки ООП позволяют программистам создавать объекты в стеке, в куче (в хипе - heap) или в статической памяти. В этих языках переменная типа класс соответствует объекту в памяти. Так работает c++.

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

Классы, объекты и ссылки

Свойство: Так как мы обсуждаем языки ООП, то после этого введения, начнём обсуждать классы и объекты. Я надеюсь, что каждый ясно понимает разницу между этими двумя терминами. В двух словах, класс - это тип данных, а объект - экземпляр типа класс. Как нам теперь использовать объекты в языках, использующих различные объектные модели?

c++: в С++, если у нас есть класс myclass с методом mymethod, мы можем написать:

    myclass obj;
    obj.mymethod();
и получить объект класса myclass с именем obj. Память для этого объекта обычно выделяется в стеке, и вы можете сразу начать использовать объект, как это сделано во второй строке. java: в java подобная инструкция выделяет только место для хэндла объекта, а не для самого объекта:
    
    myclass obj;
    obj = new myclass();
    obj.mymethod();
Прежде чем использовать объект, вы должны вызвать "new" для выделения под него памяти. Конечно, вы можете объявить и проинициализировать объект в одном предложении, избегая использования неинициализированных объектных хэндлов:
    myclass obj = new myclass();
    obj.mymethod();
op: object pascal использует подобный подход, но (к сожалению) требует отдельных предложений для объявления и инициализации:
    
    var 
    obj: myclass;
    begin 
      obj := myclass.create; 
      obj.mymethod;
Замечание: Если вам кажется, что ссылочно-объектная модель требует большей работы от программиста, обратите внимание на то, что в c++ вы часто должны использовать указатели и ссылки на объекты. Только используя указатели и ссылки, вы можете добиться полиморфизма. Ссылочно-объектная модель, наоборот, делает использование указателей подразумеваемым, скрывая от программиста сложность этого подхода. В java, в частности, официально указателей нет, хотя они там повсюду. Только программисты не имеют над ними прямого контроля, и поэтому, из соображений безопасности, не могут попасть в произвольное место памяти.

Мусорная корзина

Свойство: Если вы создали и использовали объект, вам нужно уничтожить его, чтобы не занимать неиспользуемую память.

c++: В c++ уничтожить объект, расположенный в стеке, довольно просто. С другой стороны, уничтожение объектов, созданных динамически, зачастую является сложной проблемой. Есть много решений, включая подсчет ссылок и "интеллектуальные" указатели, но ни один из них не даёт простого решения. Первое впечатление для c++ программистов, что использование ссылочно-объектной модели сделает ситуацию только хуже.

java: Это, конечно, не касается java, так как виртуальная машина запускает алгоритм сборки мусора (в фоновом процессе, согласно теории java; или начинает этот процесс после того, как ненадолго остановит программу, как в большинстве реальных jvm). Сбор мусора предоставляется программистам бесплатно, но он может неблагоприятно влиять на эффективность выполнения приложения. Отсутствие явно записываемых деструкторов может приводить к ошибкам в завершающем коде (см. также главу о деструкторах и методе finalize() ниже).

op: В object pascal, наоборот, нет механизма сбора мусора. Однако компоненты delphi поддерживают идею владельца (owner) объекта: владелец становится ответственным за уничтожение всех объектов, которыми он владеет. Это делает управление уничтожением объекта очень простым и прямым. delphi также использует механизм подсчёта ссылок для строк, динамических массивов и интерфейсов, освобождая объект в памяти, когда него нет больше ссылок.

Определение новых классов

Свойство: Теперь, когда мы рассмотрели, как создавать объекты для существующих классов, мы можем обратиться к определению новых классов. Класс - это просто набор методов, работающих с определёнными локальными данными.

c++: Вот c++ синтаксис определения простого класса:

    class date 
    {
    private: 
      int dd; 
      int mm; 
      int yy;
    public:
      void init (int d, int m, int y);
      int day ();
      int month ();
      int year ();
    };
А вот определение одного из методов:
    void date::init (int d, int m, int y)
    { 
      dd = d;
      mm = m;
      yy = y;
    }
java: Синтаксис java очень похож на синтаксис c++:
    class date 
    { 
      int dd = 1; 
      int mm = 1; 
      int yy = 1;
      public void init (int d, int m, int y) {
        dd = d; mm = m; yy = y;}
      public int day () {return dd;}

      public int month () {return mm;}
      public int year () {return yy;}
    }
Основная разница состоит в том, что код каждого метода пишется там же, где он объявляется (при этом функции не становятся вставными (inline), как в c++), и в том, что вы можете инициализировать элементы данных класса. Фактически, если вы не сделаете этого, то java проинициализирует все элементы данных за вас, используя значения по умолчанию.

op: В object pascal синтаксис определения класса другой, но похожий скорее на c++, чем на java:

    type 
      date = class 
      private 
        dd, mm, yy: integer; 
      public 
        procedure init (d, m, y: integer); 
        function month: integer; 
        function day: integer; 
        function year: integer; 
      end;
    procedure date.init (d, m, y: integer);
    begin 
      dd := d; 
      mm := m; 
      yy := y;
    end;
    
    function date.day: integer;
    begin 
      result := dd;
    end;
Как видите, здесь есть синтаксические отличия: методы определяются с ключевыми словами function и procedure, методы без параметров не используют скобок, методы просто объявляются внутри определения класса, тогда как определяются позже, как это обычно делается в c++. Однако pascal использует нотацию с точкой, а c++ - оператор :: (недоступный в object pascal и java). Примечание: Доступ к текущему объекту. В ОО языках методы отличаются от глобальных функций тем, что у них присутствует скрытый параметр, ссылка или указатель на объект, с которым мы работаем. Просто эта ссылка на текущий объект по-разному называется. Это this в c++ и java, self в object pascal.

Конструкторы

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

c++: В c++, так же, как и в java, имя конструктора совпадает с именем класса. Если вы не определили никакого конструктора, компилятор синтезирует конструктор по умолчанию, добавляя его к классу. В обоих этих языках вы можете завести несколько конструкторов благодаря перегрузке функций. java: Всё работает как в c++, хотя конструкторы называются также инициализаторами. Это подчеркивает тот факт, что объект создаёт виртуальная машина java, тогда как код, который вы пишете в конструкторе, просто инициализирует свежесозданный объект. (То же самое фактически происходит и в object pascal.)

op: В object pascal вы используете специальное ключевое слово constructor и можете дать конструктору любое имя. Хотя borland в delphi 4 добавила поддержку перегрузки методов, программисты всё ещё дают разным конструкторам разные имена. В object pascal у каждого класса по умолчанию есть конструктор create (наследуемый от tobject), если вы не перегрузите его конструктором с тем же именем и, возможно, другими параметрами. Этот конструктор, как мы увидим позднее, просто наследуется от общего базового класса.

Деструкторы и финализация

Свойство: Деструктор играет роль противоположную конструктору и обычно вызывается при уничтожении объекта. Если конструктор нужен большинству классов, только некоторые из них нуждаются в деструкторе. Деструктор в основном должен освободить ресурсы, зарезервированные конструктором (или другими методами во время жизни объекта). Эти ресурсы включают память, файлы, базы данных, ресурсы windows и т. д.

c++: В c++ деструкторы автоматически вызываются, когда объект выходит из области определения или когда вы удаляете объект, заведенный динамически. У каждого класса есть только один деструктор.

op: В object pascal деструкторы похожи на деструкторы c++. /Для деструкторов используется ключевое слово destructor (мое примечание - В.К.)/ object pascal использует стандартный виртуальный деструктор, называемый destroy. Этот деструктор вызывается стандартным методом free. Все объекты динамические, поэтому предполагается, что вы вызовете free для каждого объекта, созданного вами, если у того нет владельца, отвечающего за его уничтожение. Теоретически вы можете объявить несколько деструкторов, что имеет смысл, поскольку вы можете вызывать деструкторы в своем коде (это не делается автоматически).

java: В java нет деструкторов. Объекты, на которые нет ссылок, уничтожаются сборщиком мусора, который работает в виде фоновой задачи (как описывалось ранее). Прежде чем уничтожать объект, сборщик мусора должен вызвать метод finalize(). Однако нет никакой гарантии, что этот метод вызывается в каждой jvm. По этой причине, если вам нужно освободить ресурсы, вы должны добавить какой-нибудь метод для этого, и убедиться, что он вызывается (эти дополнительные усилия не нужны в других ОО языках).

Инкапсуляция (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 означает видимость для подклассов, тогда как комбинация private protected соответствует protected в c++.

Файлы, юниты и пакеты

Свойство: Важное различие между тремя языками заключается в организации исходного кода в файлах. Все три языка используют файлы в качестве стандартного механизма для запоминания исходного кода классов (в отличие от других ОО языков, таких как smalltalk), но компилятор c++, в отличие от op или java, не понимает файлов. Эти же два языка работают с идеей модулей, хотя называют их по-разному.

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

op: В object pascal каждый файл исходного кода называется unit, и он делится на две части: интерфейс и исполнение, отмечаемые соответственно ключевыми словами interface и implementation. Секция интерфейса включает в себя определения классов (с объявлениями методов), а секция исполнения должна включать в себя определения методов, объявленных в интерфейсе. Писать фактический код в секции интерфейса нельзя. Вы можете сослаться на объявления другого файла, используя предложение uses. Этим включается в компиляцию интерфейс того файла:

    uses
    windows, form, myfile;
java: В java каждый файл исходного кода или единица компиляции компилируется отдельно. Затем вы можете отметить группу единиц компиляции как части одного пакета. В отличие от двух других языков, вы пишете весь код методов тут же при объявлении класса. При включении какого-либо файла предложением import, компилятор читает только public объявления, а не весь код:

    import where.myclass;
    import where.* // все классы
Примечание: Модули как пространство имён. Другим важным отличием java и op является их способность читать откомпилированные файлы и извлекать из них определения, как бы извлекая заголовки из скомпилированного кода. С другой стороны, для преодоления отсутствия модулей c++ включает пространство имен (namespace). В java и op, когда два имени конфликтуют, вы можете просто использовать имя модуля в качестве префикса. Это не требует дополнительной работы по определению пространств имен, а просто включено в языки.

Методы/данные класса и объекта класса

Свойство: ОО языки обычно разрешают заводить методы и данные, относящиеся к классу целиком, а не к отдельным объектам. Метод класса обычно может быть вызван как для объекта класса, так и применён к классу в целом. Данные класса не повторяются для каждого объекта, а разделяются между всеми объектами данного типа.

c++: В c++ методы и данные класса отмечаются ключевым словом static. Данные класса должны быть проинициализированы специальным объявлением, ещё одной уступкой отсутствию модулей.

op: В op допустимы только методы класса, которые отмечаются словом class. Данные класса можно заменить, так сказать, приватными глобальными переменными в секции исполнения юнита, описывающего класс. java: java использует то же слово, что и c++, static. Статические методы используются очень часто (и даже слишком) из-за отсутствия глобальных функций. Статические данные можно инициализировать прямо в объявлении класса.

Классы и наследование

Свойство: Наследование у классов - одно из оснований ООП. Оно может быть использовано для выражения генерализации или специализации. Основная идея в том, что вы определяете новый тип, расширяя или модифицируя существующий, другими словами, производный класс обладает всеми данными и методами базового класса, новыми данными и методами и, возможно, модифицирует некоторые из существующих методов. Различные ОО языки используют различные жаргоны для описания этого механизма (derivation, inheritance, sub-classing), для класса, от которого вы наследуете (базовый класс, родительский класс, суперкласс) и для нового класса (производный класс, дочерний класс, подкласс).

c++: c++ использует слова public, protected, и private для определения типа наследования и чтобы спрятать наследуемые методы или данные, делая их приватными или защищёнными. Хотя публичное наследование наиболее часто используется, по умолчанию берётся приватное. Как мы увидим далее, c++ - единственный из этих трех языков, поддерживающий множественное наследование. Вот пример синтаксиса наследования:

    class dog: public animal 
    {
    ... 
    };
op: object pascal при наследовании использует не ключевые слова, а специальный синтаксис, добавляя в скобках имя базового класса. Этот язык поддерживает только один тип наследования, который в c++ называется публичным. Как мы увидим позднее, классы op происходят от одного общего базового класса.
    type 
    dog = class (animal) 
    ... 
    end;
java: java использует слово extends для выражения единственного типа наследования, соответствующего публичному наследованию в c++. java не поддерживает множественное наследование. Классы java тоже происходят от общего базового класса.
    class dog extends animal 
    {
    ...
    }
Примечание: Конструкторы и инициализация базового класса. И в c++, и в java у конструкторов наследующих классов сложная структура. В object pascal за инициализацию базового объекта отвечает программист. Это довольно сложный раздел, поэтому я пропустил его в этой статье. Вместо этого я сосредоточусь на общем базовом классе, множественном наследовании, интерфейсах, позднем связывании и других родственных предметах.

Предок всех классов

Свойство: В некоторых ОО языках каждый класс происходит по крайней мере от некоторого базового класса по умолчанию. Этот класс, часто называемый object, или подобно этому, обладает некоторыми основными способностями, доступными всем классам. Фактически, все другие классы в обязательном порядке ему наследуют. Этот подход является общим ещё и потому, что так первоначально делалось в smalltalk.

c++: Хотя язык c++ и не поддерживает такое свойство, многие структуры приложений базируются на нём, вводя идею общего базового класса. Пример тому - mfc с его классом coobject. Фактически это имело большой смысл вначале, когда языку не хватало шаблонов. op: Каждый класс автоматически наследует классу tobject. Так как язык не поддерживает множественное наследование, все классы формируют гигантское иерархическое дерево. Класс tobject поддерживает rtti и обладает некоторыми другими возможностями. Общей практикой является использование этого класса, когда вам нужно передать объект неизвестного типа. java: Как и в op, все классы безоговорочно наследуют классу object. И в этом языке у общего класса тоже есть некоторые ограниченные свойства и небольшая поддержка rtti.

Доступ к методам базового класса

Свойство: Когда вы пишете метод класса или перекрываете метод базового класса, вам нередко надо сослаться на методы базового класса. Если этот метод переопределен в производном классе, то, используя его имя, вы получите новую версию. В ОО языках есть некоторые приёмы или ключевые слова, позволяющие решить эту проблему.

c++: В c++ для указания нужного класса можно использовать оператор (::). Вы можете получить доступ не только к методам базового класса, но к классам выше по иерархии. Это очень мощная техника, но она создаёт проблемы, когда вы добавляете в иерархию промежуточный класс.

op: В object pascal для этой цели есть специальное слово inherited. После этого слова вы можете написать имя метода базового класса или (в некоторых случаях) просто использовать это ключевое слово для доступа к соответствующему методу базового класса.

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

Совместимость подтипов

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

c++: В c++ правило совместимости подтипов справедливо только для указателей и ссылок, но не для обычных объектов. Фактически, у различных объектов разный размер, поэтому их нельзя расположить на том же месте в памяти.

op: Благодаря ссылочно-объектной модели, совместимость подтипов возможна для каждого объекта. Более того, все объекты совместимы по типу с tobject.

java: java использует ту же модель, что и object pascal. Примечание: Полиморфизм. Совместимость подтипов особенно важна для позднего связывания и полиморфизма, как это показано в следующей секции.

Позднее связывание (и полиморфизм)

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

c++: В c++ позднее связывание доступно только для виртуальных методов (вызов которых становится немного медленнее). Метод, объявленный в базовом классе как виртуальный (virtual), поддерживает это свойство (но только если описания методов совпадают). Обычные, не виртуальные методы не позволяют позднее связывание, как и op.

op: В object pascal позднее связывание вводится с помощью ключевых слов virtual и dynamic (разница между ними только в оптимизации). В производных классах переопределённые методы должны быть отмечены словом override (это заставляет компилятор проверять описание метода). Рациональное объяснение этой особенности op состоит в том, что разрешается больше изменений в базовом классе и предоставляет некоторый дополнительный контроль во время компиляции.

java: В java все методы используют позднее связывание, если вы не отметите их явно как final. Финальные методы не могут быть переопределены и вызываются быстрее. В java написание методов с нужной сигнатурой жизненно важно для обеспечения полиморфизма. Тот факт, что в java по умолчанию используется позднее связывание, тогда как в c++ стандартом является раннее связывание, - явный признак разного подхода этих двух языков: c++ временами жертвует ОО моделью в пользу эффективности, тогда как java - наоборот Примечание: Позднее связывание для конструкторов и деструкторов. object pascal, в отличие от других двух языков, позволяет определять виртуальные конструкторы. Все три языка поддерживают виртуальные деструкторы.

Абстрактные методы и классы

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

c++: В c++ абстрактные методы или чисто виртуальные функции получаются добавлением так называемого чистого описателя (=0) в определение метода. Абстрактные классы являются просто классами с одним или более абстрактным методом (или наследующие их). Вы не можете создать объект абстрактного класса.

op: object pascal для выделения этих методов использует ключевое слово abstract. Кроме того, абстрактными классами являются классы, имеющие или наследующие абстрактные методы. Вы можете создать объект абстрактного класса (хотя компилятор выдаст предупреждающее сообщение). Это подвергает программу риску вызвать абстрактный метод, что приведёт к генерации ошибки времени выполнения и завершению программы.

java: В java и абстрактные методы, и абстрактные классы отмечаются ключевым словом abstract (действительно, в java обязательно определять как абстрактный класс, имеющий абстрактные методы, - хотя это кажется некоторым излишеством). Производные классы, которые не переопределяют все абстрактные методы, должны быть отмечены как абстрактные. Как и в c++, нельзя создавать объекты абстрактных классов.

Множественное наследование и интерфейсы

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

c++: 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 добавляет к этой структуре возможность передать реализацию интерфейса подобъекту, делая эту технику почти такой же эффективной, как и множественное наследование.

Другие свойства

Помимо обеспечения объектно-ориентированного программирования, эти языки предлагают другие интересные и мощные характеристики, которые дополняют поддержку ООП. В следующих разделах я просто представлю некоторые из них.

rtti

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

c++: Язык c++ первоначально не поддерживал rtti. Это было добавлено позже для динамического преобразования типа (dynamic_cast) и сделало доступной некоторую информацию о типе для классов. Вы можете запросить идентификацию типа для объекта, и проверить, принадлежат ли два объекта одному классу.

op: object pascal и визуальная среда поддерживает и требует много rtti. Доступен не только контроль соответствия и динамическое преобразование типов (с помощью операторов is и as). Классы генерируют расширенную rtti для своих published свойств, методов и полей. Фактически это ключевое слово управляет частью генерации rtti. Вся идея свойств, механизм потоков (файлы форм - dfm), и среда delphi, начиная с Инспектора Объектов, сильно опирается на rtti классов. У класса tobject есть (кроме прочих) методы classname и classtype. classtype возвращает переменную типа класса, объект специального типа ссылки на класс (который не является самим классом).

java: Как и в object pascal, в java тоже есть единый базовый класс, помогающий следить за информацией о классе. Безопасное преобразование типов (type-safe downcast) встроено в этот язык. Метод getclass() возвращает своего рода метакласс (объект класса, описывающего классы), и Вы можете применить функцию getname() для того, чтобы получить строку с именем класса. Вы можете также использовать оператор instanceof. java включает в себя расширенную rtti для классов или интроспекцию, которая была введена для поддержки компонентную модель javabeans.

Пример: Вот синтаксис безопасного преобразования типов на трех языках. В случае ошибки все три языка вызывают исключение:

    // c++
    dog* mydog = dynamic_cast <dog*> (myanimal);
    // java
    dog mydog = (dog) myanimal;
    // object pascal
    dog mydog := myanimal as dog;

Обработка исключений

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

c++: c++ использует ключевое слово throw для генерации исключения, try для отметки охраняемого блока и catch для записи кода обработки исключения. Исключения - объекты специального класса, которые могут образовывать некоторую иерархию во всех трёх языках. c++ выполняет опустошение стека, удаление всех объектов (и вызов деструкторов) для всех объектов в стеке.

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++ (из этих трёх языков) есть родовые классы и функции, отмечаемые ключевым словом template. Стандартный c++ включает обширную библиотеку шаблонов классов, называемую stl (Стандартная библиотека шаблонов), которая поддерживает специфический и мощный стиль программирования: родовое программирование. c++ - единственный из этих трех языков, который концентрируется на поддержке родового программирования, помимо ООП.

op: В object pascal нет шаблонов. Контейнерные классы обычно строятся как контейнеры объектов класса tobject, а затем уточняются для необходимых объектов.

java: Тоже нет шаблонов. Можно использовать (специальные) контейнеры object-ов или прибегнуть к другим трюкам.

Другие специфические свойства

Свойство: Есть еще другие свойства, не упомянутые мной, хотя они важны, только из-за того, что они специфичны только для одного из трёх языков.

c++: Я уже упомянул множественное наследование, виртуальные базовые классы и шаблоны. Эти свойства отсутствуют в двух других ОО языках. В c++ есть ещё перегрузка операторов, тогда как перегрузка методов присутствует также в java и была недавно добавлена в object pascal. c++ позволяет программистам перегружать и глобальные функции. Вы можете перегрузить операторы преобразования типов, написав конвертирующие методы, которые будут вызываться "за кулисами". Объектная модель c++ требует копировать конструкторы и перегружать операторы присваивания, в чем не нуждаются остальные два языка, поскольку базируются на ссылочно-объектной модели.

java: Только java поддерживает многопоточность непосредственно в языке. Объекты и методы поддерживают механизм синхронизации (с ключевым словом synchronised): два синхронизированных метода одного класса не могут выполняться одновременно. Для создания нового потока вы просто наследуете от класса thread, перегружая метод run(). Как альтернативу вы можете осуществить интерфейс runnable (что вы обычно делаете в апплетах, поддерживающих многопоточность). Мы уже обсуждали сборщик мусора. Ещё одно ключевое свойство java, конечно, идея переносимого байтового кода, но это не относится непосредственно к языку. Другое примечательное свойство - это поддержка основанных на языке компонентов, известных как javabeans и многие другие свойства, недавно добавленные в этот язык.

op: Вот некоторые специфические черты object pascal: ссылки на классы, легкие для использования указатели на методы (основа модели обработки событий) и, в частности, свойства (property). Свойство - это просто имя, скрывающее путь, которым вы получаете доступ к данным или методу. Свойство может проецироваться на прямое чтение или запись данных, а может ссылаться на метод, обеспечивающий доступ. Даже если вы меняете способ доступа к данным, вам не нужно менять вызывающий код (хотя вам нужно будет его перекомпилировать): это делает свойства очень мощным средством инкапсуляции.

Стандарты

Свойство: Для каждого языка требуется, чтобы кто-то установил его стандарт и проверял все реализации на соответствие ему.

c++: Стандарт ansi/iso c++ явился завершением многотрудных усилий соответствующего комитета. Большинство авторов компиляторов, кажется, пытаются подчиняться стандарту, хотя есть ещё много странностей. Теоретически развитие языка должно на этом закончиться. На практике, инициативы вроде компилятора borland c++builder, конечно, не способствуют улучшению ситуации, но многие чувствуют, что c++ очень нуждается в визуальном окружении программирования. В то же время, популярный visual c++ тянет c++ в другом направлении, например, с явным злоупотреблением макросов. (По моему личному мнению, у каждого языка есть собственная модель развития, и поэтому нет большого смысла в попытках использовать язык для того, для чего он не был предназначен.)

op: object pascal - язык-собственность, поэтому у него нет стандарта. borland лицензировал язык для пары продавцов небольших компиляторов на os/2, но это не оказало большого влияния. borland расширяет язык с каждым новым выпуском delphi.

java: java находится в очень странной ситуации. Это язык-собственность и даже обладает торговой маркой на своё имя. Однако sun лицензирует его для продавцов других компиляторов, и убедило iso создать стандарт java, не создавая специальный комитет, а просто приняв предложения sun как есть. Как известно, sun легально борется с microsoft, пытающейся расширить java на свой лад. Кроме формального стандарта, однако, java требует высокосовместимых jvm.

Заключение: Языки и программное окружение

Как я упоминал в начале, хотя я пытался исследовать эти языки, только сравнивая синтаксические и семантические характеристики, важно рассмотреть их в соответствующем контексте. Языки нацелены на различные потребности, что означает, что они решают разные проблемы разными способами и используются в очень разных средах программирования. Хотя как языки, так и их среда копируют характеристики друг друга, они были сконструированы для разных потребностей, и в этом вы можете убедиться, сравнивая их характеристики. Цель c++ - мощность и контроль за счет сложности. Целью delphi является легкое, визуальное программирование (не отказываясь от мощности) и прочная связь с windows. Цель java - мобильность, даже за счет некоторого отказа от скорости, и распределённые приложения или исполняемое содержание www (хотя это, конечно, - не microsoft-овский взгляд на java!).

Можно определить, что успех этих трех языков зависит не от технических характеристик, которые я включил в эту статью. Финансовый статус borland, операционная система управления microsoft, популярность sun в мире internet, тот факт, что java рассматривается как anti-microsoft-овский язык, будущее броузеров Паутины и win32 api, роль и признание модели activex (из-за связанной с ней проблемой безопасности) и три уровня архитектуры delphi - вот показатели, которые могли повлиять на ваши выбор сильнее, чем технические элементы. Например, такой хороший язык как eiffel, у которого object pascal и java взяли не только некоторое вдохновение, никогда не получит реальной доли рынка, хотя он был популярен во многих университетах земного шара.

Просто имейте в виду, что "модный" становится все более частым словом в компьютерном мире. Как пользователи хотят иметь инструменты этого года (вероятно, по этой причине операционные системы называются по тому году, в котором они выпущены), программисты любят работать с последним языком программирования и первыми овладеть им. Можно наверняка утверждать, что java - не последний из языков ООП. Через несколько следующих лет найдется кто-то с новым модным языком, и все прыгнут в этот поезд, думая, что нельзя отставать, и забывая, что большинство программистов в мире всё ещё печатают на клавиатуре на добром старом cobol!


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=3567