Блог о программировании

Что такое ООП на примерах. Для чайников

 10 апреля 2016 г. 14:50

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

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

ООП (или объектно-ориентированное программирование) представляет собой способ организации кода программы, когда основными строительными блоками программы являются объекты и классы, а логика работы программы построена на их взаимодействии.


Об объектах и классах

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

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

Одним из самых распространенных объектно-ориентированных языков программирования является язык java. Там без использования объектов просто не обойтись. Вот как будет выглядеть код класса, описывающего треугольник на этом языке:

/**
 * Класс Треугольник.
 */
class Triangle {

    /**
     * Специальный метод, называемый конструктор класса. 
     * Принимает на вход три параметра:
     * длина стороны А, длина стороны Б,
     * угол между этими сторонами(в градусах)
     */
    Triangle(double sideA, double sideB, double angleAB) {
        this.sideA = sideA;
        this.sideB = sideB;
        this.angleAB = angleAB;
    }

    double sideA; //Поле класса, хранит значение стороны А в описываемом треугольнике
    double sideB; //Поле класса, хранит значение стороны Б в описываемом треугольнике
    double angleAB; //Поле класса, хранит угла(в градусах) между двумя сторонами в описываемом треугольнике

    /**
     * Метод класса, который рассчитывает площадь треугольника
     */
    double getSquare() {
        double square = this.sideA * this.sideB * Math.sin(this.angleAB * Math.PI / 180);
        return square;
    }

    /**
     * Метод класса, который рассчитывает периметр треугольника
     */
    double getPerimeter() {
        double sideC = Math.sqrt(Math.pow(this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos(this.angleAB * Math.PI / 180));
        double perimeter = this.sideA + this.sideB + sideC;
        return perimeter;
    }
}

Если мы внутрь класса добавим следующий код:

/**
 * Именно в этом месте запускается программа
 */
public static void main(String[] args) {
	//Значения 5, 17, 35 попадают в конструктор класса Triangle
	Triangle triangle1 = new Triangle(5, 17, 35);
	System.out.println("Площадь треугольника1: "+triangle1.getSquare());
	System.out.println("Периметр треугольника1: "+triangle1.getPerimeter());

	//Значения 6, 8, 60 попадают в конструктор класса Triangle
	Triangle triangle2 = new Triangle(6, 8, 60);
	System.out.println("Площадь треугольника1: "+triangle2.getSquare());
	System.out.println("Периметр треугольника1: "+triangle2.getPerimeter());
}

то программу уже можно будет запускать на выполнение. Это особенность языка java. Если в классе есть такой метод

public static void main(String[] args)

то этот класс можно выполнять. Разберем код подробнее. Начнем со строки

Triangle triangle1 = new Triangle(5, 17, 35);

Здесь мы создаем экземпляр triangle1 класса Triangle и тут же задаем ему параметры сторон и угла между ними. При этом, вызывается специальный метод, называемый конструктор и заполняет поля объекта переданными значениями в конструктор. Ну, а строки

System.out.println("Площадь треугольника1: "+triangle1.getSquare());
System.out.println("Периметр треугольника1: "+triangle1.getPerimeter());

выводят рассчитанные площадь треугольника и его периметр в консоль.

Аналогично все происходит и для второго экземпляра класса Triangle.

Понимание сути классов и конструирования конкретных объектов - это уверенный первый шаг к пониманию методологии ООП.

Еще раз, самое важное:

ООП - это способ организации кода программы;

Класс - это пользовательская структура данных, которая воедино объединяет данные и функции для работы с ними(поля класса и методы класса);

Объект - это конкретный экземпляр класса, полям которого заданы конкретные значения.


Три волшебных слова

ООП включает три ключевых подхода: наследование, инкапсуляцию и полиморфизм. Для начала, приведу определения из wikipedia:

Инкапсуляция — свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе. Некоторые языки (например, С++) отождествляют инкапсуляцию с сокрытием, но большинство (Smalltalk, Eiffel, OCaml) различают эти понятия.

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

Полиморфизм — свойство системы, позволяющее использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

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


Итак, точка — это самая малая геометрическая фигура, которая является основой всех прочих построений (фигур). Поэтому именно точка выбрана в качестве базового родительского класса. Напишем класс точки на java:

/**
 * Класс точки. Базовый класс
 */
class Point {

    /**
     * Пустой конструктор
     */
    Point() {}

    /**
     * Метод класса, который рассчитывает площадь фигуры
     */
    double getSquare() {
        return 0;
    }

    /**
     * Метод класса, который рассчитывает периметр фигуры
     */
    double getPerimeter() {
        return 0;
    }

    /**
     * Метод класса, возвращающий описание фигуры
     */
    String getDescription() {
        return "Точка";
    }
}

У получившегося класса Point пустой конструктор, поскольку в данном примере мы работаем без конкретных координат, а оперируем только параметрами значениями сторон. Так как у точки нет никаких сторон, то и передавать ей никаких параметров не надо. Также заметим, что класс имеет методы Point::getSquare() и Point::getPerimeter() для расчета площади и периметра, оба возвращают 0. Для точки оно и логично.


Поскольку у нас точка является основой всех прочих фигур, то и классы этих прочих фигур мы наследуем от класса Point. Опишем класс отрезка, наследуемого от класса точки:

/**
 * Класс Отрезок
 */
class LineSegment extends Point {

    LineSegment(double segmentLength) {
        this.segmentLength = segmentLength;
    }

    double segmentLength; // Длина отрезка

    /**
     * Переопределенный метод класса, который рассчитывает площадь отрезка
     */
    double getSquare() {
        return 0;
    }

    /**
     * Переопределенный метод класса, который рассчитывает периметр отрезка
     */
    double getPerimeter() {
        return this.segmentLength;
    }

    String getDescription() {
        return "Отрезок длиной: " + this.segmentLength;
    }
}

Запись

class LineSegment extends Point

означает, что класс LineSegment наследуется от класса Point. Методы LineSegment::getSquare() и LineSegment::getPerimeter() переопределяют соответствующие методы базового класса. Площадь отрезка всегда равняется нулю, а площадь периметра равняется длине этого отрезка.

Теперь, подобно классу отрезка, опишем класс треугольника(который также наследуется от класса точки):

/**
 * Класс Треугольник.
 */
class Triangle extends Point {

    /**
     * Конструктор класса. Принимает на вход три параметра:
     * длина стороны А, длина стороны Б,
     * угол между этими сторонами(в градусах)
     */
    Triangle(double sideA, double sideB, double angleAB) {
        this.sideA = sideA;
        this.sideB = sideB;
        this.angleAB = angleAB;
    }

    double sideA; //Поле класса, хранит значение стороны А в описываемом треугольнике
    double sideB; //Поле класса, хранит значение стороны Б в описываемом треугольнике
    double angleAB; //Поле класса, хранит угла(в градусах) между двумя сторонами в описываемом треугольнике

    /**
     * Метод класса, который рассчитывает площадь треугольника
     */
    double getSquare() {
        double square = (this.sideA * this.sideB * Math.sin(this.angleAB * Math.PI / 180))/2;
        return square;
    }

    /**
     * Метод класса, который рассчитывает периметр треугольника
     */
    double getPerimeter() {
        double sideC = Math.sqrt(Math.pow(this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos(this.angleAB * Math.PI / 180));
        double perimeter = this.sideA + this.sideB + sideC;
        return perimeter;
    }

    String getDescription() {
        return "Треугольник со сторонами: " + this.sideA + ", " + this.sideB + " и углом между ними: " + this.angleAB;
    }
}

Тут нет ничего нового. Также, методы Triangle::getSquare() и Triangle::getPerimeter() переопределяют соответствующие методы базового класса.
Ну а теперь, собственно, тот самый код, который показывает волшебство полиморифзма и раскрывает мощь ООП:

class Main {
    /**
     * Именно в этом месте запускается программа
     */
    public static void main(String[] args) {
        //ArrayList - Это специальная структура данных в java,
        // позволяющая хранить объекты определенного типа в массиве.
        ArrayList figures = new ArrayList();

        //добавляем три разных объекта в массив figures
        figures.add(new Point());
        figures.add(new LineSegment(133));
        figures.add(new Triangle(10, 17, 55));

        for (int i = 0; i < figures.size(); i++) {
            //для каждого объекта, который находится в массиве
            //выводим его описание, периметр и площадь
            Point figure = figures.get(i);
            System.out.println(figure.getDescription());
            System.out.println("Периметр фигуры: "+figure.getPerimeter());
            System.out.println("Площадь фигуры: "+figure.getSquare());
        }
    }
}

Мы создали массив объектов класса Point, а поскольку классы LineSegment и Triangle наследуются от класса Point, то и их мы можем помещать в этот массив. Получается, каждую фигуру, которая есть в массиве figures мы можем рассматривать как объект класса Point. В этом и заключается полиморфизм: неизвестно, к какому именно классу принадлежат находящиеся в массиве figures объекты, но поскольку все объекты внутри этого массива принадлежат одному базовому классу Point, то все методы, которые применимы к классу Point также и применимы к его классам-наследникам.


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


Полный код примера:

import java.util.ArrayList;

class Main {
    /**
     * Именно в этом месте запускается программа
     */
    public static void main(String[] args) {
        //ArrayList - Это специальная структура данных в java,
        // позволяющая хранить объекты определенного типа в массиве.
        ArrayList figures = new ArrayList();

        //добавляем три разных объекта в массив figures
        figures.add(new Point());
        figures.add(new LineSegment(133));
        figures.add(new Triangle(10, 17, 55));

        for (int i = 0; i < figures.size(); i++) {
            //для каждого объекта, который находится в массиве
            //выводим его описание, периметр и площадь
            Point figure = figures.get(i);
            System.out.println(figure.getDescription());
            System.out.println("Периметр фигуры: "+figure.getPerimeter());
            System.out.println("Площадь фигуры: "+figure.getSquare());
        }
    }
}

/**
 * Класс точки. Базовый класс
 */
class Point {

    /**
     * Пустой конструктор
     */
    Point() {}

    /**
     * Метод класса, который рассчитывает площадь фигуры
     */
    double getSquare() {
        return 0;
    }

    /**
     * Метод класса, который рассчитывает периметр фигуры
     */
    double getPerimeter() {
        return 0;
    }

    /**
     * Метод класса, возвращающий описание фигуры
     */
    String getDescription() {
        return "Точка";
    }
}

/**
 * Класс Отрезок
 */
class LineSegment extends Point {

    LineSegment(double segmentLength) {
        this.segmentLength = segmentLength;
    }

    double segmentLength; // Длина отрезка

    /**
     * Переопределенный метод класса, который рассчитывает площадь отрезка
     */
    double getSquare() {
        return 0;
    }

    /**
     * Переопределенный метод класса, который рассчитывает периметр отрезка
     */
    double getPerimeter() {
        return this.segmentLength;
    }

    String getDescription() {
        return "Отрезок длиной: " + this.segmentLength;
    }
}


/**
 * Класс Треугольник.
 */
class Triangle extends Point {

    /**
     * Конструктор класса. Принимает на вход три параметра:
     * длина стороны А, длина стороны Б,
     * угол между этими сторонами(в градусах)
     */
    Triangle(double sideA, double sideB, double angleAB) {
        this.sideA = sideA;
        this.sideB = sideB;
        this.angleAB = angleAB;
    }

    double sideA; //Поле класса, хранит значение стороны А в описываемом треугольнике
    double sideB; //Поле класса, хранит значение стороны Б в описываемом треугольнике
    double angleAB; //Поле класса, хранит угла(в градусах) между двумя сторонами в описываемом треугольнике

    /**
     * Метод класса, который рассчитывает площадь треугольника
     */
    double getSquare() {
        double square = (this.sideA * this.sideB * Math.sin(this.angleAB * Math.PI / 180))/2;
        return square;
    }

    /**
     * Метод класса, который рассчитывает периметр треугольника
     */
    double getPerimeter() {
        double sideC = Math.sqrt(Math.pow(this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos(this.angleAB * Math.PI / 180));
        double perimeter = this.sideA + this.sideB + sideC;
        return perimeter;
    }

    String getDescription() {
        return "Треугольник со сторонами: " + this.sideA + ", " + this.sideB + " и углом между ними: " + this.angleAB;
    }
}
Теги:  ООП  java 

Поделиться статьей

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