Урок 3: ООП — Моделируем свой мир
Представь, что ты Бог в миниатюре. Ты не просто даёшь компьютеру команды «сделай то, сделай это». Ты создаёшь сущности (объекты) со своими свойствами и правилами поведения, а потом наблюдаешь, как они взаимодействуют.
Основная идея: программа состоит из объектов, которые являются экземплярами классов. Звучит сложно? Сейчас разжую.
Часть 1: Класс и Объект — Чертеж и Дом
Это самая важная аналогия. Всё остальное будет отталкиваться от неё.
- Класс — это чертёж, инструкция, описание некой сущности.
- Объект (или экземпляр класса) — это конкретный дом, построенный по этому чертежу.
Чертеж (Класс) описывает: * Какие у всех домов будут параметры (атрибуты): количество окон, цвет стен, этажность. * Что все дома смогут делать (методы): открыть дверь, включить свет, провести интернет.
По одному чертежу можно построить много одинаковых домов (объектов). У каждого дома свои конкретные значения параметров: у одного окна белые, у другого — синие, но оба построены по одному чертежу.
Переходим к коду:
# Создаём чертёж (класс) для "Студента"
class Student:
# Метод-конструктор. Вызывается при СОЗДАНИИ нового объекта (студента).
# self - это магическая ссылка на сам создаваемый объект. Это как слово "себя".
def __init__(self, name, age):
# Задаём атрибуты (свойства) нашего объекта
self.name = name # У этого студента будет имя (то, что передали в name)
self.age = age # и возраст (то, что передали в age)
self.is_exhausted = False # А этот атрибут всегда будет сначала False
# Метод (действие), которое может выполнять студент
def study(self, hours):
print(f"{self.name} усердно учится {hours} часов...")
if hours > 2:
self.is_exhausted = True # Меняем состояние объекта!
print(f"Состояние усталости: {self.is_exhausted}")
def take_exam(self):
if self.is_exhausted:
print(f"{self.name} завалил экзамен! Нужно было отдыхать.")
else:
print(f"{self.name} сдал экзамен на отлично!")
def relax(self):
print(f"{self.name} отдыхает и восстанавливает силы.")
self.is_exhausted = False
Теперь используем наш чертёж, чтобы создать реальных студентов (объекты):
# Создаём двух студентов (два объекта по чертежу Student)
student_anna = Student("Анна", 20) # Вызывается __init__ с name="Анна", age=20
student_alex = Student("Алексей", 19)
# Каждый объект живёт своей жизнью!
print(student_anna.name) # Выведет: Анна
print(student_alex.age) # Выведет: 19
# Вызываем методы у объектов
student_anna.study(3) # Анна усердно учится 3 часов...
# Состояние усталости: True
student_alex.study(1) # Алексей усердно учится 1 часов...
# Состояние усталости: False
student_anna.take_exam() # Анна завалил экзамен! Нужно было отдыхать.
student_alex.take_exam() # Алексей сдал экзамен на отлично!
student_anna.relax() # Анна отдыхает и восстанавливает силы.
print(student_anna.is_exhausted) # False
Видишь? Мы создали цифровых существ с состоянием и поведением! Это и есть мощь ООП.
Часть 2: Три Кита (Столпа) ООП
Это три основные идеи, на которых всё держится.
1. Инкапсуляция (Сокрытие)
Это правило: «Не всё, что есть внутри объекта, нужно показывать наружу». Как твой желудок: он работает, но ты не управляешь им напрямую, ты просто «вызываешь метод» eat(food)
.
В Python нет строгого сокрытия, но есть договорённость:
* Атрибуты и методы, начинающиеся с одного нижнего подчёркивания _secret
— являются «protected». Программист смотрит на них и говорит: «Ок, это для внутреннего использования, лучше не трогать это снаружи класса».
* С двух подчёркиваний __very_secret
— являются «private». Python их немного искажает, чтобы сложнее было случайно переопределить снаружи.
Зачем это? Чтобы внутренняя логика объекта могла спокойно меняться, не ломая весь остальной код, который этим объектом пользуется.
class BankAccount:
def __init__(self, initial_balance):
self._balance = initial_balance # _balance - "protected", не трогай плз.
def deposit(self, amount):
if amount > 0:
self._balance += amount
print(f"Пополнили на {amount}. Новый баланс: {self._balance}")
else:
print("Сумма должна быть положительной!")
def get_balance(self): # Делаем метод, чтобы УЗНАТЬ баланс
return self._balance
# Использование
my_account = BankAccount(1000)
# my_account._balance = -1000000 # Так делать НЕЛЬЗЯ (но технически можно). Это нарушает логику.
my_account.deposit(500) # А так — правильно.
print("Мой баланс:", my_account.get_balance())
2. Наследование
Это возможность создать новый класс на основе существующего. Новый класс (дочерний, потомок) наследует все атрибуты и методы старого класса (родительского), и может что-то добавить или изменить.
Представь иерархию: Транспорт –> Машина –> Грузовик.
# Родительский (базовый) класс
class Transport:
def __init__(self, model, speed):
self.model = model
self.speed = speed
def move(self):
print(f"{self.model} начинает движение!")
# Дочерний класс. В скобках указываем родителя.
class Car(Transport): # Car наследуется от Transport
def __init__(self, model, speed, fuel_type):
super().__init__(model, speed) # super() - обращаемся к родителю. Вызываем его конструктор.
self.fuel_type = fuel_type # Добавляем новое свойство, которого не было у родителя
# У родителя был метод move. Он нам подходит, мы его наследуем.
# Но мы можем его и переопределить!
def honk(self): # Добавляем новый метод, которого не было у родителя
print("Beep! Beep!")
# Создаём объект дочернего класса
my_car = Car("Tesla Model 3", 250, "electric")
my_car.move() # Этот метод унаследован от Transport -> "Tesla Model 3 начинает движение!"
my_car.honk() # Этот метод добавили сами -> "Beep! Beep!"
print(my_car.fuel_type) # electric
Зачем это? Чтобы избежать дублирования кода и создавать логичные иерархии.
3. Полиморфизм
Это сложное слово означает: «Один интерфейс — много реализаций». Объекты разных классов могут иметь методы с одинаковыми названиями, но разным поведением.
# Два разных класса
class Dog:
def speak(self):
return "Гав!"
class Cat:
def speak(self):
return "Мяу!"
# Функция, которая работает с ЛЮБЫМ объектом, у которого есть метод .speak()
def make_sound(animal):
print(animal.speak())
# Создаём объекты
sharik = Dog()
murzik = Cat()
make_sound(sharik) # Выведет: Гав!
make_sound(murzik) # Выведет: Мяу!
Функция make_sound
не знает и не cares, какой именно класс ей передали. Её волнует только одно: чтобы у объекта был метод .speak()
. Это и есть полиморфизм. Он позволяет писать очень гибкий и универсальный код.
(Четвёртый кит — Абстракция, но на первых порах можно думать о ней как о проектировании классов через призму этих трёх понятий).
Часть 3: Практика. Создаём маленький мирок
Давай смоделируем простую текстовую RPG-игру.
class Character:
def __init__(self, name, health, damage):
self.name = name
self.health = health
self.damage = damage
def attack(self, enemy):
print(f"{self.name} атакует {enemy.name} и наносит {self.damage} урона!")
enemy.health -= self.damage
print(f"У {enemy.name} осталось {enemy.health} здоровья.\n")
def is_alive(self):
return self.health > 0
# Наследуемся от Character чтобы создать более специфичного героя
class Hero(Character):
def __init__(self, name):
super().__init__(name, health=100, damage=15) # Герой крепче и сильнее
self.potions = 2
def drink_potion(self):
if self.potions > 0:
self.health += 30
self.potions -= 1
print(f"{self.name} выпил зелье! Здоровье: {self.health}. Зелий осталось: {self.potions}")
else:
print("Нет зелий!")
# Создаём мир
hero = Hero("Артур")
goblin = Character("Гоблин", health=40, damage=5)
# Бой!
print("Начинается бой!\n")
while hero.is_alive() and goblin.is_alive():
hero.attack(goblin)
if not goblin.is_alive():
break
goblin.attack(hero)
if not hero.is_alive():
break
# После первого обмена ударами герой решает выпить зелье
if hero.health < 50:
hero.drink_potion()
if hero.is_alive():
print(f"{hero.name} побеждает!")
else:
print("Герой пал...")
Задание для тебя:
1. Добавь класс Archer
(Лучник), который наследуется от Hero
. Пусть он наносит меньше урона в ближнем бою, но имеет метод shoot(self, enemy)
, который наносит двойной урон.
2. Сделай так, чтобы гоблин тоже мог пить зелье (с шансом 10% после получения урона).
ООП — это не про синтаксис, а про дизайн, про то, как ты мыслишь о проблеме. Сначала думаешь, из каких сущностей (объектов) она состоит, какие у них свойства и как они общаются, а потом уже пишешь код.
Ты сделал огромный шаг вперёд. Теперь твои программы могут моделировать реальный мир! Дальше — больше: исключения, работа с сетью, базы данных. Но оставь это для следующих уроков.
Удачи в создании своих вселенных! Коди с удовольствием.