Сравнение: классы против функций
Объектно-ориентированное программирование: на пальцах Что такое классы в объектно-ориентированном программировании Объясняем объекты
Сравнение: классы против функций

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

? Если вы пише­те про­стые про­грам­мы для себя, все эти вещи вам не нуж­ны. Но если хоти­те стать про­фес­си­о­на­лом — доб­ро пожа­ло­вать. Раз­го­вор будет слож­ным, но полез­ным.

Вспоминаем основные понятия

  • Есть про­грам­ма — это после­до­ва­тель­ность зада­ний, кото­рые мы даём ком­пью­те­ру.
  • Сами зада­ния про­стые, но из про­стых зада­ний мож­но сде­лать более слож­ные. Если такое более слож­ное зада­ние как-то назвать, мож­но ска­зать, что это функ­ция — мини-программа внут­ри про­грам­мы.
  • В функ­цию мож­но упа­ко­вать любые после­до­ва­тель­но­сти дей­ствий, кото­рые вам часто нуж­ны в про­грам­ме: напри­мер, сохра­нить файл осо­бым обра­зом, выве­сти что-то на осо­бый экран или отпра­вить элек­трон­ное пись­мо на осо­бый адрес.
  • Внут­ри одних функ­ций мож­но вызы­вать дру­гие. Так мож­но делать всё более и более слож­ные функ­ции. Напри­мер, есть функ­ция «сохра­нить поль­зо­ва­те­ля в базу дан­ных». Её мож­но вызвать изнут­ри функ­ции «Заре­ги­стри­ро­вать поль­зо­ва­те­ля». А уже эту функ­цию мож­но вызвать изнут­ри функ­ции «Обра­бо­тать логин и пароль».
  • Про­бле­ма: когда мно­го функ­ций вызы­ва­ют­ся друг из дру­га, они пере­пле­та­ют­ся и созда­ют мно­го зави­си­мо­стей. Если изме­нить рабо­ту одной из функ­ций, могут посы­пать­ся все свя­зан­ные.
  • Реше­ние: стан­дар­ти­зи­ро­вать под­хо­ды к рабо­те функ­ций. В том чис­ле для это­го при­ду­ма­ли объектно-ориентированное про­грам­ми­ро­ва­ние. Там глав­ное — не функ­ция, а объ­ект. Объ­ект — это набор функ­ций и дан­ных. Объ­ект мож­но стан­дар­ти­зи­ро­вать с помо­щью поня­тия «класс».
  • И вот мы здесь.

На примере зверей

Вспом­ним уро­ки био­ло­гии: цар­ство живот­ных, виды и под­ви­ды. Там есть класс «мле­ко­пи­та­ю­щие». У это­го клас­са есть свой­ство, кото­рое отли­ча­ет его от всех дру­гих: все мле­ко­пи­та­ю­щие вскарм­ли­ва­ют детей моло­ком.

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

Сре­ди мле­ко­пи­та­ю­щих есть семей­ства вол­чьих, коша­чьих и так далее. Это тоже клас­сы. У этих клас­сов есть свои мето­ды, напри­мер, как они охо­тят­ся. У них есть свои свой­ства, напри­мер, харак­тер и цвет шер­сти. У них есть свои интер­фей­сы — как они обща­ют­ся меж­ду собой и с внеш­ним миром.

Каж­дый пред­ста­ви­тель семей­ства вол­чьих, напри­мер волк, — это кон­крет­ный объ­ект на осно­ве сво­е­го клас­са. Если мы зна­ем, как в этом клас­се реа­ли­зо­ван метод «Загнать добы­чу», то у кон­крет­но­го вол­ка этот метод будет рабо­тать точ­но так же.

Суть объ­ек­тов в том, что нам не нуж­но каж­дый раз изу­чать или созда­вать его пове­де­ние или свой­ства — мы уже сде­ла­ли это один раз в клас­се, а потом про­сто созда­ём объ­ек­ты на его осно­ве. И если нам пона­до­бит­ся изме­нить спо­соб охо­ты на добы­чу у всех вол­ков, нам доста­точ­но сде­лать это толь­ко в одном месте — в опи­са­нии клас­са. И даль­ше насту­па­ет магия: все объ­ек­ты начи­на­ют вести себя по-новому, хотя мы их не тро­га­ли вооб­ще, мы про­сто изме­ни­ли их базо­вый класс.

Исходные данные

Допу­стим, мы пишем интернет-магазин с систе­мой ски­док. Нам нуж­но рабо­тать с поль­зо­ва­те­ля­ми — посто­ян­ны­ми поку­па­те­ля­ми. Поль­зо­ва­тель у нас будет объ­ек­том: у него есть имя, воз­раст и адрес достав­ки по умол­ча­нию. Мы заве­дём класс, кото­рый помо­жет ини­ци­и­ро­вать ново­го поку­па­те­ля.

Python будет нашим объ­ект­ным язы­ком в этом при­ме­ре. Зада­дим через него класс.

Важ­ное заме­ча­ние: пер­вым пара­мет­ром в кон­струк­то­ре идёт слу­жеб­ное сло­во self. Оно нуж­но для того, что­бы класс мог рабо­тать со сво­и­ми зна­че­ни­я­ми. Это чисто слу­жеб­ное сло­во.

    
language: Python 3
class  User:  

    """Класс для всех покупателей"""  

    # Заводим переменную, которая будет считать число покупателей

    user_count = 0  

    # Это — конструктор, который отвечает за создание новых покупателей 

    def __init__(self, name, age, adress): 

        # В нём мы говорим: возьми те параметры, которые пришли в скобках, и первый пусть будет именем, второй — возрастом, а третий положим в свойство «Адрес»

        self.name = name  

        self.age = age

       self.adress = adress

       # Увеличиваем количество покупателей на единицу

       user.user_count += 1


Ско­пи­ро­вать код
Код ско­пи­ро­ван

Здесь ска­за­но: «Вот класс для поку­па­те­ля. У него есть три свой­ства: имя, воз­раст и адрес». Теперь мы можем заво­дить новых поку­па­те­лей одной стро­кой:

# Заводим покупателей
user1 = User('Вася',23,'Чебоксары)  
user2 = User('Маша',19,'Белгород')

JavaScript у нас будет при­ме­ром про­це­дур­но­го язы­ка, поэто­му мы в нём не будем созда­вать клас­сы, а исполь­зу­ем про­сто пере­мен­ные и функ­ции. Созда­дим тех же двух поль­зо­ва­те­лей:

    
language: JavaScript
// Создаём первого покупателя

user1 = ['Вася',23,'Чебоксары'];  

// Создаём второго покупателя

user2 = ['Маша',19,'Белгород'];


Ско­пи­ро­вать код
Код ско­пи­ро­ван

Гля­дя на код, кажет­ся, что без клас­сов про­ще: код полу­ча­ет­ся коро­че и понят­нее. Так и есть, но лишь для самых про­стых вещей. Как толь­ко мы нач­нём делать дей­стви­тель­но слож­ную про­грам­му, без клас­сов часто вооб­ще не обой­тись.

Важ­ное объ­яс­не­ние про клас­сы в JavaScript.

Мы зна­ем, что в JS тоже есть клас­сы и с ними даже мож­но рабо­тать. Но для обу­че­ния и пони­ма­ния прин­ци­пов ООП клас­сы в JS не очень под­хо­дят: в них всё слож­но с private-переменными и види­мо­стью; тех­ни­че­ски клас­сы — это функ­ции, да и с мето­дом опре­де­ле­ния там не всё так про­сто. Поэто­му мы выбра­ли Python как клас­си­че­ский при­мер объ­ект­но­го под­хо­да: стро­гие клас­сы и все воз­мож­но­сти ООП. В этой ста­тье мы наме­рен­но упро­ща­ем исполь­зо­ва­ние JavaScript, что­бы пока­зать, как рабо­та­ет чистый про­це­дур­ный под­ход. Про­грам­ми­сты, не ругай­тесь на нас за это.

Усложняем задачу

Мы хотим, что­бы у наше­го кли­ен­та появи­лись бонус­ные бал­лы. Для это­го нуж­но впи­сать в класс ещё одно свой­ство. Тогда при созда­нии новых объектов-покупателей у них тоже будут бонус­ные бал­лы. То есть мы пой­дём не в каж­до­го поку­па­те­ля и не будем каж­до­му про­пи­сы­вать бал­лы, а сде­ла­ем это в клас­се.

И окон­ча­тель­но услож­ним: допу­стим, у нас четы­ре уров­ня про­грам­мы лояль­но­сти: 0, 1, 2 и 3. И за каж­дые 10 000 бонус­ных бал­лов мы повы­ша­ем поку­па­те­лю уро­вень на еди­ни­цу, пока не достиг­нем мак­си­маль­но­го. Мы хотим, что­бы у поку­па­те­лей появил­ся метод add_bonus(), кото­рый добав­ля­ет бонус­ные бал­лы и при необ­хо­ди­мо­сти повы­ша­ет уро­вень про­грам­мы лояль­но­сти. И что­бы всё это рабо­та­ло оди­на­ко­во на всех деся­ти поку­па­те­лях.

В ООП-подходе нам пона­до­бит­ся:

  • класс для поку­па­те­лей, в нём пять свойств — имя, воз­раст, адрес достав­ки, чис­ло бонус­ных бал­лов и уро­вень бонус­ной про­грам­мы;
  • в клас­се метод add_bonus(), кото­рый повы­ша­ет уров­ни лояль­но­сти и сле­дит за тем, что­бы он у всех был не выше трёх;
  • когда всё будет гото­во, мы ини­ци­и­ру­ем поку­па­те­лей, а потом начис­ля­ем им какое-то коли­че­ство бал­лов.

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

Класс для покупателей

Python. Вот как будет выгля­деть класс с объектно-ориентированным под­хо­дом. Пока что без мето­дов, толь­ко дан­ные:

    
language: Python 3
class  user:  

    """Класс для всех покупателей"""  

    #  Заводим переменную, которая будет считать число покупателей

    user_count = 0  

  

   # Конструктор, который отвечает за создание новых объектов

    def __init__(self, name, age, adress, bonus, loyalty):  

     # Записываем в новый объект имя покупателя…

         self.name = name

        # его возраст…

        self.age = age

        # адрес доставки…

        self.adress = adress

        # количество бонусных баллов на старте…

        self.bonus = bonus

        # и уровень программы лояльности

        self.loyalty = loyalty

        # Увеличим порядковый номер покупателя на единицу

        user.user_count += 1


Ско­пи­ро­вать код
Код ско­пи­ро­ван

JavaScript. Так как мы дого­во­ри­лись не исполь­зо­вать здесь клас­сы, то созда­вать тоже пока нече­го. На вся­кий слу­чай напом­ним, как мы будем созда­вать пере­мен­ные с поку­па­те­ля­ми, это при­го­дит­ся нам на сле­ду­ю­щем шаге:

// Создаём первого покупателя
user1 = ['Вася',23,'Чебоксары']; 
// Создаём второго покупателя
user2 = ['Маша',19,'Белгород'];

Начисляем бонусные баллы — метод add_bonus()

Зада­ча это­го кода — сде­лать так, что­бы опре­де­лён­ный поку­па­тель полу­чил нуж­ное коли­че­ство бонус­ных бал­лов и исполь­зо­вать это в про­грам­ме лояль­но­сти. Рабо­та­ет она так: за каж­дые 10 000 бал­лов даёт­ся новый уро­вень, но мак­си­маль­ный уро­вень — 3, выше него полу­чить ниче­го нель­зя, даже если бал­лы поз­во­ля­ют.

Логи­ка будет такая: берём бонус­ные бал­лы и при­бав­ля­ем к тем, что уже есть у поку­па­те­ля. Затем делим бал­лы на 10 000, берём целую часть от деле­ния и дела­ем это уров­нем про­грам­мы лояль­но­сти. При этом про­ве­ря­ем, что­бы она не была боль­ше трёх. Если уро­вень полу­ча­ет­ся боль­ше трёх — дела­ем при­ну­ди­тель­но трой­ку.

    
language: Python 3
# Определяем метод начисления бонусных баллов

def add_bonus(self, bonus_count):  

# На вход нам поступило количество бонусных баллов — поместим их в свойство bonus

self.bonus += bonus_count

# Посчитаем новый уровень программы лояльности и поместим его в свойство loyalty

self.loyalty = self.bonus // 10000

# Если у нас получился уровень выше чем 3, то

if self.loyalty > 3:

    # принудительно делаем его равным 3

    self.loyalty = 3

# Выводим сообщение о том, что у такого-то покупателя теперь вот такой бонусный уровень

print('Бонусный уровень покупателя {}: {}' .format(self.name, self.loyalty))


Ско­пи­ро­вать код
Код ско­пи­ро­ван

Посмот­ри­те на послед­нюю строч­ку, где мы выво­дим сооб­ще­ние. Мы сна­ча­ла под­го­то­ви­ли всю стро­ку, а там, где долж­ны быть имя и уро­вень про­грам­мы лояль­но­сти, сто­ят фигур­ные скоб­ки. Это зна­чит, что мы долж­ны назвать те пере­мен­ные, кото­рые туда по оче­ре­ди под­ста­вят­ся — имен­но в той после­до­ва­тель­но­сти, в кото­рой мы их зада­дим. Это дела­ет­ся коман­дой .format, кото­рую мы пишем сра­зу после нашей стро­ки.

Плюс тако­го под­хо­да в том, что это уни­вер­саль­ный метод для каж­до­го объ­ек­та, кото­рый созда­ёт­ся на осно­ве это­го клас­са. Нам не нуж­но писать код для каж­до­го поку­па­те­ля — он рабо­та­ет для всех.

JavaScript. В про­це­дур­ном про­грам­ми­ро­ва­нии каж­дая пере­мен­ная — это отдель­ная сущ­ность, кото­рая может тре­бо­вать инди­ви­ду­аль­но­го под­хо­да. Поэто­му мы будем писать обра­бот­чик бонус­ных бал­лов для каж­дой пере­мен­ной user1, user2 и так далее:

    
language: JavaScript
function user1_add_bonus(bonus_count) {

user1[3] += bonus_count;

user1[4] = Math.floor(user1[3]/10000);

if (user1[4] > 3) { 

user1[4] = 3;

}

console.log('Бонусный уровень покупателя ', user1[0], ' : ', user1[4])

}

 

function user2_add_bonus(bonus_count) {

user2[3] += bonus_count;

user2[4] = Math.floor(user2[3]/10000);

if (user2[4] > 3) { 

user2[4] = 3;

}

console.log('Бонусный уровень покупателя ', user2[0], ' : ', user2[4])

}


Ско­пи­ро­вать код
Код ско­пи­ро­ван

Сра­зу ста­ло мно­го кода — а это мы обра­бо­та­ли все­го двух поку­па­те­лей. Пред­ставь­те, что будет, когда поку­па­те­лей ста­нет хотя бы 10.

Финал: работаем с покупателями

Теперь смо­де­ли­ру­ем такую ситу­а­цию: у нас появ­ля­ет­ся 5 поку­па­те­лей, и неко­то­рым из них мы начис­ля­ем бонус­ные бал­лы.

Python.

    
language: Python 3
class  user:  

    """Класс для всех покупателей"""  

    #  Заводим переменную, которая будет считать число покупателей

    user_count = 0  

  

    # Конструктор, который отвечает за создание новых объектов

    def __init__(self, name, age, adress, bonus, loyalty): 

        # Записываем в новый объект имя покупателя…

        self.name = name

        # его возраст…

        self.age = age

        # адрес доставки…

        self.adress = adress

        # количество бонусных баллов на старте…

        self.bonus = bonus

        # и уровень программы лояльности

        self.loyalty = loyalty

        # Увеличим порядковый номер покупателя на единицу

        user.user_count += 1

 

    # Определяем метод начисления бонусных баллов

    def add_bonus(self, bonus_count):  

        # На вход нам поступило количество бонусных баллов — поместим их в свойство bonus

        self.bonus += bonus_count

        # Посчитаем новый уровень программы лояльности и поместим его в свойство loyalty

        self.loyalty = self.bonus // 10000

        # Если у нас получился уровень выше чем 3, то

        if self.loyalty > 3:

            # принудительно делаем его равным 3

            self.loyalty = 3

        # Выводим сообщение о том, что у такого-то покупателя теперь вот такой бонусный уровень

        print('Бонусный уровень покупателя {}: {}' .format(self.name, self.loyalty))

 

# Создаём первого покупателя

user1 = user('Вася',23,'Чебоксары',0,0)

# Добавляем ему 15000 бонусов

user1.add_bonus(15000)

# Создаём второго покупателя

user2 = user('Маша',19,'Белгород',3000,0)

# Добавляем ей 5000 бонусов

user2.add_bonus(5000)

# Создаём третьего покупателя

user3 = user('Максим',31,'Москва',0,1)

# Создаём четвёртого покупателя

user4 = user('Аня',45,'Казань',5000,2)

# Создаём пятого покупателя

user5 = user('Наташа',32,'Брянск',8000,1)

# Добавляем ей 10000 бонусов

user5.add_bonus(10000)


Ско­пи­ро­вать код
Код ско­пи­ро­ван

JavaScript. Сде­ла­ем то же самое на JS, не забы­вая про­пи­сать свой обра­бот­чик для каж­дой пере­мен­ной:

    
language: JavaScript
function user1_add_bonus(bonus_count) {

    user1[3] += bonus_count;

    user1[4] = Math.floor(user1[3]/10000);

    if (user1[4] > 3) { 

        user1[4] = 3;

    }

    console.log('Бонусный уровень покупателя ', user1[0], ' : ', user1[4])

}

 

function user2_add_bonus(bonus_count) {

    user2[3] += bonus_count;

    user2[4] = Math.floor(user2[3]/10000);

    if (user2[4] > 3) { 

        user2[4] = 3;

    }

    console.log('Бонусный уровень покупателя ', user2[0], ' : ', user2[4])

}

 

function user3_add_bonus(bonus_count) {

    user3[3] += bonus_count;

    user3[4] = Math.floor(user3[3]/10000);

    if (user3[4] > 3) { 

        user3[4] = 3;

    }

    console.log('Бонусный уровень покупателя ', user3[0], ' : ', user3[4])

}

 

function user4_add_bonus(bonus_count) {

    user4[3] += bonus_count;

    user4[4] = Math.floor(user4[3]/10000);

    if (user4[4] > 3) { 

        user4[4] = 3;

    }

    console.log('Бонусный уровень покупателя ', user4[0], ' : ', user4[4])

}

 

function user5_add_bonus(bonus_count) {

    user5[3] += bonus_count;

    user5[4] = Math.floor(user5[3]/10000);

    if (user5[4] > 3) { 

        user5[4] = 3;

    }

    console.log('Бонусный уровень покупателя ', user5[0], ' : ', user5[4])

}

 

// Создаём первого покупателя

user1 = ['Вася',23,'Чебоксары',0,0];

// Добавляем ему 15000 бонусов

user1_add_bonus(15000);

// Создаём второго покупателя

user2 = ['Маша',19,'Белгород',3000,0];

// Добавляем ей 5000 бонусов

user2_add_bonus(5000);

// Создаём третьего покупателя

user3 = ['Максим',31,'Москва',0,1]

// Создаём четвёртого покупателя

user4 = ['Аня',45,'Казань',5000,2];

// Создаём пятого покупателя

user5 = ['Наташа',32,'Брянск',8000,1];

// Добавляем ей 10000 бонусов

user5_add_bonus(10000);


Ско­пи­ро­вать код
Код ско­пи­ро­ван

Код дела­ет то же самое, при этом он боль­ше на 20%, хотя у нас все­го 5 поль­зо­ва­те­лей. Если мы доба­вим ещё 5, раз­мер кода уве­ли­чит­ся вдвое. А теперь пред­ставь­те, что вам нуж­но доба­вить новое дей­ствие — спи­са­ние бонус­ных бал­лов. В клас­се это сде­лать про­сто: в опи­са­нии клас­са добав­ля­ем новый метод, и им могут поль­зо­вать­ся все объ­ек­ты это­го клас­са. А в про­це­дур­ном про­грам­ми­ро­ва­нии нам нуж­но будет про­пи­сы­вать столь­ко же обра­бот­чи­ков, сколь­ко и поку­па­те­лей. Если у нас будет 100 поку­па­те­лей, код пре­вра­тит­ся в ад.

Что дальше

Мы изу­чи­ли, как рабо­та­ют клас­сы, и немно­го про­грам­ми­ро­ва­ли на Питоне. Сле­ду­ю­щий мате­ри­ал — про то, как устро­ен этот язык про­грам­ми­ро­ва­ния и как его быст­ро осво­ить, если вы уже уме­е­те писать на JavaScript.