Выбрать главу

Один из подклассов — SVGA — не абстрактный. Это отдельный тип видеоадаптера, и программист точно знает, как его реализовать. Таким образом, класс SVGA переопределяет обе функции — initialize( ) и write( ) — именно так, как необходимо для данного адаптера.

Ещё один подкласс — HWVGA . Программисту известно, как программировать ускоренный VGA-адаптер. Поэтому между общим классом Display и его частным случаем, ThreedVGA, который представляет собой специальный тип карт 3-D, находится ещё один уровень абстракции.

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

_________________

256 стр. Часть 4. Наследование

При этом функция initialize( ) не переопределяется, поскольку для разных типов карт HWVGA она реализуется по-разному.

Несмотря на то что функция write( ) переопределена в классе HWVGA, он всё равно остаётся абстрактным, так как функция initialize( ) всё ещё не переопределена.

Поскольку ThreedVGA наследуется от HWVGA, он должен переопределить только одну функцию, initialize( ), для того чтобы окончательно определить адаптер дисплея. Таким образом, функция fn( ) может свободно реализовать и использовать объект класса ThreedVGA.

«Замещение нормальной функцией последней чисто виртуальной функции делает класс завершённым ( т.е. неабстрактным ). Только неабстрактные классы могут быть реализованы в виде объектов.»

[Помни!]

Передача абстрактных классов...257

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

    void fn( Account *pAccount ) ; /* Это допустимо */

    void otherFn( )

    {

        Savings s ;

        Checking c ;

        /* Savings ЯВЛЯЕТСЯ Account */

        fn( &s ) ;

        /* Checking — тоже */

        fn( &c ) ;

    }

В этом примере pAccount объявлен как указатель на Account. Разумеется, при вызове функции ей будет передаваться адрес какого-то объекта неабстрактного класса, например Checking или Savings.

Все объекты, полученные функцией fn( ), будут объектами либо класса Checking, либо Savings ( или другого неабстрактного подкласса Account ). Можно с уверенностью заявить, что вы никогда не передадите этой функции объект класса Account, поскольку никогда не сможете создать объект этого класса.

Нужны ли чисто виртуальные функции...257

Если нельзя определить функцию withdrawal( ), почему бы просто не опустить её? Почему бы не объявить её в классах Savings и Checking, где она может быть определена, оставив в покое класс Account? Во многих объектно-ориентированных языках вы могли бы именно так и сделать. Но С++ предпочитает иметь возможность убедиться в вашем понимании того, что вы делаете.

«Не забывайте, что объявление функции — это указание полного имени функции, включающего её аргументы. Определение же функции включает в себя и код, который будет выполняться в результате вызова этой функции.» 

[Помни!]

_________________

257 стр. Глава 22. Разложение классов

Чтобы продемонстрировать суть сказанного, можно внести следующие незначительные изменения в класс Account: