Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы
Основы объектно-ориентированного программирования
C# — Руководство по C# — Основы объектно-ориентированного программирования
Все основанные на объектах языки (C#, Java, С++, Smalltalk, Visual Basic и т.п.) должны отвечать трем основным принципам объектно-ориентированного программирования (ООП), которые перечислены ниже:
Инкапсуляция
Как данный язык скрывает детали внутренней реализации объектов и предохраняет целостность данных?
Наследование
Как данный язык стимулирует многократное использование кода?
Полиморфизм
Как данный язык позволяет трактовать связанные объекты сходным образом?
Прежде чем погрузиться в синтаксические детали реализации каждого принципа, важно понять базовую роль каждого из них.
Роль инкапсуляции
— это механизм программирования, объединяющий вместе код и данные, которыми он манипулирует, исключая как вмешательство извне, так и неправильное использование данных. В объектно-ориентированном языке данные и код могут быть объединены в совершенно автономный черный ящик. Внутри такого ящика находятся все необходимые данные и код. Когда код и данные связываются вместе подобным образом, создается объект. Иными словами, объект — это элемент, поддерживающий инкапсуляцию.
Т.е. инкапсуляция представляет собой способности языка скрывать излишние детали реализации от пользователя объекта. Например, предположим, что используется класс по имени DatabaseReader, который имеет два главных метода: Open() и Close().
Фиктивный класс DatabaseReader инкапсулирует внутренние детали нахождения, загрузки, манипуляций и закрытия файла данных. Программистам нравится инкапсуляция, поскольку этот принцип ООП упрощает кодирование. Нет необходимости беспокоиться о многочисленных строках кода, которые работают «за кулисами», чтобы реализовать функционирование класса DatabaseReader. Все, что потребуется — это создать экземпляр и отправлять ему соответствующие сообщения (например, «открыть файл по имени AutoLot.mdf, расположенный на диске С:»).
С идеей инкапсуляции программной логики тесно связана идея защиты данных. В идеале данные состояния объекта должны быть специфицированы с использованием ключевого слова private (или, возможно, protected). Таким образом, внешний мир должен вежливо попросить, если захочет изменить или получить лежащее в основе значение. Это хороший принцип, поскольку общедоступные элементы данных можно легко повредить (даже нечаянно, а не преднамеренно).
Основной единицей инкапсуляции в C# является класс, который определяет форму объекта. Он описывает данные, а также код, который будет ими оперировать. В C# описание класса служит для построения объектов, которые являются экземплярами класса. Следовательно, класс, по существу, представляет собой ряд схематических описаний способа построения объекта.
Код и данные, составляющие вместе класс, называют членами. Данные, определяемые классом, называют полями, или переменными экземпляра. А код, оперирующий данными, содержится в функциях-членах, самым типичным представителем которых является метод. В C# метод служит в качестве аналога подпрограммы. (К числу других функций-членов относятся свойства, события и конструкторы.) Таким образом, методы класса содержат код, воздействующий на поля, определяемые этим классом.
Роль наследования
Следующий принцип ООП — наследование — касается способности языка позволять строить новые определения классов на основе определений существующих классов. По сути, наследование позволяет расширять поведение базового (или родительского) класса, наследуя основную функциональность в производном подклассе (также именуемом дочерним классом):
Т.е. наследование представляет собой процесс, в ходе которого один объект приобретает свойства другого объекта. Это очень важный процесс, поскольку он обеспечивает принцип иерархической классификации. Если вдуматься, то большая часть знаний поддается систематизации благодаря иерархической классификации по нисходящей.
Если не пользоваться иерархиями, то для каждого объекта пришлось бы явно определять все его свойства. А если воспользоваться наследованием, то достаточно определить лишь те свойства, которые делают объект особенным в его классе. Он может также наследовать общие свойства своего родителя. Следовательно, благодаря механизму наследования один объект становится отдельным экземпляром более общего класса.
Роль полиморфизма
Последний принцип ООП — полиморфизм. Он обозначает способность языка трактовать связанные объекты в сходной манере. В частности, этот принцип ООП позволяет базовому классу определять набор членов (формально называемый полиморфным интерфейсом), которые доступны всем наследникам. Полиморфный интерфейс класса конструируется с использованием любого количества виртуальных или абстрактных членов.
По сути, — это член базового класса, определяющий реализацию по умолчанию, которая может быть изменена (или, говоря более формально, переопределена) в производном классе. В отличие от него, — это член базового класса, который не предусматривает реализации по умолчанию, а предлагает только сигнатуру. Когда класс наследуется от базового класса, определяющего абстрактный метод, этот метод обязательно должен быть переопределен в производном классе. В любом случае, когда производные классы переопределяют члены, определенные в базовом классе, они по существу переопределяют свою реакцию на один и тот же запрос.
Рассмотрим для примера стек, т.е. область памяти, функционирующую по принципу «последним пришел — первым обслужен». Допустим, что в программе требуются три разных типа стеков: один — для целых значений, другой — для значений с плавающей точкой, третий — для символьных значений. В данном примере алгоритм, реализующий все эти стеки, остается неизменным, несмотря на то, что в них сохраняются разнотипные данные. В языке, не являющемся объектно-ориентированным, для этой цели пришлось бы создать три разных набора стековых подпрограмм с разными именами. Но благодаря полиморфизму для реализации всех трех типов стеков в C# достаточно создать лишь один общий набор подпрограмм. Зная, как пользоваться одним стеком, вы сумеете воспользоваться и остальными.
В более общем смысле понятие полиморфизма нередко выражается следующим образом: «один интерфейс — множество методов«. Это означает, что для группы взаимосвязанных действий можно разработать общий интерфейс. Полиморфизм помогает упростить программу, позволяя использовать один и тот же интерфейс для описания общего класса действий. Выбрать конкретное действие (т.е. метод) в каждом отдельном случае — это задача компилятора. Программисту не нужно делать это самому. Ему достаточно запомнить и правильно использовать общий интерфейс.
Проилюстрировать ООП модель на примере
Такой вопрос в контрольной: ООП. Классы и обьекты, их синтаксис и семантика. Проилюстрировать ООП модель на примере.
Классы и обьексты что такое я обьяснил, привожу такой пример
Класс, например, это проект дома. А объект — это реальный дом.
ООП модель данных, Компоновщик
Подскажите, можно ли как то сделать «красивее». существует текстовый формат. каждая строка это.
Реализация принципов ООП на примере
Задача проекта должна содержать примеры реализации основных принципов обьектно-ориентированного.
Ооп. Изучаю основы, обьясните на примере
Класс: форум — сайт для общения. Объект форум — собственно любой форум. Тут все понятно. Форумный.
Последний гвоздь в гроб ООП ( на примере ММОРПГ сервера )
Мультинаследование уже и так похоронено мировым сообществом. Остаётся одинарное наследование с.
Модель ООП
Помоги сделать, пожалуйста работу! Возможно у кого нибудь есть примеры работы с классами! БУду.
Дом, это и класс и объект. Только с объектом мы работаем, создаем, используем, удаляем, можем менять его свойства и т.д. А класс описывает этот объект, т.е. содержит его свойства, методы работы с объектом и т.д.
Класс Дом можно описать следующим образом:
Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы
В предыдущих статьях (sealed классы в C# и статические классы в C#) я рассказывал про достаточно специфические классы, это классы, которые не могут быть использованы в качестве базовых классов (это sealed классы) и классы, объекты которых нельзя создавать в своих программах (это статические классы, хотя, они и в наследовании участия принимать тоже не могут). Так вот в этой статье, я хочу рассказать о еще одном типе классов, это абстрактные классы.
Абстрактные классы, тоже имеют ряд особенностей. Их объекты нельзя создавать (как и в случае статических классов), но предназначены абстрактные классы, именно для того, чтобы служить базовыми для других (т.е. полная противоположность sealed классам).
Таким образом, без механизма наследования, абстрактные классы практически бессмысленны!
А теперь, давайте разберемся подробнее, для чего же нужны абстрактные классы… Дело в том, что когда программист продумывает иерархию классов, на её вершине оказывается самый обобщенный класс. Возьмем для примера иерархию классов из урока № 22 базового курса C#:
Как видно, в приведенном примере есть три класса, на вершине иерархии находится класс «Weapon» (некое абстрактное оружие), а его наследниками, являются классы «Knife» (нож) и «Gun» (ружье), вполне себе конкретные классы. Так вот класс «Weapon» действительно представляет собой некое абстрактное оружие, и совсем непонятно, как должна выглядеть атака этим оружием… Создавать объекты этого класса нет никакого смысла, да и метод «Attack» нужно бы сделать виртуальным! Т.е. приведенный выше пример явно нуждается в доработке. И сейчас я покажу как это можно сделать.
А поступим мы так, класс «Weapon» мы сделаем абстрактным, и метод «Attack» в нем тоже будет абстрактным, у него не будет тела, т.е. реализации, так как мы действительно и не знаем что он должен делать в контексте класса «Weapon«. Плюс ко всему, метод «Attack» автоматически станет виртуальным, и в классах наследниках, мы его просто переопределим. Вот так это будет выглядеть на практике:
Всё довольно просто, изменения уложились в несколько строк (они выделены). Мы сделали класс «Weapon» абстрактным (добавили ключевое слово abstract перед его объявлением), так же сделали абстрактным метод «Attack» этого класса, а в классах наследника переопределили его (добавив ключевое слово override). И что мы получили в результате? Мы убрали всю конкретику (которая была не нужна) из класса «Weapon«, запретили создавать объекты этого класса (которые в принципе бессмысленны), сделали метод «Attack» автоматически виртуальным (объявив его абстрактным). Таким образом, мы убрали «всё ненужное» из первоначального примера и добились необходимого результата, используя абстрактный класс.
Зачем нужны виртуальные методы рассказывается в уроке № 22 базового курса.
В завершение, хочу отметить, что если в классе наследнике, не переопределить абстрактные сущности (в нашем случае, метод «Attack») базового класса, то класс наследник тоже придется делать абстрактным (и объекты этого класса мы создавать не сможем).
И еще один момент, эту же задачу, можно решить без применения абстрактных классов, используя вместо этого интерфейсы, пример и теория приведены в уроке № 24 базового курса. А о том, какой подход лучше, или скажем так, какие плюсы и минусы есть у обоих подходом, я расскажу в отдельной статье.
Добавить комментарий Отменить ответ
Для отправки комментария вам необходимо авторизоваться.
Абстрактные классы и чисто виртуальные функции
Абстрактные классы предназначены для создания обобщенных сущностей, на основе которых в дальнейшем предполагается создавать более конкретные производные классы. Абстрактный класс – это класс, который может использоваться лишь в качестве базового класса для некоторого другого класса, поэтому невозможно создать объект типа абстрактного класса
Класс, содержащий хотя бы одну чисто виртуальную функцию, является абстрактным. Поэтому, классы, производные от абстрактного класса, должны реализовать все его чисто виртуальные функции, иначе они также будут абстрактными классами.
Виртуальная функция объявляется как «чистая» с помощью синтаксиса спецификатора-чистоты. Рассмотрим в качестве примера класс CAnimal, который создаётся только для того, чтобы предоставлять общие функции – сами объекты типа CAnimal имеют слишком общий характер для практического применения. Таким образом, класс CAnimal является хорошим кандидатом в абстрактный класс:
class CAnimal
<
public :
CAnimal(); // конструктор
virtual void Sound() = 0; // чисто виртуальная функция
private :
double m_legs_count; // количество ног животного
>;
Здесь функция Sound() является чисто виртуальной, потому что она объявлена со спецификатором чисто виртуальной функции PURE ( =0 ).
Чисто виртуальными функциями являются только такие виртуальные функции, для которых указан спецификатор чистоты PURE, а именно: (=NULL) или (=0). Пример объявления и использования абстрактного класса:
class CAnimal
<
public :
virtual void Sound()= NULL ; // PURE method, должен быть переопределён в потомке, сам класс CAnimal стал абстрактным и не может быть создан
>;
//— потомок от абстрактного класса
class CCat : public CAnimal
<
public :
virtual void Sound() < Print ( "Myau" ); >// PURE переопределён, класс CCat не абстрактный и может быть создан
>;
//— примеры неправильного использования
new CAnimal; // ошибка ‘CAnimal’ — компилятор выдаст ошибку «cannot instantiate abstract class»
CAnimal some_animal; // ошибка ‘CAnimal’ — компилятор выдаст ошибку «cannot instantiate abstract class»
//— примеры правильного использования
new CCat; // ошибки нет — класс CCat не абстрактный
CCat cat; // ошибки нет — класс CCat не абстрактный
Ограничения на использование абстрактных классов
При вызове конструктором абстрактного класса чистой виртуальной функции (прямо или косвенно) результат будет неопределённым.
//+——————————————————————+
//| Абстрактный базовый класс |
//+——————————————————————+
class CAnimal
<
public :
//— чисто виртуальная функция
virtual void Sound( void )= NULL ;
//— функция
void CallSound( void ) < Sound(); >
//— конструктор
CAnimal()
<
//— явный вызов виртуального метода
Sound();
//— неявный вызов (через третью функцию)
CallSound();
//— в конструкторе и/или деструкторе всегда вызываются свои функции,
//— несмотря на виртуальность и переопределение вызываемой функции в потомке
//— если вызываемая функция чисто виртуальная, то
//— вызов приведёт к критической ошибке выполнения: «pure virtual function call»
>
>;
Однако конструкторы и деструкторы абстрактных классов могут вызывать другие функции-члены.
Основы объектно-ориентированного программирования
Шаг 6. «Пять классов математики» или введение в объектно-ориентированное программирование.
Этот шаг – краткий экскурс в объектно-ориентированное программирование (ООП). Перед тем как начать знакомиться с приведенными ниже примерами, рекомендую пройти по ссылке и кратко познакомиться с этим направлением в программировании. Про ООП написано очень много всякой разной литературы. Тема эта настолько популярна, насколько и гениальна, поэтому представить свое мнение об объектно-ориентированной парадигме считает своим долгом практически каждый автор книг о программировании.
На сайте Coding Craft более подробно рассматривается тема классов, наследования и полиморфизма в разделах курса программирования C# Quick Guide. В рамках курса основ программирования в целом и этого шага в частности я ни в коем случае не ставлю задачи научить читателя всем тонкостям и хитростям объектно-ориентированного программирования, но ввести в курс дела, надеюсь, у меня получится. Считаю, что не стоит уделять излишнего внимания приводимым в примере синтаксическим конструкциям — вполне достаточно уловить их смысл. Речь пойдет о пяти бинарных математических операциях: сложение, вычитание, умножение, деление и возведение в степень. Их всех объединяет то, что они принимают два вещественных аргумента и возвращают одно вещественное число в качестве результата.
Описание реализации
Пример, приведенный ниже, с одной стороны довольно прост, но с другой – демонстрирует практически все преимущества объектно-ориентированного программирования:
Применение абстрактных типов, как способа выделения наиболее значимых аспектов описываемых объектов. С помощью введения абстрактного класса MathOperation и абстрактной функции Calculate мы выделяем “бинарность” наших операций, как их общее, наиболее значимое в текущем контексте свойство. Абстрактные классы на то и абстрактные, что нельзя создавать их экземпляры, а абстрактные функции и методы не имеют своей реализации. Одно из назначений абстрактных типов – наложение ограничений на производные от них типы. Одним из таких ограничений является обязательство всех производных от MathOperation классов реализовать функцию Calculate() . Реализовать именно в том виде, в котором она описана в базовом абстрактном классе, но логику операции определить самостоятельно.
Наследование, как способ выделения общей функциональности описываемых объектов в базовом типе. В нашем случае базовый класс MathOperation за все свои производные классы определяет то, как будет выводиться результат математической операции: реализует метод WriteResult() . Обратите внимание, что реализация метода WriteResult() ссылается на абстрактную функцию Calculate() , реализация которой может появиться только в производных от MathOperation классах. Таким образом, MathOperation определяет общее поведение метода, а его нюансы будут уточнены производными классами.
Полиморфизм, как способ одинаково обращаться к экземплярам наших математических операций, но получать от каждого из них уникальный результат. Классы Addition , Subtraction , Multiplication , Division и Power реализуют одну и ту же функцию Calculate() , которая им досталась по наследству от их общего предка – класса MathOperation , но каждый из них делает это по-своему.
Фрагмент кода
Код приводимого примера достаточно объемный, поэтому здесь представлен только базовый абстрактный класс MathOperation и один из производных классов – класс Addition . То, как создаются и используются экземпляры этих классов показано чуть ниже. Чтобы посмотреть исходный код всех классов и увидеть, как он работает, скачайте полные версии программ и запустите их с использованием обучающего приложения.
Полные версии программы
Важно: В классе MathOperation функция assign() определена, как статическая ( static в C# и Shared в VB.NET). Это сделано для того, чтобы вызывать метод, не создавая экземпляров класса, а обращаясь к нему через имя типа. В нашем примере значения аргументов a и b общие для всех операций, а код самого ввода аргумента хотелось вынести в базовый тип. Вызов метода assign() расположен в функции Main() , исходный код которой представлен ниже.
Резюме
Если тема программирования вам интересна, а объектно-ориентированное программирование перестало быть для вас чем-то непонятным, то вы в любом случае прочитаете еще много различной литературы на эту тему. Я же, со своей стороны, рекомендую познакомиться с материалами курса программирования C# Quick Guide. Надеюсь, это будет не бесполезно.
Самостоятельно
Предлагаю добавить определение производного класса, выполняющего целочисленное деление или возвращающего остаток от деления.
|
|