пятница, 15 мая 2015 г.

Объекты и классы в объектно-ориентированном программировании

Вместо вступления
Этим постом в блоге я, начну серию постов об объектно-ориентированном программировании для студентов ИТАСУ МИСиС, которых мне приходится мучать, собственно, объектно-ориентированным программированием. Данная серия постов по моему замыслу должна вкупе с лекциями в группе OneDrive и самостоятельным трудом помочь студентам освоить науку программирования. А мне это поможет оживить блог и хоть как-то приблизиться к выполнению одной, поставленной ещё в 2012м году цели, о которой, впрочем, как-нибудь потом расскажу.

В качестве основного языка программирования для иллюстрации приводимых примеров я возьму C# из MS Visual Studio. Думаю, что для начинающих программистов это идеальное сочетание простоты и перспективности, а для «крутых кодеров» не составит труда перевести простые примеры на свой любимый язык. Конечно, я не буду останавливаться на таких ясных программисту вещах, как комментарии, операторные скобки, понятии оператора, условиях, циклах и прочих терминах и понятиях, которые Вы вполне можете изучить самостоятельно если ещё не изучили в школе. Впрочем, если это необходимо, то пишите в комментариях – я подумаю над подготовкой серии постов по основам именно алгоритмического программирования.

Итак, переходим к сути поста. J

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

Для удобства изучения окружающего мира мы делим объекты на классы. Так, «Порше» и «Лада» принадлежат к одному классу «Автомобиль» (как бы смешно это ни звучало), а «клубника» и «малина» - к классу «ягода». Классификация объектов реального мира позволяет нам делать индуктивный вывод: если для всего класса «автомобиль» характерно наличие руля и колёс, значит, купив себе новый автомобиль, мы однозначно столкнёмся с рулём и колёсами. Такой подход сокращает время на изучение объектов – нам нет необходимости изучать каждый объект в отдельности, мы изучаем класс целиком. Как и в реальном мире, в программировании объекты делятся на классы, т.е., в терминологии программиста, являются экземплярами классов.

У объекта реального мира существуют некие признаки, которые отличают его от других объектов. Если набор признаков объекта характерен для двух объектов – они относятся к одному классу. Например, у всех здоровых собак четыре ноги и один хвост. При этом, вряд ли мы можем найти двух собак, у которых длины ног и хвостов будут строго одинаковы. Таким образом, перенося ситуацию в программирование, мы можем сказать, что объекты «Дог Пират» и «Пудель Кнопка» являются экземплярами класса «Собака», т.к. они имеют одинаковые признаки принадлежности к этому классу, но значения этих признаков (длины ног, длина хвоста) у этих экземпляров разные. В программировании такие признаки, которые могут иметь значение, называются полями.

Также объекты в реальном мире имеют какое-то поведение. Например, автомобиль может ехать, а самолёт – лететь. Здесь возникает примерно та же ситуация, что и с полями: объекты одного класса имеют одно и то же название поведения, но под этим названием могут скрываться разные действия. Так, самолёты «Миг-29» и «По-2» обладают поведение «Полёт», но осуществляют полёт эти два самолёта совершенно по-разному. В ООП правила поведения объектов описываются в методах его класса, а для конкретизации поведения под конкретный объект используются параметры метода.

Важно. Для тех, кто осваивает ООП, имея опыт работы с процедурным программированием (например, на Pascal или QBasic), понять термин «Метод» будет легче, если вспомнить понятие «Функция» («Процедура»). Метод класса – это по сути функция, жёстко связанная с классом, т.е., существовать и выполняться без класса или его экземпляра она не может.

Для поиска аналогии параметру метода в реальной жизни обратимся к следующему примеру. Некто (мама, жена, друзья) просят Вас сходить в магазин и купить хлеба. На это они дают Вам деньги. В этом случае «Сходить в магазин» - это метод, хлеб – возвращаемое значение метода, а деньги – параметр, который Вам необходим для решения задачи.
При этом, параметр может быть в одном из трёх вариантов:
  1. Передача параметра по значению. В метод в качестве параметра передаётся непосредственно само значение: число или символ. Аналогично в случае похода за хлебом Вам дают непосредственно деньги в руки.
  2. Передача параметра по ссылке. В метод передаётся не сам объект значения, а ссылка на адрес в памяти, где лежит этот объект. «Деньги возьми в шкафу».
  3. Обратная передача. Смысл её в том, что не Вам передают что-то, а Вы передаёте обратно. Например, приносите из магазина чек.

В ООП существует отдельный вид методов, который называется конструктором. Конструктор предназначен для создания того экземпляра класса, к которому этот конструктор принадлежит. Конструктор работает примерно также, как обычный метод, но на него накладываются некоторые ограничения:
  1. Для конструктора не указывается возвращаемый тип, т.к. конструктор всегда возвращает экземпляр класса, к которому принадлежит;
  2. Конструктор вызывается с помощью операции создания экземпляра, т.е. не как обычный метод. В C# это оператор new;
  3. Конструктор в ряде языков (в частности в C#) называется так же, как и сам класс;

А теперь синтаксис всего этого на языке C#
Для объявления класса в C# существует ключевое слово class. Содержимое класса определяется фигурными скобками: открывающей «{» и закрывающей «}». Основная программа также представляет собой класс с именем Program и главным методом Main().
  
    public class MyClass
    {
        //Содержимое класса
    }
Для создания экземпляра класса необходимо запустить его конструктор. В следующем примере создаётся экземпляр класса и помещается в переменную типа этого же класса.
  
    class Program
    {
        static void Main(string[] args)
        {
            MyClass cl = new MyClass();
        }
    }
Для обращения к полям класса необходимо объявить эти поля и впоследствии обратиться к их значениям у конкретного экземпляра.
  
    public class Student
    {
        public String _fio;
        public Int32 _age;
    }

    class Program
    {
        static void Main(string[] args)
        {
            Student s = new Student();
            s._fio = "Иванов И.И.";
            s._age = 21;
        }
    }
Для вызова метода по аналогии с полями необходимо создать экземпляр класса и обратиться к методу по имени. По правилам языка C# после названия метода необходимо ставить пару скобок. Между скобками размещаются параметры, которые передаются в метод. Если параметров нет, скобки оставляются «пустыми». Если метод должен вернуть значение, то в его описании указывается тип этого значения. В противном случае - пишется ключевое слово void. Если в методе необходимо обратиться к параметрам того класса, где описан этот метод, можно не писать название переменной, а применить метод "напрямую". Либо использовать ключевое слово "this".
    public class Exam
    {
        /// 
        /// Метод без параметров, который не возвращает ничего
        /// 
        public void Pass() 
        {
            //Код метода
        }

        /// 
        /// Метод с параметром, возвращающий дату, которая 
        /// наступит через 2 недели
        /// 
        /// Оценка на экзамене - параметр метода
        /// Возвращаемое значение метода
        public DateTime RepeatExam(Byte mark)
        {
            return DateTime.Now.AddDays(14);
        }
    }
Ниже приведён фрагмент кода, в котором показаны все рассмотренные выше понятия: класс, поле, методы с параметрами и без, конструкторы с параметрами и без.
    /// 
    /// Треугольник
    /// 
    public class Triangle
    {
        //Стороны треугольника
        public Double _a;
        public Double _b;
        public Double _c;

        /// 
        /// Конструктор, создающий треугольник по трём переданным сторонам
        /// 
        /// Сторона 1
        /// Сторона 2
        /// сторона 3
        public Triangle(Double a, Double b, Double c)
        {
            _a = a;
            _b = b;
            _c = c;
        }

        /// 
        /// Конструктор, создающий прямоугольный треугольник
        /// 
        /// Катет 1
        /// Катет 2
        public Triangle(Double a, Double b)
        {
            _a = a;
            _b = b;
            _c = Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2));
        }

        /// 
        /// Конструктор, задающий треугольник с жёстко определёнными сторонами
        /// 
        public Triangle()
        {
            _a = 3;
            _b = 3;
            _c = 3;
        }

        /// 
        /// Расчёт площади треугольника по формуле Герона
        /// Метод не имеет параметров, но возвращает значение
        /// 
        /// Возвращает площадь треугольника
        public Double GetS()
        {
            double p = _a + _b + _c;
            return Math.Sqrt(p * (p - _a) * (p - _b) * (p - _c));
        }

        /// 
        /// Задаёт случайные стороны треугольника
        /// 
        public void SetRandomTriangle()
        {
            _a = 3;
            _b = 1;
            _c = 1;
            Random x = new Random();

            //В условии цикла while вызывается метод, который проверяет
            //возможность существования такого треугольника
            while (!this.Exists(_a, _b, _c))
            {
                _a = x.Next(10);
                _b = x.Next(10);
                _c = x.Next(10);
            }
        }

        /// 
        /// Проверка существования такого треугольника
        /// 
        /// Сторона 1
        /// Сторона 2
        /// Сторона 3
        /// Существует ли такой треугольник
        protected Boolean Exists(Double a, Double b, Double c)
        {
            return !((a > b + c) || (b > a + c) || (c > a + b));
        }
    }

    /// 
    /// Основной класс программы
    /// 
    class Program
    {
        /// 
        /// Основной метод
        /// 
        /// Возможные аргументы
        static void Main(string[] args)
        {
            //Создание трёх треугольников с применением трёх конструкторов
            Triangle t1 = new Triangle();
            t1.SetRandomTriangle();
            Triangle t2 = new Triangle(4, 5);
            Triangle t3 = new Triangle(3, 4, 5);

            //Расчёт площадей треугольников (через вызов их методов)
            Console.WriteLine("Площадь треугольника 1: {0}", t1.GetS());
            Console.WriteLine("Площадь треугольника 2: {0}", t2.GetS());
            Console.WriteLine("Площадь треугольника 3: {0}", t3.GetS());

            Console.ReadLine();
        }
    }

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

Важно. Обратите внимание на ключевое слово «public», которое предшествует практически всем объявлениям в приведённых фрагментах кода. Это модификатор доступа. Объяснение этого термина будет чуть позже.