Опубликован: 18.05.2011 | Уровень: специалист | Доступ: платный
Лекция 3:

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

< Лекция 2 || Лекция 3 || Лекция 4 >
Аннотация: Рассматривается одна из основных технологий объектно-ориентированного программирования - инкапсуляция. Приведены примеры на C# создания классов и использования инкапсуляции.

Цель лекции: научить использовать технологию инкапсуляции при программировании на 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}

Теперь у нас есть новый тип данных - TAirplan. Если мы попробуем использовать этот класс следующим образом:

\begin{verbatim}
TAirplan Airplan;

Airplan.Name = "Boeing"; Airplan.Altitude = 1000;
Airplan.IncAltitude(100);
\end{verbatim}

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

\begin{verbatim}
TAirplan Airplan = new TAirplan();
\end{verbatim}

Во-вторых, использование полей класса и вызов его методов в строках:

\begin{verbatim}
Airplan.Name = "Boeing"; Airplan.Altitude = 1000;
Airplan.IncAltitude(100);
\end{verbatim}

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

  • public
  • protected
  • private
  • new
  • internal
  • static
  • readonly
  • volatile

Некоторые спецификаторы могут использоваться совместно, а некоторые являются взаимоисключающими. По умолчанию, если не указан спецификатор доступа, полям и методам присваивается спецификатор private. Этот спецификатор означает, что к данному полю и методу невозможно обратиться из другого класса и даже из класса - наследника нашего класса. Спецификатор protected аналогичен спецификатору private за исключением того, что поля и методы с этим спецификатором "видны" для классов наследников данного класса. Наиболее либеральным является спецификатор public. К полям и методам, помеченным этим спецификатором, можно обращаться из любых других классов. Может показаться, что легче всего помечать все поля и методы public, чтобы "не мучаться". Часто начинающие программисты именно так и поступают. Однако такой подход противоречит идеологии объектно-ориентированного программирования. Важная идея инкапсуляции состоит в сокрытии внутренних полей и методов от внешней среды. Может показаться удивительным, но порой чем меньше мы знаем о внутреннем устройстве класса, тем легче и безопаснее им пользоваться.

Возвращаясь к нашему примеру, разумно дать возможность вызывать метод IncAltitude() для изменения высоты. Для этого нужно назначить для метода IncAltitude спецификатор доступа - public. Но не стоит давать возможность пользователям класса самостоятельно изменять высоту путем доступа к полю Altitude, поскольку тогда возможно превышение порога высоты. В тоже время, если у поля Altitude будет спецификатор доступа private, то пользователь не сможет не только изменить это поле, но и прочитать. Для этого нужно создать еще один метод, который будет выдавать пользователю значение высоты:

\begin{verbatim}
        public double GetAltitude()
        {
            return Math.Round(Altitude);
        }
\end{verbatim}

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

Однако если мы создадим наш класс в таком виде:

\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}

и попробуем его использовать таким образом:

\begin{verbatim}
    TAirplan Airplan = new TAirplan(); // создать объект
    Airplan.IncAltitude(100); // увеличить высоту
    Console.WriteLine("Высота = {0}", Airplan.GetAltitude());
    // показать высоту
\end{verbatim}

то мы увидим строку "Высота = 100". Однако есть одно обстоятельство, состоящее в том, что мы никак не инициализировали наши поля перед использованием. Конечно, мы можем знать, что C# всегда инициализирует переменные типа double нулем, но желательно инициализировать переменные явно. К счастью, в объектно-ориентированном программировании каждый класс может иметь специальный метод (и даже не один) называемый конструктором. Конструктор - это метод, который всегда вызывается при создании экземпляра класса. Кстати, если в классе не определен ни один конструктор, то компилятор создает конструктор по умолчанию. Конструктор имеет следующие черты, которые выделяют его из других методов.

  1. Имя конструктора всегда совпадает с именем класса. Никакой другой метод не может иметь имя, совпадающее с именем класса.
  2. Конструктор не имеет возвращаемого типа. Никакой другой метод не может не иметь возвращаемого типа.
  3. Конструктор можно вызвать только путем создания экземпляра класса.

Добавим в наш класс конструктор:

\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}

Теперь создавать экземпляр нашего класса нужно следующим образом:

\begin{verbatim}
        TAirplan Airplan = new TAirplan("Boeing", 0);
\end{verbatim}

Заметим, что мы пометили поле Name спецификатором доступа readonly, для того, чтобы извне можно было читать это поле, но нельзя было модифицировать. Обратим Ваше внимание на ключевое слово this, для доступа к полям самого класса. Это необходимо для того, чтобы отделить переменные-параметры от полей класса.

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

Ключевые термины

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

Конструктор - метод, который всегда вызывается при создании экземпляра класса.

Спецификаторы доступа - ключевые слова языка C# для указания уровня доступа к полю или методу класса.

Ссылка - именованный адрес в памяти, где хранится переменная, для доступа к этой переменной.

Экземпляр класса - переменная типа класс.

Краткие итоги: Рассмотрена технология инкапсуляции на примере классов C#. На модельных примерах показано применение этой технологии и ее эффективность.

< Лекция 2 || Лекция 3 || Лекция 4 >
Владимир Власов
Владимир Власов
Россия, Магнитогорск, МГТУ им. Носова
Наталья Фомина
Наталья Фомина
Россия, Санкт-Петербург