Инкапсуляция
Цель лекции: научить использовать технологию инкапсуляции при программировании на C#.
Начиная с этой лекции мы будем подробно рассматривать принципы объектно-ориентированного программирования только на примере языка C#. Язык C# является полноценным объектно-ориентированным языком программирования со строгим контролем типов. Мы будем рассматривать объектно-ориентированное программирование только для языка C#, поскольку этот язык поддерживает все основные конструкции объектно-ориентированного подхода за исключением множественного наследования.
В C# объектно-ориентированное программирование поддерживается с помощью классов. Класс - это пользовательский тип данных, который включает в себя как данные, так и программный код в виде функций. Данные хранимые в классе называются полями, а функции класса называются методами. Также класс может содержать свойства и индексаторы, которые в определенном смысле объединяют поля и методы. После того, как класс описан, можно создавать переменные с типом этого класса. Эти переменные называются экземплярами класса или объектами данного класса. Приведем пример простейшего класса:
![\begin{verbatim}
class TAirplan
{
string Name;
double Altitude;
double MaxAltitude = 12000;
void IncAltitude(double delta)
{
Altitude += delta;
if(Altitude > MaxAltitude)
{
Altitude = MaxAltitude;
}
}
}
\end{verbatim}](/sites/default/files/tex_cache/b8578cc3524a1e34e645a0c43a3b1e5a.png)
Теперь у нас есть новый тип данных - . Если мы попробуем
использовать этот класс следующим образом:
![\begin{verbatim}
TAirplan Airplan;
Airplan.Name = "Boeing"; Airplan.Altitude = 1000;
Airplan.IncAltitude(100);
\end{verbatim}](/sites/default/files/tex_cache/db2526b11a974a4e72e470929a5df0fe.png)
то обнаружим следующие ошибки. Во-первых, для создания экземпляра класса недостаточно объявить соответствующую переменную. Экземпляр класса необходимо создать с помощью оператора New. Дело в том, что переменная типа класса на самом деле является не переменной, а ссылкой на переменную. По сути ссылка - это адрес в памяти, где должна храниться переменная. Поэтому процедура создания класса включает в себя выделение соответствующей памяти, инициализации полей класса и присвоение ссылке адреса выделенной памяти. Создать экземпляр нашего класса можно следующим образом:
![\begin{verbatim}
TAirplan Airplan = new TAirplan();
\end{verbatim}](/sites/default/files/tex_cache/3aa342877b93b8aea0de741ba16a496d.png)
Во-вторых, использование полей класса и вызов его методов в строках:
![\begin{verbatim}
Airplan.Name = "Boeing"; Airplan.Altitude = 1000;
Airplan.IncAltitude(100);
\end{verbatim}](/sites/default/files/tex_cache/7f5cfad8c26cbf5780b6a4383d914af0.png)
будет невозможным. Дело в том, что по умолчанию определенные поля и методы являются закрытыми от внешнего обращения. К таким полям можно обращаться только из методов самого класса. Для того, чтобы сделать возможным общаться к полям и методам класса необходимо использовать спецификаторы доступа. В C# существуют следующие спецификаторы доступа:
- public
- protected
- private
- new
- internal
- static
- readonly
- volatile
Некоторые спецификаторы могут использоваться совместно, а
некоторые являются взаимоисключающими. По умолчанию, если не
указан спецификатор доступа, полям и методам присваивается
спецификатор private. Этот спецификатор означает, что к данному
полю и методу невозможно обратиться из другого класса и даже из
класса - наследника нашего класса. Спецификатор
аналогичен спецификатору
за исключением того, что поля и
методы с этим спецификатором "видны" для классов наследников
данного класса. Наиболее либеральным является спецификатор
.
К полям и методам, помеченным этим спецификатором, можно
обращаться из любых других классов. Может показаться, что легче
всего помечать все поля и методы
, чтобы "не мучаться".
Часто начинающие программисты именно так и поступают. Однако такой
подход противоречит идеологии объектно-ориентированного программирования. Важная
идея инкапсуляции состоит в сокрытии внутренних полей и методов от
внешней среды. Может показаться удивительным, но порой чем меньше
мы знаем о внутреннем устройстве класса, тем легче и безопаснее им
пользоваться.
Возвращаясь к нашему примеру, разумно дать возможность вызывать
метод для изменения высоты. Для этого нужно назначить
для метода
спецификатор доступа -
. Но не
стоит давать возможность пользователям класса самостоятельно
изменять высоту путем доступа к полю
, поскольку тогда
возможно превышение порога высоты. В тоже время, если у поля
будет спецификатор доступа
, то пользователь не
сможет не только изменить это поле, но и прочитать. Для этого
нужно создать еще один метод, который будет выдавать пользователю
значение высоты:
![\begin{verbatim}
public double GetAltitude()
{
return Math.Round(Altitude);
}
\end{verbatim}](/sites/default/files/tex_cache/3a7d61ae5a005527ebfc324f2557b0b2.png)
Заметим, что таким образом мы контролируем, чтобы пользователь всегда получал значение высоты, округленное до ближайшего целого.
Однако если мы создадим наш класс в таком виде:
![\begin{verbatim}
class TAirplan
{
string Name;
double Altitude;
double MaxAltitude = 12000;
void IncAltitude(double delta)
{
Altitude += delta;
if(Altitude > MaxAltitude)
{
Altitude = MaxAltitude;
}
}
public double GetAltitude()
{
return Math.Round(Altitude);
}
}
\end{verbatim}](/sites/default/files/tex_cache/3f477e6f0af8497002413c553b8d05a7.png)
и попробуем его использовать таким образом:
![\begin{verbatim}
TAirplan Airplan = new TAirplan(); // создать объект
Airplan.IncAltitude(100); // увеличить высоту
Console.WriteLine("Высота = {0}", Airplan.GetAltitude());
// показать высоту
\end{verbatim}](/sites/default/files/tex_cache/dc7ae7ab2d3704f80f712fd9da8cbb7d.png)
то мы увидим строку "Высота = 100". Однако есть одно обстоятельство, состоящее в том, что мы никак не инициализировали наши поля перед использованием. Конечно, мы можем знать, что C# всегда инициализирует переменные типа double нулем, но желательно инициализировать переменные явно. К счастью, в объектно-ориентированном программировании каждый класс может иметь специальный метод (и даже не один) называемый конструктором. Конструктор - это метод, который всегда вызывается при создании экземпляра класса. Кстати, если в классе не определен ни один конструктор, то компилятор создает конструктор по умолчанию. Конструктор имеет следующие черты, которые выделяют его из других методов.
- Имя конструктора всегда совпадает с именем класса. Никакой другой метод не может иметь имя, совпадающее с именем класса.
- Конструктор не имеет возвращаемого типа. Никакой другой метод не может не иметь возвращаемого типа.
- Конструктор можно вызвать только путем создания экземпляра класса.
Добавим в наш класс конструктор:
![\begin{verbatim}
class TAirplan
{
public readonly string Name;
double Altitude;
double MaxAltitude = 12000;
public TAirplan(string Name, double Altitude)
{
this.Name = Name;
this.Altitude = Altitude;
}
public void IncAltitude(double delta) { ... }
public double GetAltitude() { ... }
}
\end{verbatim}](/sites/default/files/tex_cache/51c0ea417557cb457fc2cedbe42fafdf.png)
Теперь создавать экземпляр нашего класса нужно следующим образом:
![\begin{verbatim}
TAirplan Airplan = new TAirplan("Boeing", 0);
\end{verbatim}](/sites/default/files/tex_cache/100eee83d1a3d8d8ff241949bebf25e5.png)
Заметим, что мы пометили поле спецификатором доступа
, для того, чтобы извне можно было читать это поле, но
нельзя было модифицировать. Обратим Ваше внимание на ключевое
слово
, для доступа к полям самого класса. Это необходимо для
того, чтобы отделить переменные-параметры от полей класса.
Рассмотренная выше технология программирования называется инкапсуляцией. Инкапсуляцией называется технология программирования, при которой реализуется тщательный контроль доступа к внутренним полям и методам класса. На наш взгляд инкапсуляция является наиболее важным принципом объектно-ориентированного программирования.
Ключевые термины
Класс - пользовательский тип данных, который включает в себя как данные, так и программный код в виде функций.
Конструктор - метод, который всегда вызывается при создании экземпляра класса.
Спецификаторы доступа - ключевые слова языка C# для указания уровня доступа к полю или методу класса.
Ссылка - именованный адрес в памяти, где хранится переменная, для доступа к этой переменной.
Экземпляр класса - переменная типа класс.
Краткие итоги: Рассмотрена технология инкапсуляции на примере классов C#. На модельных примерах показано применение этой технологии и ее эффективность.