Главная страница

Л 08 интерфейсы и абстрактные базовые классы


Скачать 255.51 Kb.
НазваниеЛ 08 интерфейсы и абстрактные базовые классы
Дата05.10.2021
Размер255.51 Kb.
Формат файлаdocx
Имя файла08_ (1).docx
ТипДокументы
#241678

20.10.2021

Л_08 ИНТЕРФЕЙСЫ И АБСТРАКТНЫЕ БАЗОВЫЕ КЛАССЫ


Оглавление


ПРИМЕР OOPEx08_01. Чистые виртуальные функции в С++ 6

ПРИМЕР OOPEx09_02 Интерфейс и два прямых потомка 15

ОТНОШЕНИЯ МЕЖДУ КЛАССАМИ И ОБЪЕКТАМИ 27

АБСТРАКТНЫЙ МЕТОД (ЧИСТАЯ ВИРТУАЛЬНАЯ ФУНКЦИЯ)



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

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

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

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

Для объявления чистой виртуальной функции используется форма

virtual Тип ИмяФункции (СписокПараметров) = 0;

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

В C# при объявлении абстрактного метода используется модификатор abstract. Абстрактный метод автоматически становится виртуальным, поэтому модификатор virtual при объявлении такого метода не нужен.

Для объявления абстрактного метода используется форма

abstract Тип ИмяФункции (СписокПараметров);

Тело метода здесь отсутствует, модификатор virtual использовать запрещено.

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

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


АБСТРАКТНЫЙ БАЗОВЫЙ КЛАСС



В общем случае возможность непосредственно создавать экземпляры базового класса является недостатком и плохим стилем программирования.

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

Иногда требуется создать наследуемый (базовый) класс, в котором определены лишь некоторые характеристики методов – имя метода, тип результата, список параметров, то есть прототип. Тела этих методов пустые, поскольку трудно предвидеть какие переменные и операторы понадобятся в объектах наследующих классов.

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

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

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

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

Поэтому абстрактные классы могут быть только наследуемыми. Когда класс наследует абстрактный класс, он должен реализовать все абстрактные методы наследуемого класса.

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

Добавление абстрактных методов в базовый класс приводит к созданию абстрактного базового класса и лишает возможности непосредственного создания новых объектов базового класса.

Если класс является абстрактным базовым классом, он может определять любое число абстрактных методов

Тем не менее, можно создавать указатели типа абстрактный базовый класс и обеспечивать динамический полиморфизм.
ПРИМЕР OOPEx08_01. Чистые виртуальные функции в С++
/**************************************************************

* КАФЕДРА № 304 2 КУРС ООП *

*-------------------------------------------------------------*

* Project Type : Win32 Console Application *

* Project Name : OOPEx08_01 *

* File Name : OOPEx08_01_АБСТРАКТНЫЙ КЛАСС.CPP *

* Language : C/C++ *

* Programmer(s) : Чечиков Ю.Б. *

* Modified By : *

* Lit Source : Шилдт Г. С++ самоучитель с.314 *

* Created : 17/04/20 *

* Last Revision : 17/04/20 *

* Comment(s) : ЧИСТЫЕ ВИРТУАЛЬНЫЕ ФУНКЦИИ *

* АБСТРАКТНЫЙ БАЗОВЫЙ КЛАСС *

* ПОЛИМОРФИЗМ *

*-------------------------------------------------------------*

* РЕЗУЛЬТАТ РАБОТЫ ПРОГРАММЫ *

* *

* ПРЯМОУГОЛЬНИК (4, 5) *

* Периметр = 18 *

* Площадь = 20 *

* *

* ПРЯМОУГОЛЬНЫЙ ТРЕУГОЛЬНИК (3, 4) *

* Периметр = 12 *

* Площадь = 6 *

* *

* Для продолжения нажмите любую клавишу . . . *

* *

**************************************************************/

#pragma once
#include

using namespace std;

/**************************************************************/

/* ОБЪЯВЛЕНИЕ И РЕАЛИЗАЦИЯ МЕТОДОВ КЛАССОВ */

/**************************************************************/

/*------ АБСТРАКТНЫЙ БАЗОВЫЙ КЛАСС ------------------------*/

class Figure //абстрактный базовый класс

{

protected:

double mDim1; //линейные элементы фигуры

double mDim2;

public:

//обычный метод

void GetDimention(double &xd1, double &xd2)

{

//вернуть размеры

xd1 = mDim1;

xd2 = mDim2;

} // GetDimention ()
virtual double CalcPerim() = 0; //чистая виртуальная функция

virtual double CalcSquare() = 0; //чистая виртуальная функция
}; //class Figure
/******** Класс ПРЯМОУГОЛЬНИК ***********************/

//наследование абстрактного базового класса

class Rectangle : public Figure

{

public:

//----------------------------------------------------

//конструктор

Rectangle(double InitSide1, double InitSide2)

{

mDim1 = InitSide1; //стороны

mDim2 = InitSide2;

cout << "(" << mDim1 << ", " << mDim2 << ")\n";

}//Rectangle()
//----------------------------------------------------

//принудительная реализация чистой виртуальной функции

double CalcPerim() //рассчитать периметр прямоугольника

{

double d1, d2;

GetDimention(d1, d2); //узнать размеры

return 2 * (d1 + d2); //вернуть периметр

}//CalcPerim()
//----------------------------------------------------

//принудительная реализация чистой виртуальной функции

double CalcSquare() //рассчитать площадь прямоугольника

{

double d1, d2;

GetDimention(d1, d2); //узнать размеры

return (d1 * d2); //вернуть площадь

}//CalcSquare()
}; //class Rectangle
/******** Класс ПРЯМОУГОЛЬНЫЙ ТРЕУГОЛЬНИК ****************/

//наследование интерфейса

class RightTriangle : public Figure

{

public:

//----------------------------------------------------

//конструктор

RightTriangle(double InitLeg1, double InitLeg2)

{

mDim1 = InitLeg1; //катеты

mDim2 = InitLeg2;

cout << "(" << mDim1 << ", " << mDim2 << ")\n";

}//RightTriangle()
//----------------------------------------------------

//принудительная реализация чистой виртуальной функции

double CalcPerim() //рассчитать периметр треугольника

{

double Leg1, Leg2, Hypot; //катеты и гипотенуза

double Perim; //периметр треугольника
GetDimention(Leg1, Leg2); //узнать размеры

Hypot = sqrt(Leg1*Leg1 + Leg2*Leg2);

Perim = Leg1 + Leg2 + Hypot;
return Perim; //вернуть периметр

}//CalcPerim()
//----------------------------------------------------

//принудительная реализация чистой виртуальной функции

double CalcSquare() //рассчитать площадь треугольника

{

double Leg1, Leg2; //катеты

GetDimention(Leg1, Leg2); //узнать размеры

return (Leg1 * Leg2) / 2; //вернуть площадь

}//CalcSquare()
};//end class RightTriangle
/**************************************************************/

/* О С Н О В Н А Я П Р О Г Р А М М А */

/**************************************************************/

int main()

{

//стандартная молитва

setlocale(LC_ALL, "RUSSIAN"); //подключение русского языка

system("color F0"); //экран белый, буквы черные
//********** ПРЯМОУГОЛЬНИК **************

cout << "\tПРЯМОУГОЛЬНИК ";

Rectangle ARectangle(4,5);

cout << "\t Периметр = " << ARectangle.CalcPerim() << endl;

cout << "\t Площадь = " << ARectangle.CalcSquare() << endl;

//********** ПРЯМОУГОЛЬНЫЙ ТРЕУГОЛЬНИК **************

cout << "\tПРЯМОУГОЛЬНЫЙ ТРЕУГОЛЬНИК ";

RightTriangle ARightTriangle(3, 4);

cout << "\t Периметр = " << ARightTriangle.CalcPerim() << endl;

cout << "\t Площадь = "<< ARightTriangle.CalcSquare() << endl;
cout << endl;
system("pause");

return 0;

} //end main()
/******* End of File OOPEx08_01_АБСТРАКТНЫЙ КЛАСС.CPP ********/
В С# абстрактный базовый класс можно сделать программными средствами, используя ключевое слово abstract.
//обозначение класса Car как абстрактного запрещает

//непосредственное создание его экземпляров

abstract public class Car

{

. . . //тело объявления класса

}//end abstract Car
При попытке создать экземпляр класса Car мы получим ошибку компиляции

Car ACar = new Car(); // Ошибка! Нельзя создать экземпляр абстрактного класса.


абстрактные и ВИРТУАЛЬНЫЕ МЕТОДЫ


Абстрактный метод может быть описан только в абстрактном классе.

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

В С# используется модификатор abstract.

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

В абстрактных классах производным классам придется принудительно использовать полиморфизм, поскольку они обязаны создавать свои реализации абстрактных методов.

Экземпляр абстрактного класса создать нельзя, потому что нет реализации методов.
Другой тип методов – виртуальные методы. Виртуальный метод имеет тело при определении, абстрактный – нет.

Виртуальный метод – метод класса, который МОЖЕТ БЫТЬ переопределен в классе-наследнике, а МОЖЕТ И НЕ БЫТЬ. Используется модификатор virtual.

По умолчанию наследник получает возможность вызова метода предка, который не помечен как private. Но иногда возникает необходимость изменить этот метод в соответствии с логикой конкретного класса.

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

Виртуальный метод позволяет решить эту проблему. Класс-наследник может переопределить базовый функционал метода, дополнив его или определив заново, не меняя своей сигнатуры.

Базовая реализация метода вызывается через base.ИмяМетода().

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

Виртуальная функция может быть переопределена, а может быть и нет.
Базовая реализация метода – реализация метода в классе, являющимся родительским по отношению к рассматриваемому.

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

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

Процесс определения виртуального метода внутри наследуемого класса, при котором частично или полностью изменяется тело метода. Переопределять виртуальный метод не обязательно. Если наследуемый класс предоставляет свою собственную версию виртуального метода, то используется метод наследуемого класса, иначе – виртуальная версия класса-предка.

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


ИНТЕРФЕЙС


В ООП иногда возникает необходимость определения действий, выполняемых классом, без указания способа выполнения этих действий, например, как это происходит в абстрактном методе.

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

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

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

Синтаксис интерфейсов подобен синтаксису абстрактных классов – интерфейсные методы не имеют тела. Интерфейс определяет что нужно делать, но не показывает как именно это нужно делать. Если брать пример из реальной жизни, то интерфейс –это контракт, план работ, где указано что надо делать, но не указано как это делать.

После того как интерфейс определен, он может наследоваться произвольным количеством классов. Один класс может наследоваться от нескольких интерфейсов (множественное наследование в C#).

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

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

Пример. Интерфейс – это стандартная розетка с двумя гнездами, а реализация интерфейса – любой электрический прибор, имеющий вилку с двумя штеккерами.

Базовый класс, содержащий только абстрактные методы становится интерфейсом. Если все методы базового класса имеют реализацию, то это обычный базовый класс.
Интерфейс (C#) – подобное классу описание сущности, которое может включать в себя сигнатуры методов, свойств, событий или индексаторов. Содержат только методы и свойства, поля содержать не может, так как не описывает реализацию. Не может порождать экземпляры потому что не реализован – это описательная структура.

При наличии интерфейса и класса, реализующего данный интерфейс, дается гарантия, что у класса реализованы все методы, определенные в интерфейсе.

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

ПРИМЕР OOPEx09_02 Интерфейс и два прямых потомка

/**************************************************************

* КАФЕДРА № 304 2 КУРС ООП *

*-------------------------------------------------------------*

* Project Type : Win32 Console Application *

* Project Name : OOPEx08_02 *

* File Name : OOPEx08_02_ИНТЕРФЕЙС И НАСЛЕДОВАНИЕ.CPP *

* Language : C/C++ *

* Programmer(s) : Чечиков Ю.Б. *

* Modified By : *

* Created : 16/04/20 *

* Last Revision : 16/04/20 *

* Comment(s) : ИНТЕРФЕЙС И НАСЛЕДОВАНИЕ *

* ЧИСТЫЕ ВИРТУАЛЬНЫЕ ФУНКЦИИ *

* ПОЛИМОРФИЗМ И ДИНАМИЧЕСКИЙ ПОЛИМОРФИЗМ *

*-------------------------------------------------------------*

* РЕЗУЛЬТАТ РАБОТЫ ПРОГРАММЫ *

* *

* ПОЛИМОРФИЗМ ЧЕРЕЗ ИНТЕРФЕЙС *

* Фигура ТОЧКА Point APoint; *

* /Point Show(): Я маленькая ТОЧКА *

* /Point Hide(): ТОЧКА скрылась *

* *

* Фигура КРУГ Circle ACircle; *

* /Circle Show(): Я большой КРУГ *

* /Circle Hide(): КРУГ исчез *

* *

* ДИНАМИЧЕСКИЙ ПОЛИМОРФИЗМ ЧЕРЕЗ ИНТЕРФЕЙС IFigure * pIFig; *

* IFigure * pIFig; *

* Фигура ТОЧКА pIFig = &APoint; *

* /Point Show(): Я маленькая ТОЧКА *

* /Point Hide(): ТОЧКА скрылась *

* *

* Фигура КРУГ pIFig = &ACircle; *

* /Circle Show(): Я большой КРУГ *

* /Circle Hide(): КРУГ исчез *

* *

* Для продолжения нажмите любую клавишу . . . *

* *

**************************************************************/

#pragma once
#include

using namespace std;
/**************************************************************/

/* О Б Ъ Я В Л Е Н И Е И Н Т Е Р Ф Е Й С А */

/**************************************************************/

/*-------------- ИНТЕРФЕЙС --------------------------------*/

class IFigure

{

public: //эти методы доступны извне всем в программе
virtual void Show() = 0; //чистая виртуальная функция

virtual void Hide() = 0; //чистая виртуальная функция

};//end class IFigure
/**************************************************************/

/* ОБЪЯВЛЕНИЕ И РЕАЛИЗАЦИЯ МЕТОДОВ КЛАССОВ */

/**************************************************************/

/*------- Класс ТОЧКА ---------------------------------------*/

//наследование интерфейса

class Point : public IFigure

{

protected:

bool Visible; //чтоб было
public:

//показать фигуру ТОЧКА

void Show() //принудительная реализация интерфейсного метода

{

cout << "/Point Show(): Я маленькая ТОЧКА\n";

};

//скрыть фигуру ТОЧКА

void Hide() //принудительная реализация интерфейсного метода

{

cout << "/Point Hide(): ТОЧКА скрылась\n";

};

};//end class Point

/*-------------- Класс КРУГ --------------------------------*/

//наследование интерфейса

class Circle : public IFigure

{

private: //необязательно , т.к. по умолчанию

int Radius; //радиус круга, чтоб было

public:

//показать фигуру КРУГ

void Show() //принудительная реализация интерфейсного метода

{

cout << "/Circle Show(): Я большой КРУГ\n";

};

//скрыть фигуру КРУГ

void Hide() //принудительная реализация интерфейсного метода

{

cout << "/Circle Hide(): КРУГ исчез\n";

};
void Expand(int DeltaRad) //увеличить радиус КРУГА

{

//ПУСТО

}; //Expand()
void Reduce(int DeltaRad) //уменьшить радиус КРУГА

{

//ПУСТО

}; //Reduce()
};//end class Circle

/**************************************************************/

/* О С Н О В Н А Я П Р О Г Р А М М А */

/**************************************************************/

int main()

{

//стандартная молитва

setlocale(LC_ALL, "RUSSIAN"); //подключение русского языка

system("color F0"); //экран белый, буквы черные
//полиморфизм через интерфейс

cout << "\tПОЛИМОРФИЗМ ЧЕРЕЗ ИНТЕРФЕЙС\n";
//мы знаем, что в иерархии используется ИНТЕРФЕЙС
//********** ТОЧКА **************

cout << "\tФигура ТОЧКА Point APoint; \n";

Point APoint; //объект (экземпляр) производного класса

APoint.Show();

APoint.Hide();
//********** КРУГ **************

Circle ACircle; //объект (экземпляр) производного класса

cout << "\n\tФигура КРУГ Circle ACircle;\n";

ACircle.Show();

ACircle.Hide();
//динамический полиморфизм через интерфейс

cout << "\n\tДИНАМИЧЕСКИЙ ПОЛИМОРФИЗМ ЧЕРЕЗ ИНТЕРФЕЙС\n";

cout << "\t\t IFigure * pIFig; \n";

IFigure * pIFig;
//********** ТОЧКА **************

pIFig = &APoint;

cout << "\tФигура ТОЧКА pIFig = &APoint;\n";

pIFig->Show();

pIFig->Hide();
//********** КРУГ **************

pIFig = &ACircle;

cout << "\n\tФигура КРУГ pIFig = &ACircle;\n";

pIFig->Show();

pIFig->Hide();
cout << endl;
system("pause");

return 0;

} //end main()
/****** End of File OOPEx08_02_ИНТЕРФЕЙС И НАСЛЕДОВАНИЕ.CPP ******/


По своей сути абстрактный класс подобен интерфейсу, однако, между ними существует минимум два важных различия.

  1. Во-первых, абстрактный класс может содержать в себе и конструкторы, и поля, и тела методов, в то время как интерфейс лишь представляет набор (чаще всего) сигнатур методов и свойств.

  2. Во-вторых (C#), у класса может быть только один предок типа класса, но неограниченное число интерфейсов, которые он реализует ( = наследует).

Причина этого различия кроется в следующем: в C# нет множественного наследования – наследования одновременно от двух и более классов. Так как любой класс всегда является потомком базового класса Object, наследование одновременно от двух и более классов приведет к множественным ссылкам на члены класса Object. А вот интерфейс не находится в этой иерархии, поэтому интерфейсы необходимы в случае, если в предметной области какая-то сущность должна совмещать в себе черты двух и более иных сущностей.


Интерфейсы или абстрактные классы


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

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

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

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

  1. Если надо определить общий функционал для родственных объектов

  2. Если мы проектируем довольно большую функциональную единицу, которая содержит много базового функционала

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

  4. Если же нам вдруг надо будет поменять название или параметры метода интерфейса, то придется вносить изменения и также во всех классы, которые данный интерфейс реализуют.



Посмотрим на примере - абстрактный класс.

Допустим, у нас есть система транспортных средств: легковой автомобиль, автобус, трамвай, поезд и т.д. Поскольку данные объекты являются родственными, мы можем выделить у них общие признаки, то в данном случае можно использовать абстрактные классы:
//ТранспортноеСредство (C#)

public abstract class Vehicle // Транспортное средство

{

public abstract void Move();

}// abstract class Vehicle

//производный класс Машина

public class Car : Vehicle

{

public override void Move()

{

Console.WriteLine("Машина едет");

}// Move()

}// class Car

//производный класс Автобус

public class Bus : Vehicle

{

public override void Move()

{

Console.WriteLine("Автобус едет");

}// Move()

}// class Bus

//производный класс Трамвай

public class Tram : Vehicle

{

public override void Move()

{

Console.WriteLine("Трамвай едет");

}// Move()

}// class Tram

Абстрактный класс Vehicle (англ. Транспортное средство) определяет абстрактный метод перемещения Move(), а классы-наследники его реализуют.

Когда следует использовать интерфейсы:

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

  • Если мы проектируем небольшой функциональный тип



Посмотрим на примере - интерфейс.

Предположим, что наша система транспорта не ограничивается вышеперечисленными транспортными средствами. Например, мы можем добавить самолеты, лодки. Возможно, также мы добавим лошадь - животное, которое может также выполнять роль транспортного средства. Также можно добавить дирижабль. В общем получается довольно широкий круг объектов, которые связаны только тем, что являются транспортным средством и должны реализовать некоторый метод Move(), выполняющий перемещение.

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

Например, лошадь может быть классом в структуре системы классов животного мира.

Возможная реализация интерфейса могла бы выглядеть следующим образом:
//********* АБСТРАКТНЫЕ ПРЕДКИ ***********

//интерфейс СпособностьКДвижению

publicinterfaceIMovable

{

voidMove(); //разновидность движения

}// interfaceIMovable

//абстрактный класс Транспортное Средство

publicabstractclassVehicle

{

abstract void Capacity(); //вместимость

} // abstractclassVehicle

//********* КОНКРЕТНЫЕ ПОТОМКИ ***********

//классАвтомобиль

public class Car : Vehicle, IMovable // ABC + Intrface

{

//вместимость

publicvoidCapacity()

{

Console.WriteLine("Машина длясемьи");

} // Capacity()
//реализация вида движения

publicvoidMove()

{

Console.WriteLine("Машина едет");

}// Move()

}// class Car

//классАвтобус

public class Bus : Vehicle, IMovable

{

//вместимость

public void Capacity()

{

Console.WriteLine("Автобус для группы");

} // Capacity()

//реализация вида движения

public void Move()

{

Console.WriteLine("Автобус едет");

}// Move()

}// class Bus

//классЛошадь

public class Hourse : IMovable

{

//реализация вида движения

public void Move()

{

Console.WriteLine("Лошадь скачет");

}// Move()

}// class Hourse

//классСамолет

public class Aircraft : IMovable

{

//реализация вида движения

public void Move()

{

Console.WriteLine("Самолет летит");

}// Move()

}// class Aircraft

Теперь метод Move() объявляется в интерфейсе IMovable, метод Capacity() объявляется в классе Vehicle, а конкретные классы его реализуют. Это более высокий уровень иерархии.

Итак:

ЕСЛИ классы относятся к единой системе классификации,

ТО

выбирается абстрактный класс

ИНАЧЕ

выбирается интерфейс.

КОНЕЦЕСЛИ
Говоря об использовании абстрактных классов и интерфейсов, можно привести еще такую аналогию, как состояние и действие.

Как правило, абстрактные классы фокусируются на общем состоянии классов-наследников. В то время как интерфейсы строятся вокруг какого-либо общего действия.

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

Можно ли для них создать общий абстрактный класс?

Можно, но это не будет оптимальным решением, тем более у нас могут быть какие-то родственные сущности, которые мы, возможно, тоже захотим использовать.

Поэтому для каждой вышеперечисленной сущности мы можем определить свою систему классификации.

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

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

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

А для общего действия можно интерфейс, например, IHeatable, в котором бы был метод Heat(), и этот интерфейс реализовать во всех необходимых классах.

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

А для одноплановых классов, которые имеют общее состояние, лучше определять абстрактный класс.

ОТНОШЕНИЯ МЕЖДУ КЛАССАМИ И ОБЪЕКТАМИ
Можно выделить несколько основных отношений между классами и объектами:

  1. наследование,

  2. реализация,

  3. ассоциация,

  4. композиция

  5. агрегация.




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

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

Наследование определяет отношение IS A, то есть "является".

Например:

//базовый класс

class User

{

//свойства

public int Id { get; set; } //идентификатор

public string Name { get; set; } //имя пользователя

}// class User
//производный класс

class Manager : User

{

public string Company{ get; set; } //имя компании

}// class Manager
В данном случае используется наследование, а объекты класса Manager также являются и объектами класса User.

С помощью диаграмм UML отношение между классами выражается в незакрашенной стрелочке от класса-наследника к классу-родителю:




    1. Реализация

Реализация предполагает определение интерфейса и его реализацию в классах.

Например, имеется интерфейс IMovable с методом Move, который реализуется в классе Car:
//способность к движению

public interface IMovable

{

void Move();

}// interface IMovable
//производный класс Машина

public class Car : IMovable

{

//реализация вида движения

public void Move()

{

Console.WriteLine("Машина едет");

}// Move()

}// class Car

?

С помощью диаграмм UML отношение реализации также выражается в незакрашенной стрелочке от класса к интерфейсу, только линия теперь пунктирная:





    1. Ассоциация

Ассоциация - это отношение, при котором объекты одного типа неким образом связаны с объектами другого типа.

Например, объект одного типа содержит или использует объект другого типа. Например, игрок играет в определенной команде:
//базовый класс Команда

class Team

{
} // class Team
//производный класс Игрок

class Player

{

public Team Team { get; set; }

} // class Player

Класс Player связан отношением ассоциации с классом Team.

На схемах UML ассоциация обозначается в виде обычной стрелки:




Нередко при отношении ассоциации указывается кратность связей. В данном случае единица у Team и звездочка у Player на диаграмме отражает связь 1 ко многим. То есть одна команда будет соответствовать многим игрокам.

Агрегация и композиция являются частными случаями ассоциации.



    1. Композиция

Композиция определяет отношение HAS A, то есть отношение "имеет". Например, класс автомобиля содержит объект класса электрического двигателя:
//класс ЭлектрическийДвигатель

public class ElectricEngine

{

}// class ElectricEngine
//класс Автомобиль

public class Car

{

//объект другого класса

ElectricEngine engine;
//конструктор

public Car()

{

//создание объекта другого класса

engine = new ElectricEngine();

}// Car()

} // class Car
При этом класс автомобиля полностью управляет жизненным циклом объекта двигателя. При уничтожении объекта автомобиля в области памяти вместе с ним будет уничтожен и объект двигателя. И в этом плане объект автомобиля является главным, а объект двигателя - зависимым.

На диаграммах UML отношение композиции проявляется в обычной стрелке от главной сущности к зависимой, при этом со стороны главной сущности, которая содержит, объект второй сущности, располагается закрашенный ромбик:




    1. Агрегация

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

public abstract class Engine

{

}// abstract class Engine

//класс Автомобиль

public class Car

{

Engine engine;
public Car(Engine eng) //ссылка на абстрактный класс

{

engine = eng; //связь с внешним миром

}//Car()

}// class Car
При агрегации реализуется слабая связь, то есть в данном случае объекты Car и Engine будут равноправны. В конструктор Car передается ссылка на уже имеющийся объект Engine. И, как правило, определяется ссылка не на конкретный класс, а на абстрактный класс или интерфейс, что увеличивает гибкость программы.

Отношение агрегации на диаграммах UML отображается также, как и отношение композиции, только теперь ромбик будет незакрашенным:


При проектировании отношений между классами надо учитывать некоторые общие рекомендации.

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

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

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

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


КОНЕЦ

/


написать администратору сайта