пятница, 10 июля 2015 г.

Агрегация и композиция

Агрегация

Иногда в рамках одного класса нам необходимо создать один или несколько экземпляров другого класса (других классов). Например, если мы проектируем класс «Дом», то внутри экземпляра этого класса вполне могут существовать экземпляры классов «Стена», «Окно», «Дверь». Для реализации такой конструкции используется понятие «Агрегация». Т.е., агрегация – это вхождение экземпляра/ов одного класса в экземпляры другого класса в качестве полей.
    /// 
    /// Стена
    /// 
    public class Wall { }

    /// 
    /// Окно
    /// 
    public class Window { }

    /// 
    /// Дверь
    /// 
    public class Door { }

    /// 
    /// Дом
    /// 
    public class House
    {
        protected Wall _wall1;
        protected Wall _wall2;
        protected Wall _wall3;
        protected Wall _wall4;
        protected Window _window;
        protected Door _door;
    }

Композиция

Композиция – это более сложный случай агрегации. Отличие композиции от агрегации в том, что в случае композиции класс-контейнер не может обойтись (не имеет смысла) без экземпляров класса-содержимого. В описанном выше примере дом не может существовать без стен. Это композиция. Но, дом спокойно может обойтись без печной трубы (например, если в нём электрическое отопление) – это агрегация.

Агрегация массивов

Поля класса контейнера могут агрегировать не только одиночные экземпляры другого класса, но и массивы (любые коллекции, но об этом позже) этих экземпляров. Для этого надо создать поле типа «массив» и использовать его в дальнейшем.
    /// 
    /// Дом
    /// 
    public class House
    {
        /// 
        /// Массив стен дома
        /// 
        protected Wall[] _walls;

        /// 
        /// Массив окон дома
        /// 
        protected Window _windows;
        
        /// 
        /// А дверь в нарушение пожарной безопасности - одна
        /// 
        protected Door _door;
    }

Использование конструкторов

Если в классе-контейнере мы используем экземпляр другого класса, то перед использованием этот экземпляр надо создать. Для этого мы используем конструктор класса-содержимого. Если экземпляр класса-содержимого не создавать, то при попытке его использовать мы получим Exception.
        /// 
        /// Конструктор - инициализирует агрегированные поля класса
        /// 
        public House()
        {
            _walls = new Wall[4]
            {
                new Wall(), new Wall(), new Wall(), new Wall()
            };
            _windows = new Window[4] { new Window(), new Window(), new Window(), new Window() };
            _door = new Door();
        }

Агрегация или наследование?

В некоторых случаях рекомендуется использовать агрегацию вместо наследования для расширения функциональности класса. Так, скажем, если в классе А нам надо реализовать функциональность классов B и C, то нам будет затруднительно выбрать класс-предок для А. Логичным решением будет создание в классе А полей типа В и С. Впрочем, правильный подход к решению подобных задач мы рассмотрим позже.
    /// 
    /// Класс камеры
    /// 
    public class Camera
    {
        /// 
        /// Сфотографировать
        /// 
        public void Photo() { }
    }

    /// 
    /// Класс телефона
    /// 
    public class Phone
    {
        /// 
        /// Позвонить
        /// 
        public void Call() { }
    }


    /// 
    /// Смартфон
    /// 
    public class Smartphone
    {
        /// 
        /// Встроенная камера
        /// 
        protected Camera _camera;
        /// 
        /// Встроенный телефон
        /// 
        protected Phone _phone;

        /// 
        /// Конструктор
        /// 
        public Smartphone()
        {
            //Инициализация агрегированных полей 
            _camera = new Camera();
            _phone = new Phone();
        }

        /// 
        /// Сфотографировать
        /// 
        public void Photo() { _camera.Photo(); }

        /// 
        /// Позвонить
        /// 
        public void Call() { _phone.Call(); }
    }

Заключение


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

Полиморфизм

Понятие полиморфизма

Под понятием «полиморфизм» в объектно-ориентированном программировании понимают возможность методов с одинаковым названием иметь разную реализацию (т.е. разные алгоритмы работы). Применение полиморфизма позволяет с одной стороны сделать интуитивно понятным интерфейс класса, а с другой – даёт возможность наделять класс-наследник отличной от класса-предка функциональностью.

Различают полиморфизм двух видов: внутри одного и того же класса (перегрузка методов класса) и между классом предком и классом наследником (переопределение и сокрытие методов). В данном посте мы рассмотрим различные возможные варианты полиморфизма методов и способы его применения.

Перегрузка методов

Перегрузка методов технически реализует полиморфизм методов внутри класса. Методы, участвующие в перегрузке, должны иметь одинаковое название, но разную сигнатуру (т.е. разные списки параметров и/или типы возвращаемых значений). Именно по сигнатуре метода компилятор будет определять, какой из перегруженных методов использовать при вызове.

Легче всего понять принцип работы перегрузки методов на примере. Рассмотрим следующую ситуацию. Нам необходимо создать класс, который будет реализовывать регистрацию юридического лица. Юридические лица, рассматриваемые в системе, будут двух типов: организация и индивидуальный предприниматель. Для ООО параметрами метода будут: название организации, ФИО руководителя, ИНН, КПП, юридический адрес. У ИП остаётся ИНН, но отсутствует такой параметр как «КПП», отсутствует название организации, вместо ФИО руководителя – ФИО самого ИП, вместо юридического адреса – адрес прописки.

Названия параметров для перегрузки роли не играют. Имеют значение лишь количество и типы параметров. Поэтому, у нас получится два метода:
1.      Registry (String, String, String, String, String);
2.      Registry (String, String, String);
Т.е., в нашем случае методы различаются количеством параметров, но может быть различие как в их типах (даже при равном количестве), так и в типе возвращаемых значений.

    /// 
    /// Контрагент
    /// 
    public class Contractor
    {
        /// 
        /// Регистрация юрлица
        /// 
        /// Название
        /// ФИО директора
        /// ИНН
        /// КПП
        /// Юридический адрес
        public void Registry (String name, String director, String inn, String kpp, String address)
        {
        }

        /// 
        /// Регистрация индивидуального предпринимателя
        /// 
        /// ФИО предпринимателя
        /// ИНН
        /// Адрес прописки
        public void Registry(String fio, String inn, String address)
        { 
        }
    }

Перегружать можно любые методы, в том числе и конструкторы. Это бывает удобно, если экземпляр класса может создаваться в нескольких вариантах. Рассмотрим следующий пример. В организации создаётся новый сотрудник (экземпляр класса Employee). В зависимости от должности, от него требуется предоставление разных параметров (например, для повара необходима медкнижка, а для охранника – лицензия на охранную деятельность).
    /// 
    /// Сотрудник
    /// 
    public class Employee 
    {
        /// 
        /// Найм повара
        /// 
        /// ФИО
        /// № медкнижки
        /// Разряд
        public Employee(String fio, String medDataNum, Int32 rank)
        { 
        }
        /// 
        /// Найм охранника
        /// 
        /// ФИО
        /// № лицензии
        public Employee(String fio, String license)
        { 
        }
    }

Полиморфизм в наследовании

В отношении наследования между классами полиморфизм используется для переопределения функционала класса-предка в классе-наследнике. Здесь требования к методам диаметрально противоположные: базовый и переопределяемый методы должны иметь строго одинаковую сигнатуру. Кроме того, метод базового класса должен быть помечен как виртуальный (virtual), а в классе-наследнике он должен быть либо переопределён (override), либо скрыт (new). Если в классе-наследнике метод не описан вообще, то вызывается метод базового класса.
Рассмотрим следующий пример. Существует класс «Прямоугольник», в котором площадь прямоугольника рассчитывается как произведение двух смежных сторон. Его наследником является класс «Прямоугольный треугольник», площадь которого рассчитывается как половина площади прямоугольника, образованного катетами треугольника. Метод расчёта площади переопределён в классе-наследнике с помощью конструкции override. Для того, чтобы не писать код расчёта площади прямоугольника заново, обратимся из переопределённого метода к базовому методу (методу базового класса) с помощью оператора base.
    /// 
    /// Прямоугольник
    /// 
    public class Rectangle
    {
        /// 
        /// Расчёт площади
        /// 
        /// Сторона 1
        /// Сторона 2
        /// Площадь прямоугольника
        public virtual Double GetArea(Double a, Double b)
        {
            return a * b;
        }
    }

    /// 
    /// Прямоугольный треугольник
    /// 
    public class RectTriangle : Rectangle
    {
        /// 
        /// Расчёт площади
        /// 
        /// Катет 1
        /// Катет 2
        /// Площадь прямоугольного треугольника
        public override double GetArea(double a, double b)
        {
            return base.GetArea(a, b) / 2;
        }
    }
В следующем примере мы используем в качестве базового класса «Произвольный треугольник», площадь которого рассчитывается по формуле Герона. Класс «Прямоугольный треугольник», являющийся наследником произвольного треугольника, использует для нахождения площади половину произведения катетов, т.е. никак не использует функциональность базового класса. В этом случае будет уместно скрыть базовый функционал с помощью модификатора new.
    /// 
    /// Произвольный треугольник
    /// 
    public class Triangle
    {
        /// 
        /// Сторона А
        /// 
        protected Double _a;

        /// 
        /// Сторона В
        /// 
        protected Double _b;

        /// 
        /// Сторона С
        /// 
        protected Double _c;

        /// 
        /// Расчёт площади по формуле Герона
        /// 
        /// 
        public virtual Double GetArea()
        {
            Double p = (_a + _b + _c) / 2;
            return Math.Sqrt(p * (p - _a) * (p - _b) * (p - _c));
        }
    }

    /// 
    /// Прямоугольный треугольник
    /// 
    public class RectTriangle : Triangle
    {
        /// 
        /// Расчёт площади при условии, что а и б - катеты
        /// 
        /// Площадь прямоугольного треугольника
        public new double GetArea()
        {
            return _a * _b / 2;
        }
    }
Различие между сокрытием и переопределением будет показано в следующем примере. Используем в качестве базового класса «Произвольный треугольник», а в качестве наследников «Прямоугольный треугольник» и «Равносторонний треугольник». Площадь произвольного треугольника будет рассчитываться по формуле Герона, площадь прямоугольного треугольника – через половину произведения катетов, а площадь равностороннего треугольника – как половина произведения стороны на высоту.

Метод нахождения площади в прямоугольном треугольнике мы скрываем, а в равностороннем – переопределяем. Также, мы переопределяем конструкторы в классах-наследниках, потому что для прямоугольного треугольника необязательно указывать гипотенузу, а для равностороннего треугольника достаточно указать одну сторону.
    /// 
    /// Произвольный треугольник
    /// 
    public class Triangle
    {
        /// 
        /// Сторона А
        /// 
        protected Double _a;

        /// 
        /// Сторона В
        /// 
        protected Double _b;

        /// 
        /// Сторона С
        /// 
        protected Double _c;

        /// 
        /// Конструктор
        /// 
        /// Сторона 1
        /// Сторона 2
        /// Сторона 3
        public Triangle(Double a, Double b, Double c)
        {
            _a = a;
            _b = b;
            _c = c;
        }

        /// 
        /// Расчёт площади по формуле Герона
        /// 
        /// 
        public virtual Double GetArea()
        {
            Double p = (_a + _b + _c) / 2;
            return Math.Sqrt(p * (p - _a) * (p - _b) * (p - _c));
        }
    }

    /// 
    /// Прямоугольный треугольник
    /// 
    public class RectTriangle : Triangle
    {
        /// 
        /// Конструктор
        /// 
        /// Катет 1
        /// Катет 2        
        public RectTriangle(Double a, Double b) : base(a, b, Math.Sqrt(a * a + b * b)) { }

        /// 
        /// Расчёт площади при условии, что а и б - катеты
        /// 
        /// Площадь прямоугольного треугольника
        public new double GetArea()
        {
            return _a * _b / 2;
        }
    }

    /// 
    /// Равносторонний треугольник
    /// 
    public class SameSizesTriangle : Triangle
    {
        /// 
        /// Конструктор
        /// 
        /// Сторона треугольника
        public SameSizesTriangle(Double a) : base(a, a, a) { }

        /// 
        /// Расчёт площади треугольника
        /// 
        /// Площадь равностороннего треугольника
        public override double GetArea()
        {
            Double h = Math.Sqrt(3) / 2 * _a;
            return h * _a / 2;
        }
    }
Экземпляры классов треугольников мы можем создавать как «напрямую», так и используя наследование. Именно при использовании наследования становится очевидна разница между переопределением и сокрытием методов. Если обратиться к экземпляру класса-наследника как к экземпляру класса-предка, то переопределённый метод сработает сам по себе (т.е. сработает метод из класса-наследника), а вместо сокрытого метода будет работать метод базового класса. Это принципиальное различие, которое помогает строить иерархию наследования классов.
    class Program
    {
        static void Main(string[] args)
        {
            //Создание обычного треугольника
            Triangle t1 = new Triangle(4, 4, 5);
            //Создание прямоугольного треугольника
            Triangle t2 = new RectTriangle(4, 4);
            //Создание равностороннего треугольника
            Triangle t3 = new SameSizesTriangle(4);

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

            Console.ReadLine();
        }
    }
Полный код примера, иллюстрирующего использование сокрытия и переопределения описан ниже. В нём мы задаём массив экземпляров «Произвольный треугольник», но заполняем его всеми видами треугольников. Затем в цикле перебираем массив и выводим площадь треугольника в консоль.
    class Program
    {
        static void Main(string[] args)
        {
            //Создаём массив треугольников и заполняем его
            Triangle[] triangles = new Triangle[3]
            {
                new Triangle(4,4,5),
                new RectTriangle(4,4),
                new SameSizesTriangle(4)
            };

            //Циклом перебираем элементы массива и выводим площади в консоль
            for (Int32 i = 0; i < 3; i++)
            {
                Console.WriteLine(triangles[i].GetArea());
            }
            Console.ReadLine();
        }
    }

Заключение


Полиморфизм является мощным средством объектно-ориентированного программирования, которое позволяет одноимённым методам сопоставлять разную реализацию. Полиморфизм внутри класса (перегрузка методов) и полиморфизм в наследовании (переопределение и сокрытие методов) позволяет получать гибкую объектно-ориентированную структуру, с помощью которой можно эффективно решать задачи предметной области.

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

Заключение


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

пятница, 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», которое предшествует практически всем объявлениям в приведённых фрагментах кода. Это модификатор доступа. Объяснение этого термина будет чуть позже.