Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы


Оглавление (нажмите, чтобы открыть):

Основы объектно-ориентированного программирования

C# — Руководство по C# — Основы объектно-ориентированного программирования

Все основанные на объектах языки (C#, Java, С++, Smalltalk, Visual Basic и т.п.) должны отвечать трем основным принципам объектно-ориентированного программирования (ООП), которые перечислены ниже:

Инкапсуляция

Как данный язык скрывает детали внутренней реализации объектов и предохраняет целостность данных?

Наследование

Как данный язык стимулирует многократное использование кода?

Полиморфизм

Как данный язык позволяет трактовать связанные объекты сходным образом?

Прежде чем погрузиться в синтаксические детали реализации каждого принципа, важно понять базовую роль каждого из них.

Роль инкапсуляции

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

Т.е. инкапсуляция представляет собой способности языка скрывать излишние детали реализации от пользователя объекта. Например, предположим, что используется класс по имени DatabaseReader, который имеет два главных метода: Open() и Close().

Фиктивный класс DatabaseReader инкапсулирует внутренние детали нахождения, загрузки, манипуляций и закрытия файла данных. Программистам нравится инкапсуляция, поскольку этот принцип ООП упрощает кодирование. Нет необходимости беспокоиться о многочисленных строках кода, которые работают «за кулисами», чтобы реализовать функционирование класса DatabaseReader. Все, что потребуется — это создать экземпляр и отправлять ему соответствующие сообщения (например, «открыть файл по имени AutoLot.mdf, расположенный на диске С:»).

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

Основной единицей инкапсуляции в C# является класс, который определяет форму объекта. Он описывает данные, а также код, который будет ими оперировать. В C# описание класса служит для построения объектов, которые являются экземплярами класса. Следовательно, класс, по существу, представляет собой ряд схематических описаний способа построения объекта.

Код и данные, составляющие вместе класс, называют членами. Данные, определяемые классом, называют полями, или переменными экземпляра. А код, оперирующий данными, содержится в функциях-членах, самым типичным представителем которых является метод. В C# метод служит в качестве аналога подпрограммы. (К числу других функций-членов относятся свойства, события и конструкторы.) Таким образом, методы класса содержат код, воздействующий на поля, определяемые этим классом.

Роль наследования

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

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

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

Роль полиморфизма

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

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

Рассмотрим для примера стек, т.е. область памяти, функционирующую по принципу «последним пришел — первым обслужен». Допустим, что в программе требуются три разных типа стеков: один — для целых значений, другой — для значений с плавающей точкой, третий — для символьных значений. В данном примере алгоритм, реализующий все эти стеки, остается неизменным, несмотря на то, что в них сохраняются разнотипные данные. В языке, не являющемся объектно-ориентированным, для этой цели пришлось бы создать три разных набора стековых подпрограмм с разными именами. Но благодаря полиморфизму для реализации всех трех типов стеков в C# достаточно создать лишь один общий набор подпрограмм. Зная, как пользоваться одним стеком, вы сумеете воспользоваться и остальными.

В более общем смысле понятие полиморфизма нередко выражается следующим образом: «один интерфейс — множество методов«. Это означает, что для группы взаимосвязанных действий можно разработать общий интерфейс. Полиморфизм помогает упростить программу, позволяя использовать один и тот же интерфейс для описания общего класса действий. Выбрать конкретное действие (т.е. метод) в каждом отдельном случае — это задача компилятора. Программисту не нужно делать это самому. Ему достаточно запомнить и правильно использовать общий интерфейс.

Проилюстрировать ООП модель на примере

Такой вопрос в контрольной: ООП. Классы и обьекты, их синтаксис и семантика. Проилюстрировать ООП модель на примере.

Классы и обьексты что такое я обьяснил, привожу такой пример
Класс, например, это проект дома. А объект — это реальный дом.

14.06.2014, 16:07

ООП модель данных, Компоновщик
Подскажите, можно ли как то сделать «красивее». существует текстовый формат. каждая строка это.

Реализация принципов ООП на примере
Задача проекта должна содержать примеры реализации основных принципов обьектно-ориентированного.

Ооп. Изучаю основы, обьясните на примере
Класс: форум — сайт для общения. Объект форум — собственно любой форум. Тут все понятно. Форумный.

Последний гвоздь в гроб ООП ( на примере ММОРПГ сервера )
Мультинаследование уже и так похоронено мировым сообществом. Остаётся одинарное наследование с.

Модель ООП
Помоги сделать, пожалуйста работу! Возможно у кого нибудь есть примеры работы с классами! БУду.

14.06.2014, 17:30 2

Дом, это и класс и объект. Только с объектом мы работаем, создаем, используем, удаляем, можем менять его свойства и т.д. А класс описывает этот объект, т.е. содержит его свойства, методы работы с объектом и т.д.

Класс Дом можно описать следующим образом:

Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы

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

Абстрактные классы, тоже имеют ряд особенностей. Их объекты нельзя создавать (как и в случае статических классов), но предназначены абстрактные классы, именно для того, чтобы служить базовыми для других (т.е. полная противоположность sealed классам).

Таким образом, без механизма наследования, абстрактные классы практически бессмысленны!

А теперь, давайте разберемся подробнее, для чего же нужны абстрактные классы… Дело в том, что когда программист продумывает иерархию классов, на её вершине оказывается самый обобщенный класс. Возьмем для примера иерархию классов из урока № 22 базового курса C#:

Как видно, в приведенном примере есть три класса, на вершине иерархии находится класс «Weapon» (некое абстрактное оружие), а его наследниками, являются классы «Knife» (нож) и «Gun» (ружье), вполне себе конкретные классы. Так вот класс «Weapon» действительно представляет собой некое абстрактное оружие, и совсем непонятно, как должна выглядеть атака этим оружием… Создавать объекты этого класса нет никакого смысла, да и метод «Attack» нужно бы сделать виртуальным! Т.е. приведенный выше пример явно нуждается в доработке. И сейчас я покажу как это можно сделать.

А поступим мы так, класс «Weapon» мы сделаем абстрактным, и метод «Attack» в нем тоже будет абстрактным, у него не будет тела, т.е. реализации, так как мы действительно и не знаем что он должен делать в контексте класса «Weapon«. Плюс ко всему, метод «Attack» автоматически станет виртуальным, и в классах наследниках, мы его просто переопределим. Вот так это будет выглядеть на практике:

Мастер Йода рекомендует:  MIT представил новую супербыструю технологию 3D-печати

Всё довольно просто, изменения уложились в несколько строк (они выделены). Мы сделали класс «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™. Надеюсь, это будет не бесполезно.

Самостоятельно

Предлагаю добавить определение производного класса, выполняющего целочисленное деление или возвращающего остаток от деления.

Опубликовал +автор Октябрь 19 2011 13:44:30 12064 Прочтений
Компьютерные курсы и курсы программирования

Курс для начинающих программистов на C# и VB.NET.

Построение SQL запросов и работа с базой данных.

Программирование на C#. Краткое руководство.

Применение регулярных выражений.

Примеры программной Plug-in архитектуры.

Язык разметки XML и его расширения с примерами.

Языки HTML, XHTML и CSS с примерами разметки.

Основы веб-дизайна: решения типовых задач верстки.

Руководство по программированию на PHP для начинающих.

Шаблоны проектирования
Каталог шаблонов проектирования программных компонентов.

Рефакторинг кода
Каталог приемов рефакторинга программного кода.

Абстрактные классы C#

Методы класса могут быть объявлены как абстрактные. Это означает, что в этом классе нет реализации этих методов. Абстрактные методы пишутся с модификатором abstract. Класс, в котором есть хотя бы

один абстрактный метод, называется абстрактным (в таком классе могут быть и обычные методы). Нельзя создавать экземпляры абстрактного класса — такой класс может использоваться только в качестве базового класса для других классов. Для потомка такого класса есть две возможности — или он реализует все абстрактные методы базового класса (и в этом случае для такого класса-потомка мы сможем создавать его экземпляры), или реализует не все абстрактные методы базового класса (в этом случае он является тоже абстрактным классом, и единственная возможность его использования — это производить от него классы потомки). Вот пример, иллюстрирующий использование абстрактных

abstract class Figure

public abstract double square();

public abstract double perimeter();

class Triangle: Figure

108 Раздел I I . Фундаментальные понятия

double а, Ь, с; //Стороны

public Triangle (double a, double b, double с)


public override double square()

//Используем формулу Герока

double p — (aib+c)/2;

return Math.Sqrt(p*(p-a)* (p-b)* (p-c));

public override double perimeter()

class Rectangle: Figure

double a, b; //Стороны

public Rectangle(double a, double b)

public override double squared

public override double perimeter!)

public static void Main()

fl=new Triangle(3, 4, 5);

f2=new Rectangle(2, 6);

System.Console . WriteLine (f 2 .perimeter ()•+», «+ f 2 . square ()

Результаты работы программы отобразятся как:

Тут мы объявляем абстрактный класс Figure, от которого производим два класса — Rectangle (класс прямоугольника) и Triangle (треугольника). В классе Figure есть два абстрактных метода — square (для подсчета площади) и perimeter (для периметра). Так как для произвольной фигуры формул для площади и для периметра не существует, то эти методы объявлены в классе Figure и переопределены в производных классах (с ключевым словом override). Далее в классе Test мы проводим испытание — заводим две переменные ссылочного типа базового класса Figure, ниже в эти ссылки мы записываем созданные экземпляры производных классов Triangle и Rectangle. Обратите внимание, что ссылку на абстрактный класс мы создать можем, а экземпляр — нет. Далее мы выводим на экран периметр и площадь для наших фигур.

C# объектно-ориентированное программирование (ООП)

Опубликовано Константин Туйков в 13.07.2020 13.07.2020

Ранее мы изучили, что такое объекты и классы. Также, рассмотрели свойства, которыми объекты обладают. Кроме того, были затронуты действия, которые могут совершаться над объектами, называемые методами. Главной задачей объектно — ориентированного подхода является упрощение процесса разработки. Весь подход стоит на трех основных принципах: наследование, инкапсуляция и полиморфизм. Рассмотрим каждый из них.

Наследование

Используя наследование, мы может определить ряд дочерних классов. Они же будут наследовать свойства и методы базового. Таким образом, мы расширяем функциональность базового класса.

Вернемся к ранее рассмотренному примеру кода. Был описан класс «Персонаж», который является базовым. От него могут быть унаследованы более «узкие» классы персонажей, такие как «Воин», «Стрелок», «Маг» и другие. Следует заметить, что класс может наследоваться только от одного базового класса.

Инкапсуляция

Этот принцип гласит о том, что внутреннее устройство объекта должно быть скрыто от пользователя. Так, пользователю предоставляется доступ только к определенному набору методов, которые необходимы для взаимодействия с объектом. Под пользователем в данном случае понимается наследник или разработчик. По сути своей объект должен быть абсолютно автономным и иметь определенный набор инструментов, позволяющих управлять им. К примеру, стрелок не обязан иметь доступ к внутреннему устройству оружия. Однако ему необходимы методы для ведения огня и перезарядки, а также пополнению боезапаса. И, соответственно, доступ к определенным частям оружия.

Полиморфизм

Достаточно сложное в определении понятие. Если говорить упрощенно, мы можем использовать свойства и методы потомков. К примеру, мы объявили несколько потомков класса «Персонаж».

При подобной записи warrior будет являться объектом класса «Воин» несмотря на то, что типом мы указали базовый класс.

Наследование от Object

В C# любой класс является наследником класса Object. Благодаря этому свойству экземпляр любого класса обладает рядом полезных методов.

  • Equals() — сравнение переданного в качестве параметра объекта с текущим.
  • GetHashCode() — возвращает хеш-код текущего объекта в памяти.
  • GetType() — позволяет идентифицировать тип объекта.
  • ToString() — приводит класс к строке.
  • Finalize() — отвечает за уничтожение объекта (вызывается автоматически).
  • MemberwiseClone() — возвращает точную копию объекта.

Любой из этих методов может быть переопределен для изменения или дополнения функциональности.

Итак, это минимум, необходимый для понимания что такое объектно-ориентированное программирование. Переопределение методов мы рассмотрим далее. Удачных экспериментов, коллеги.

Что такое ООП на примерах. Для чайников

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

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

ООП (или объектно-ориентированное программирование) представляет собой способ организации кода программы, когда основными строительными блоками программы являются объекты и классы, а логика работы программы построена на их взаимодействии.

Об объектах и классах

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

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

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

Если мы внутрь класса добавим следующий код:

то программу уже можно будет запускать на выполнение. Это особенность языка java. Если в классе есть такой метод

то этот класс можно выполнять. Разберем код подробнее. Начнем со строки

Здесь мы создаем экземпляр triangle1 класса Triangle и тут же задаем ему параметры сторон и угла между ними. При этом, вызывается специальный метод, называемый конструктор и заполняет поля объекта переданными значениями в конструктор. Ну, а строки

выводят рассчитанные площадь треугольника и его периметр в консоль.

Аналогично все происходит и для второго экземпляра класса Triangle .

Понимание сути классов и конструирования конкретных объектов — это уверенный первый шаг к пониманию методологии ООП.

Еще раз, самое важное:

ООП — это способ организации кода программы;

Класс — это пользовательская структура данных, которая воедино объединяет данные и функции для работы с ними(поля класса и методы класса);


Объект — это конкретный экземпляр класса, полям которого заданы конкретные значения.

Три волшебных слова

ООП включает три ключевых подхода: наследование, инкапсуляцию и полиморфизм. Для начала, приведу определения из wikipedia:

Инкапсуляция — свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе. Некоторые языки (например, С++) отождествляют инкапсуляцию с сокрытием, но большинство (Smalltalk, Eiffel, OCaml) различают эти понятия.

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

Полиморфизм — свойство системы, позволяющее использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

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

Итак, точка — это самая малая геометрическая фигура, которая является основой всех прочих построений (фигур). Поэтому именно точка выбрана в качестве базового родительского класса. Напишем класс точки на java:

У получившегося класса Point пустой конструктор, поскольку в данном примере мы работаем без конкретных координат, а оперируем только параметрами значениями сторон. Так как у точки нет никаких сторон, то и передавать ей никаких параметров не надо. Также заметим, что класс имеет методы Point::getSquare() и Point::getPerimeter() для расчета площади и периметра, оба возвращают 0. Для точки оно и логично.

Поскольку у нас точка является основой всех прочих фигур, то и классы этих прочих фигур мы наследуем от класса Point . Опишем класс отрезка, наследуемого от класса точки:

означает, что класс LineSegment наследуется от класса Point . Методы LineSegment::getSquare() и LineSegment::getPerimeter() переопределяют соответствующие методы базового класса. Площадь отрезка всегда равняется нулю, а площадь периметра равняется длине этого отрезка.

Теперь, подобно классу отрезка, опишем класс треугольника(который также наследуется от класса точки):

Тут нет ничего нового. Также, методы Triangle::getSquare() и Triangle::getPerimeter() переопределяют соответствующие методы базового класса.
Ну а теперь, собственно, тот самый код, который показывает волшебство полиморифзма и раскрывает мощь ООП:

Мы создали массив объектов класса Point , а поскольку классы LineSegment и Triangle наследуются от класса Point , то и их мы можем помещать в этот массив. Получается, каждую фигуру, которая есть в массиве figures мы можем рассматривать как объект класса Point . В этом и заключается полиморфизм: неизвестно, к какому именно классу принадлежат находящиеся в массиве figures объекты, но поскольку все объекты внутри этого массива принадлежат одному базовому классу Point , то все методы, которые применимы к классу Point также и применимы к его классам-наследникам.

Теперь о инкапсуляции. То, что мы поместили в одном классе параметры фигуры и методы расчета площади и периметра — это и есть инкапсуляция, мы инкапсулировали фигуры в отдельные классы. То, что у нас для расчета периметра используется специальный метод в классе — это и есть инкапсуляцию, мы инкапсулировали расчет периметра в метод getPerimiter() . Иначе говоря, инкапсуляция — это сокрытие реализции (пожалуй, самое короткое, и в то же время емкое определением инкапсуляции).

Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы

Пример, демонстрирующий перегрузку метода родительского класса: В рассмотренном примере от класса Human порожден класс Student, в котором перегружен метод PrintInfo. В следующем примере будет вызываться метод класса Human или метод класса Student в зависимости от того, относительно какого объекта этот метод вызывается. Вывод: Если требуется, чтобы в каждом производном классе обязательно был перегружен метод базового класса, необходимо определить такой метод в базовом классе как абстрактный (abstract) .
Тело метода отсутствует, если он (метод) абстрактный. Если класс содержит хотя бы один абстрактный метод, то этот класс абстрактный.

Пример:
Создадим иерархию классов геометрических фигур.
Базовым классом является класс Shape. От него порождены два класса: Hexagon и Circle. В классе Shape объявлен абстрактный метод Draw, который должен быть перегружен в порожденных классах.

Отличия абстрактного класса от интерфейса (abstract > Задать вопрос

Каковы отличия между абстрактным классом и интерфейсом?

12 ответов 12

Абстрактный класс — это класс, у которого не реализован один или больше методов (некоторые языки требуют такие методы помечать специальными ключевыми словами).

Интерфейс — это абстрактный класс, у которого ни один метод не реализован, все они публичные и нет переменных класса.

Интерфейс нужен обычно когда описывается только интерфейс (тавтология). Например, один класс хочет дать другому возможность доступа к некоторым своим методам, но не хочет себя «раскрывать». Поэтому он просто реализует интерфейс.

Абстрактный класс нужен, когда нужно семейство классов, у которых есть много общего. Конечно, можно применить и интерфейс, но тогда нужно будет писать много идентичного кода.

В некоторых языках (С++) специального ключевого слова для обозначения интерфейсов нет.

Можно считать, что любой интерфейс — это уже абстрактный класс, но не наоборот.

tl;dr: Абстрактный класс — средство разработки классов на нижнем уровне, средство для повторного использования кода; интерфейс — средство выражения семантики класса. Таким образом, это совершенно разные, мало связанные между собой понятия.

Думайте об этом по-другому.

Абстрактный класс — это «заготовка» класса: реализовано большинство методов (включая внутренние), кроме нескольких. Эти несколько нереализованных методов вполне могут быть внутренними методами класса, они лишь уточняют детали имплементации. Абстрактный класс — средство для повторного использования кода, средство, чтобы указать, какой метод обязан быть перекрыт для завершения написания класса.

Интерфейс же — это своего рода контракт: интерфейсы используются в определениях чтобы указать, что объект, который будет использован на самом деле, должен реализовывать (для входных параметров) или будет гарантированно реализовывать (для выходных параметров) набор методов и (что намного важнее!) иметь определённую семантику. Интерфейс вполне может быть и пустым, тем не менее, имплементировать интерфейс означает поддерживать данную семантику.

Абстрактные классы идеологически схожи с шаблонами C++: и те, и другие являются заготовками классов, но шаблону для получения класса нужно специфицировать шаблонные типы, а абстрактному классу — абстрактные методы.

Интерфейсы идеологически схожи с заголовочными файлами C++: они раскрывают методы и скрывают конкретную реализацию.

Вопрос о том, является ли интерфейс или абстрактный класс собственно классом — техническая подробность реализации, зависящая от конкретного языка программирования. Например, в C++ интерфейсы отсутствуют вовсе, и их приходится эмулировать классами без данных. Абстрактный класс в C++ как таковой также отсутствует, но им можно считать любой класс с абстрактными методами. (Отсюда ограничение C++: как минимум 1 абстрактный метод в абстрактном классе.) Также в C++ можно (непрямо) инстанциировать абстрактный класс, вызвать абстрактный метод и (возможно) получить ошибку времени выполнения. В C# интерфейсы и абстрактные классы встроены в язык.

Пример (на C#, конкретный язык значения не имеет):

Мне кажется достаточно любопытным, что данный вопрос помечен тегом «ооп», но при этом во многих ответах явно просачиваются специфические аспекты конкретных языков программирования. Я же постараюсь дать ответ исходя из понятий ООП и лишь потом показать, почему вообще это различие появилось в некоторых языках программирования.

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

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

Конкретная фиксированная операция, которая должна быть абсолютно стабильно для всех типов кнопок.

Конкретная операция с поведением по умолчанию (т.е. операция, чье поведение подходит для многих типов кнопок, но могут быть кнопки с другим поведением).

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

Другими словами, тип Кнопки может содержать невиртуальные методы (non-virtual methods), виртуальные методы (virtual methods) и абстрактные методы (abstract methods).

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

После того, как мы определили базовый тип, пришло время определить произвольные типы. И тут начинаются вопросы. Точнее, вопросов никаких не возникает, когда у типа есть лишь один непосредственный базовый тип или все базовые типы содержат лишь декларации операций. Не проблема, унаследовать «Кнопку меню» от «Кнопки» и переопределить метод «Нажать на кнопку». Но что, если наш тип «Кнопка меню» будет отнаследован от двух типов с одной и той же виртуальной операцией? Как переопределить лишь одну, а оставить другую? А как быть клиенту нового типа и различить, какую операцию вызвать? А что если у двух базовых типов есть поле с одним именем? А что если у одного базового типа метод «Нажать кнопку» реализован, а у другого – лишь описан в виде декларации?

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

Теперь будет легко провести разницу между тремя понятиями – интферфейса, абстрактного базового класса и конкретного базового класса.

Интерфейс – описывает некоторое семейство типов и содержит лишь декларации операций (да, я осознанно пишу слово «декларация», а не использую слово «контракт», которое в ООП имеет вполне определенное значение).

Абстрактный базовый класс описывает некоторое семейство типов, но помимо декларации операций может содержать реализации по умолчанию (виртуальные методы) и фиксированные операции (невиртуальные методы).

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

Выделение интерфейсов в отдельную категорию полезно не только с точки зрения упрощения реализации языков программирования, но и для выделения разных подходов к моделированию. Так, например, наследование классов моделирует отношение «Является» («Кнопка меню» ЯВЛЯЕТСЯ «Кнопкой»), а базовые классы обычно содержат определенный функционал, тесно связанный с функционалом производного класса. Базовые классы не просто моделируют группу типов, но и позволяют использовать повторно существующий функционал.

Интерфейсы же, по своей природе обладают меньшей связностью (low coupling), поскольку не обладают конкретным поведением, которое может осложнить жизнь класса-наследника. Интерфейсы также могут моделировать отношение «Является» («Кнопка меню» ЯВЛЯЕТСЯ «IКнопкой»), но могут определять и менее жесткое отношение «Может выполнять роль» (CAN DO). Например, интерфейс IEquatable из BCL определяет «дополнительное» поведение, которое говорит о возможности типов сравнивать значения объектов.

Мастер Йода рекомендует:  PHP 5.6 что новенького PHP
Добавить комментарий
Справочные материалы