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

ООП расшифровывается как объектно-ориентированное программирование. Давайте разбираться, что это такое и зачем это нужно.

Что такое ООП

В ООП мы будем иметь дело с объектами, как в реальной жизни. Выгляните в окно. Наверняка там на стоянке стоят машины. Каждая машина - это отдельный объект. Мы можем сказать, что каждая машина является представителем группы Автомобили.

То есть: каждая отдельная машина - это объект, и все эти машины принадлежат какой-то группе объектов. Эта группа называется класс. По сути класс - это чертеж, по которому сделана каждая отдельная машина.

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

Каждая машина также может реагировать на какие-то команды. К примеру, мы можем сказать: машина - едь, или: машина - поверни. Команды, на которые может реагировать объект, называются методами объекта.

Давайте начнем

Давайте разберем ООП в JavaScript на примере игры, в которой по полю ездят танчики и стреляют друг в друга. При попадании в танчик у него уменьшается количество брони, пока он не взорвется.

Данный пример очень удобен, потому что каждый танчик по сути - это объект. У этого объекта есть методы (едь, стой, стрельни) и свойства (количество снарядов, количество брони, местоположение танчика в пространстве).

Давайте уже приступим к написанию кода, на примере которого вы поймете преимущества ООП.

Классы и объекты

Вы уже знаете, что в ООП существует понятие класса и понятие объекта. Давайте еще раз разберем это понятие на примере фабрики, выпускающей танчики. У этой фабрики есть чертежи, по которым делается каждый танк. Вот эти чертежи и есть класс, а каждый отдельный танк, сделанный по этим чертежам - это объект.

То есть объект - это представитель конкретного класса.

Давайте напишем класс Tank, у которого будет 2 метода: moveTo (переместисьВ) и fireTo (стельниВ). Реализацию методов пока не пишем, это просто заготовки:

class Tank {
	moveTo(x, y) {
	
	}

	fireTo(x, y) {
	
	}
}

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

Пока у нас написан только класс, то есть по сути мы сделали чертеж танка, но самого танка у нас нет. Сделаем его. Новый объект создается с помощью команды new, написанной перед названием класса:

var tank = new Tank; //в переменной tank теперь объект класса Tank

Можно указать пустые круглые скобки: не new Tank, а new Tank() - разницы не будет:

var tank = new Tank();

Давайте теперь создадим новый танк и вызовем метод moveTo, чтобы танк переместился в нужную точку:

var tank = new Tank;
tank.moveTo(10, 20); //командуем перемещение

Давайте сделаем два танка и каждый из них переместим в заданную точку:

var tank1 = new Tank;
tank1.moveTo(10, 10);

var tank2 = new Tank;
tank2.moveTo(20, 20);

Ну а теперь повоюем - пусть второй танк стрельнет в точку нахождения первого:

var tank1 = new Tank; //создаем первый танк
tank1.moveTo(10, 10); //командуем перемещение в точку 10, 10

var tank2 = new Tank; //создаем второй танк
tank2.fireTo(10, 10); //командуем выстрел в местонахождение первого

Свойства объекта

Как вы уже знаете, кроме методов объектов, есть еще и свойства. Пусть у каждого созданного танка будет еще и свойство ammunition. В нем мы будем хранить количество снарядов танка.

Давайте создадим новый танк и в момент создания положим в него 10 снарядов:

var tank = new Tank;
tank.ammunition = 10;

При создании танка мы следующей строчкой на лету создали ему свойство ammunition. Теперь это свойство доступно внутри любого метода класса вот так: this.ammunition:

class Tank {
	fireTo() {
		//тут доступны снаряды через this.ammunition
	}
}

В данном случае this указывает на объект нашего класса. То есть: если мы вызываем метод fireTo для первого танчика, то this будет указывать на него, а если для второго танчика - то на него.

Давайте при каждом выстреле будем уменьшать количество снарядов на один:

class Tank {
	fireTo() {
		this.ammunition = this.ammunition - 1; //уменьшаем количество снарядов
	}
}

Метод в методе

Иногда нам может потребоваться вызывать метод одного класса внутри другого. К примеру, мы хотим сделать метод moveAndFire, который будет перемещать танк и одновременно делать выстрел. Но у нас уже есть методы moveTo и fireTo - используем их внутри нового метода, чтобы избежать дублирования кода.

Для этого также, как и при работе со свойствами, к методам следует обратиться через this, вот так: this.moveTo() и this.fireTo:

class Tank {
	moveTo(x, y) {
	
	}

	fireTo(x, y) {
	
	}

	moveAndFire(moveX, moveY, fireX, fireY) {
		this.moveTo(moveX, moveY);
		this.fireTo(fireX, fireY);
	}
}

Конструктор

Помните, как мы задавали количество снарядов в танке при создании:

var tank = new Tank;
tank.ammunition = 10; //количество снарядов

На самом деле это не очень удобно, ведь эту строчку легко забыть написать - и танк будет безоружен. Лучше было бы, чтобы танк уже в момент создания был обеспечен нужным количеством снарядов.

Исправить это проблему нам поможет метод-конструктор. Этот метод имеет стандартное название constructor и вызывается автоматически в момент создания объекта (когда мы пишем new).

Давайте создадим танк и в момент создания выведем что-нибудь алертом на экран:

class Tank {
	constructor() {
		alert('создание');
	}
}

var tank = new Tank; //выведет 'создание'

Так же, как и в остальные методы, в конструктор можно передавать параметры. Давайте передадим в него параметр message и выведем его содержимое алертом на экран:

class Tank {
	constructor(message) {
		alert(message+'!');
	}
}

var tank = new Tank('создание'); //выведет 'создание!'

Давайте теперь исправим описанное выше неудобство - пусть количество снарядов задается прямо в конструкторе:

class Tank {
	constructor() {
		this.ammunition = 10; //положим 10 снарядов в момент создания
	}

	fireTo(x, y) {
		this.ammunition = 	this.ammunition - 1;
	}
}

Теперь каждый созданный танк будет иметь 10 снарядов уже в момент создания. Убедимся в этом:

var tank = new Tank;
alert(tank.ammunition); //выведет 10

А еще лучше давайте сделаем параметр, в который будем передавать количество снарядов в момент создания танка, вот так:

var tank = new Tank(10); //создаем танк с 10-ю снарядами
alert(tank.ammunition); //выведет '10'

Для этого в конструкторе напишем параметр ammunition, вот так: constructor(ammunition). Этот ammunition будет содержать в себе десятку, которую мы передаем в момент создания.

При этом переменная ammunition будет локальной переменной внутри конструктора. В данном случае нам это не нужно - мы хотели бы, чтобы количество снарядов было доступно во всех других методах (вот так: this.ammunition), а также снаружи объекта (вот так: tank.ammunition).

Для этого давайте запишем содержимое переменной ammunition в свойство ammunition, вот так: this.ammunition = ammunition.

Реализуем указанное:

class Tank {
	constructor(ammunition) {
		this.ammunition = ammunition; //запишем 10-тку в свойство объекта
	}

	fireTo(x, y) {
		this.ammunition = this.ammunition - 1;
	}
}

Вспомогательные методы

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

Вот этот код:

class Tank {
	constructor(ammunition) {
		this.ammunition = ammunition;
	}

	fireTo(x, y) {
		this.ammunition = this.ammunition - 1;
	}
}

var tank = new Tank(10); //создаем танк
tank.fireTo(10, 20); //командуем выстрел

Если мы немного постреляем - с каждым выстрелом количество снарядов будет уменьшаться:

var tank = new Tank(10);
alert(tank.ammunition); //выведет '10'

tank.fireTo(10, 20); //скомандуем выстрел
alert(tank.ammunition); //выведет '9'

tank.fireTo(10, 20); //скомандуем выстрел
alert(tank.ammunition); //выведет '8'

Есть проблема: если выстрелить более 10 раз, то танк не перестанет стрелять - он будет выводить 0, -1, -2 и так далее. Все потому, что мы не запрещаем выстрел, если количество снарядов достигло нуля.

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

class Tank {
	constructor(ammunition) {
		this.ammunition = ammunition;
	}

	fireTo() {
		//Перед выстрелом проверяем снаряды:
		if (this.canFire()) {
			this.ammunition = this.ammunition - 1;
		}
	}

	//Вспомогательный метод для проверки снарядов:
	canFire(ammunition) {
		if (ammunition > 0) {
			return true;
		} else {
			return false;
		}
	}
}

Приватные методы

Созданный нами метод canFire() имеет один недостаток - его можно вызвать снаружи класса, хотя нам эта возможность в общем-то не нужна. Более того - такая возможность опасна: вы можете случайно вызвать его снаружи, а потом ваш напарник по проекту с уверенностью, что этот метод не используется нигде снаружи, поредактирует его - и скрипт перестанет работать.

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

Такой подход называется инкапсуляция. Говорят, что мы инкапсулируем вспомогательные методы и свойства внутри класса.

Как же этого добиться?

в других языках программирования перед такими методами и свойствами указывается команда private в знак того, что это приватные (то есть недоступные извне) свойства.

В современном JavaScript такой команды нет, но она возможно появится в дальнейшем. В этом случае поступают так: всем методам и свойствам, который не должны быть видны снаружи, в начале названия пишется подчеркивание (знак _).

Это просто соглашение - всё по-прежнему видно снаружи, но мы просто договорились сами с собой о следующем: всё, что начинается с подчеркивания - снаружи мы не используем. Можем, но не будем.

Давайте переименуем метод canFire в _canFire:

class Tank {
	constructor() {
		this.ammunition = 10;

	}

	fireTo() {
		if (this._canFire()) {
			this.ammunition = 	this.ammunition - 1;
		}
	}

	_canFire(ammunition) {
		if (ammunition > 0) {
			return true;
		} else {
			return false;
		}
	}
}

Ничего не понятно толком?

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