суббота, 20 июня 2015 г.

Свойства ООП. Инкапсуляция. Наследование

Основные свойства (инструменты) ООП

Обычно выделяют четыре свойства (иногда их ещё называют основными инструментами) ООП:
  1. Абстракция;
  2. Инкапсуляция;
  3. Наследование;
  4. Полиморфизм;

Абстракция

Под понятием «Абстракция» подразумевается выделение программистом только тех свойств объекта предметной области, которые необходимы для решения конкретной задачи. Так, например, для программного обеспечения деканата в классе «Студент» необходимо создать поля «ФИО» и «Номер зачётной книжки». При этом, для работы деканата совершенно не важны такие характеристики студента, как «Рост» и «Вес». Описывая класс, мы абстрагируемся от несущественных для нас в данной задаче параметров.

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

Инкапсуляция подразумевает предоставление внешним потребителям лишь той части класса, использование которой не повредит работе класса. В рамках данного поста мы будем рассматривать три уровня инкапсуляции составных частей объекта:
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();
        }
    }
Как мы видим из примера, несмотря на обращение ко всем трём элементам массива, как к экземпляру базового класса, значения характеристик определяются самими наследниками. Но, при этом, количество и типы характеристик передаются по наследству от предка.
Ещё одним мощным инструментом ООП является полиморфизм. Однако, его мы рассмотрим в следующем посте.

Заключение


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

Комментариев нет:

Отправить комментарий