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