Основные свойства (инструменты) ООП
Обычно выделяют четыре свойства (иногда их ещё называют
основными инструментами) ООП:
- Абстракция;
- Инкапсуляция;
- Наследование;
- Полиморфизм;
Абстракция
Под понятием «Абстракция» подразумевается выделение
программистом только тех свойств объекта предметной области, которые необходимы
для решения конкретной задачи. Так, например, для программного обеспечения
деканата в классе «Студент» необходимо создать поля «ФИО» и «Номер зачётной
книжки». При этом, для работы деканата совершенно не важны такие характеристики
студента, как «Рост» и «Вес». Описывая класс, мы абстрагируемся от
несущественных для нас в данной задаче параметров.
Инкапсуляция
Инкапсуляция подразумевает предоставление внешним
потребителям лишь той части класса, использование которой не повредит работе
класса. В рамках данного поста мы будем рассматривать три уровня инкапсуляции составных
частей объекта:
1.
Скрытый (private) – характеристика видна только внутри данного класса;
2.
Защищённый (protected) – характеристика видна внутри данного класса и его
наследников (механизм наследования разберём чуть позже);
3.
Открытый (public) – характеристика видна всем внешним потребителям;
Важно. В C# существует ещё один
модификатор доступа – internal.
Однако, по определённым причинам его мы рассмотрим в другом посте, посвящённом
сборкам и пространствам имён.
Для иллюстрации понятия «инкапсуляция» мы разберём следующий
пример. Класс, который решает квадратное уравнение. Поля класса, как это обычно
делается в ООП, защищённые. Поле «Дискриминант» и метод расчёта дискриминанта -
скрытые. Метод решения уравнения и конструктор – открытые. Таким образом,
пользователь может лишь создать экземпляр класса и запустить метод решения
уравнения. Он не может изменить значение какого-либо поля или даже прочитать
его значение.
///
/// Класс квадратного уравнения
///
public class SquareEquation
{
//Коэффициенты
protected Double _a;
protected Double _b;
protected Double _c;
//Дискриминант
private Double _d;
///
/// Расчёт дискриминанта
///
private Double GetDisc()
{
return Math.Pow(_b, 2) - (4 * _a * _c);
}
///
/// Конструктор класса
///
/// Коэффициент а
/// Коэффициент b
/// Коэффициент c
public SquareEquation(Double a, Double b, Double c)
{
_a = a;
_b = b;
_c = c;
}
///
/// Вычисляет корни уравнения или сообщает об их отсутствии
///
/// Массив корней уравнения
public Double[] GetResult()
{
_d = GetDisc();
if (_d < 0)
{
Console.WriteLine("Корней нет!");
return new Double[0];
}
else
{
Console.WriteLine("Корни найдены!");
Double x1 = (-_b + Math.Sqrt(_d)) / 2 / _a;
Double x2 = (-_b - Math.Sqrt(_d)) / 2 / _a;
return new Double[2] { x1, x2 };
}
}
}
//Код основной программы
static void Main(string[] args)
{
SquareEquation eq = new SquareEquation(1, 3, 2);
Double[] res = eq.GetResult();
for (Int32 i = 0; i < res.Count(); i++)
{
Console.Write(res[i] + " ");
}
Console.Read();
}
Важно. В
соответствии с требованиями ООП поля класса должны быть всегда скрыты от
внешних потребителей. Любая запись и чтение в поле должны осуществляться с
помощью методов класса. Конечно, такой подход неудобен ввиду своей
громоздкости. В дальнейшем мы рассмотрим более современные способы работы с
полями.
Наследование
Наследование является мощнейшим инструментом ООП, лежащим в
основе понятия «повторно используемый код». Наследование позволяет программисту
написать некий функционал единожды и использовать его впоследствии многократно,
расширяя и дополняя при необходимости.
При использовании наследования выделяют два класса: предок
(иногда называют базовым классом) и наследник. Все характеристики (поля, методы
и т.д.) базового класса без изменения передаются наследнику. Однако, в
классе-наследнике возможно изменение, например, методов базового класса (это
механизм полиморфизма, который мы разберём в следующем посте).
Рассмотрим следующий пример. Базовый класс «Автомобиль»
содержит поля «Номер», «Марка», «Цвет». Класс «Автомобиль Мерседес» является
наследником класса «Автомобиль», а значит тоже содержит все эти поля. Однако,
кроме того Мерседес содержит поле «Наличие парктроников», которого нет в
базовом классе.
///
/// Базовый автомобиль
///
public class CarBase
{
protected String _number;
protected String _mark;
protected String _color;
///
/// Конструктор базового класса
///
/// Гос. номер
/// Марка
/// Цвет
public CarBase(String number, String mark, String color)
{
_number = number;
_mark = mark;
_color = color;
}
}
///
/// Автомобиль "Мерседес"
///
public class CarMercedes : CarBase
{
protected Boolean _hasParktronics;
///
/// Переопределённый конструктор класса-наследника
///
/// Гос. номер
/// Марка
/// Цвет
/// Наличие парктроников
public CarMercedes(String number, String mark, String color, Boolean hasParktronics)
: base(number, mark, color)
{
_hasParktronics = hasParktronics;
}
}
Одной из важнейших особенностей наследования в ООП является
возможность использовать экземпляр класса-наследника, как экземпляр
класса-предка. Так как в наследнике реализован весь функционал предка, мы можем
задать тип переменной – предок, а значение – наследник. Однако, необходимо
учитывать, что при таком задании переменной все характеристики, добавленные в
наследнике, не будут видны для этой переменной.
Рассмотрим следующий пример. В массив экземпляров класса
«Автомобиль» помещаем три автомобиля: «Мерседес», «Ауди», «Фольксваген». В
базовом классе определяем метод «Печать», который выводит характеристики
автомобиля в консоль. Также определяем в нём конструктор, который записывает
значения из переданных параметров в характеристики. Затем в цикле перебираем
элементы массива и вызываем у них метод «Печать».
///
/// Базовый автомобиль
///
public class CarBase
{
protected String _number;
protected String _mark;
protected String _color;
///
/// Конструктор базового класса
///
/// Гос. номер
/// Марка
/// Цвет
public CarBase(String number, String mark, String color)
{
_number = number;
_mark = mark;
_color = color;
}
///
/// Метод печати характеристик автомобиля, который выводит в консоль строчку вида:
/// Номер 111, Марка автомобиль, Цвет белый
///
public void Print()
{
Console.WriteLine("Номер {0}, Марка {1}, Цвет{2}", _number, _mark, _color);
}
}
///
/// Автомобиль "Мерседес"
///
public class CarMercedes : CarBase
{
protected Boolean _hasParktronics;
///
/// Переопределённый конструктор класса-наследника
///
/// Гос. номер
/// Марка
/// Цвет
/// Наличие парктроников
public CarMercedes(Boolean hasParktronics)
: base("222", "Mercedes-Benz", "Black")
{
_hasParktronics = hasParktronics;
}
}
///
/// Автомобиль "Audi"
///
public class CarAudi : CarBase
{
public CarAudi() : base("333", "Audi", "Silver") { }
}
///
/// Автомобиль "VolksWagen"
///
public class CarVW : CarBase
{
public CarVW() : base("444", "VolksWagen", "Orange") { }
}
///
/// Код основной программы
///
class Program
{
static void Main(string[] args)
{
CarBase[] cars = new CarBase[3]
{
new CarMercedes(true), new CarAudi(), new CarVW()
};
for (Int32 i = 0; i < cars.Count(); i++)
{
cars[i].Print();
}
Console.Read();
}
}
Как мы видим из примера, несмотря на обращение ко всем трём
элементам массива, как к экземпляру базового класса, значения характеристик
определяются самими наследниками. Но, при этом, количество и типы характеристик
передаются по наследству от предка.
Ещё одним мощным инструментом ООП является полиморфизм.
Однако, его мы рассмотрим в следующем посте.
Заключение
Абстракция, инкапсуляция, наследование и полиморфизм являются
четырьмя мощными инструментами ООП. Абстракция позволяет выделять лишь нужные в
данной задаче характеристики объекта. Инкапсуляция позволяет указывать для
класса зоны доступа к характеристикам. Наследование позволяет организовывать
иерархию классов таким образом, что от базового класса к классам-наследникам
передаются характеристики и поведение.
Комментариев нет:
Отправить комментарий