Практика: Компьютер из компонентов
В прошлых уроках мы разобрали теорию агрегации. Сегодня строим реальную систему: класс Computer, который состоит из независимых компонентов — CPU, RAM, Disk и опционального GPU. Это учебный полигон, где агрегация работает во всей красе.
🎯 Что мы строим сегодня
Представьте, что вы пишете систему для магазина компьютерной техники. Клиент собирает ПК из отдельных комплектующих: выбирает процессор, оперативную память, накопитель. Каждый компонент — самостоятельный объект со своими характеристиками. Компьютер их объединяет, но не владеет ими намертво — можно взять ту же планку RAM и поставить в другой ПК.
🔍 Это классическая агрегация: компоненты создаются отдельно, а затем передаются в компьютер. Компьютер может быть «разобран» — а компоненты останутся и могут войти в новый ПК.
По ходу урока мы пройдём пять шагов: создадим компоненты, соберём компьютер, напишем методы boot(), describe() и upgrade_ram(), а в конце добавим опциональный GPU.
🗂 Проектируем иерархию классов
Прежде чем писать код — договоримся о структуре. Хорошие программисты сначала думают, потом кодят.
CPU
Модель, количество ядер, тактовая частота (ГГц)
RAM
Объём (ГБ), тип памяти (DDR4, DDR5 и т.д.)
Disk
Объём (ГБ), тип — SSD или HDD
GPU (опционально)
Модель, объём видеопамяти (ГБ). Может отсутствовать.
Computer
Агрегирует все компоненты. Методы: boot(), describe(), upgrade_ram()
💡 Почему GPU опциональный? Потому что не каждый компьютер имеет дискретную видеокарту. Это отличная демонстрация того, как агрегация позволяет гибко управлять составом объекта.
🔧 Шаг 1: Классы компонентов — CPU, RAM, Disk
Начнём с «кирпичей». Каждый компонент — самостоятельный класс. Обратите внимание: у каждого есть метод __repr__, чтобы объект красиво отображался при печати.
class CPU:
"""Процессор — мозг компьютера."""
def __init__(self, model, cores, frequency):
self.model = model # например "Intel Core i7-13700K"
self.cores = cores # количество ядер, например 16
self.frequency = frequency # тактовая частота в ГГц, например 3.4
def __repr__(self):
return f"CPU({self.model}, {self.cores} ядер, {self.frequency} ГГц)"
class RAM:
"""Оперативная память."""
def __init__(self, size, memory_type):
self.size = size # объём в ГБ, например 32
self.memory_type = memory_type # тип: "DDR4", "DDR5" и т.д.
def __repr__(self):
return f"RAM({self.size} ГБ, {self.memory_type})"
class Disk:
"""Накопитель данных."""
def __init__(self, size, ssd_or_hdd):
self.size = size # объём в ГБ, например 1000
self.ssd_or_hdd = ssd_or_hdd # строка: "SSD" или "HDD"
def __repr__(self):
return f"Disk({self.size} ГБ, {self.ssd_or_hdd})"
✅ Зачем __repr__? Это «отладочное представление» объекта. Когда мы напишем print(cpu) или увидим объект в ошибке, Python вызовет именно __repr__. Без него вы получите бесполезное <__main__.CPU object at 0x10f5...>. Всегда добавляйте __repr__ к своим классам — это хорошая привычка.
Проверим, что классы работают:
# Создаём компоненты НЕЗАВИСИМО — они живут сами по себе
cpu = CPU("Intel Core i7-13700K", 16, 3.4)
ram = RAM(32, "DDR5")
disk = Disk(1000, "SSD")
print(cpu) # CPU(Intel Core i7-13700K, 16 ядер, 3.4 ГГц)
print(ram) # RAM(32 ГБ, DDR5)
print(disk) # Disk(1000 ГБ, SSD)
💻 Шаг 2: Класс Computer — агрегируем компоненты
Теперь создаём класс Computer. Он принимает готовые объекты компонентов в параметрах __init__. Это и есть агрегация в чистом виде: компьютер не создаёт компоненты — он их принимает.
class Computer:
"""Компьютер, собранный из независимых компонентов.
Использует АГРЕГАЦИЮ: компоненты передаются снаружи
и могут жить после «разборки» этого компьютера.
"""
def __init__(self, name, cpu, ram, disk):
self.name = name # название конфигурации, например "Игровой ПК"
# ✅ АГРЕГАЦИЯ: принимаем уже созданные объекты
self.cpu = cpu
self.ram = ram
self.disk = disk
# GPU пока не добавляем — это будет шаг 5
🔍 Обратите внимание на сигнатуру: def __init__(self, name, cpu, ram, disk). Мы принимаем объекты, а не числа или строки. Это значит, что снаружи уже должны быть созданы экземпляры CPU, RAM, Disk — и затем переданы в компьютер. Жизненный цикл компонентов не зависит от жизненного цикла компьютера.
Соберём компьютер из ранее созданных компонентов:
# Компоненты уже созданы (шаг 1)
cpu = CPU("Intel Core i7-13700K", 16, 3.4)
ram = RAM(32, "DDR5")
disk = Disk(1000, "SSD")
# Передаём их в Computer — агрегация
my_pc = Computer("Рабочая станция", cpu, ram, disk)
# Компоненты доступны через атрибуты компьютера
print(my_pc.cpu) # CPU(Intel Core i7-13700K, 16 ядер, 3.4 ГГц)
print(my_pc.ram) # RAM(32 ГБ, DDR5)
print(my_pc.disk) # Disk(1000 ГБ, SSD)
# Но они всё ещё доступны и напрямую!
print(cpu) # CPU(Intel Core i7-13700K, 16 ядер, 3.4 ГГц)
# Это невозможно при композиции — там объект живёт ТОЛЬКО внутри родителя.
▶️ Шаг 3: Методы boot() и describe()
Добавим два метода в класс Computer. Оба обращаются к атрибутам компонентов — то есть компьютер использует свои компоненты, делегируя им часть работы.
class Computer:
def __init__(self, name, cpu, ram, disk):
self.name = name
self.cpu = cpu
self.ram = ram
self.disk = disk
def describe(self):
"""Выводит полную конфигурацию компьютера."""
print(f"\n💻 Конфигурация: {self.name}")
print(f" Процессор : {self.cpu.model}, {self.cpu.cores} ядер @ {self.cpu.frequency} ГГц")
print(f" Память : {self.ram.size} ГБ {self.ram.memory_type}")
drive_type = "⚡ SSD" if self.disk.ssd_or_hdd == "SSD" else "🐌 HDD"
print(f" Накопитель: {self.disk.size} ГБ ({drive_type})")
def boot(self):
"""Симулирует запуск компьютера, используя данные компонентов."""
print(f"\n🚀 Запуск компьютера «{self.name}»...")
print(f" Инициализация CPU: {self.cpu.model}")
print(f" Загрузка ОС в RAM: {self.ram.size} ГБ доступно")
if self.disk.ssd_or_hdd == "SSD":
print(f" Чтение с диска: {self.disk.size} ГБ SSD — быстро ✅")
else:
print(f" Чтение с диска: {self.disk.size} ГБ HDD — медленно ⚠️")
print(" Система готова к работе!")
💡 Почему это правильный дизайн? Метод boot() не дублирует данные о компонентах — он обращается к self.cpu.model, self.ram.size и т.д. Если завтра мы заменим CPU на новый объект, boot() автоматически начнёт использовать новые данные. Агрегация + делегирование = гибкость.
Проверим:
cpu = CPU("Intel Core i7-13700K", 16, 3.4)
ram = RAM(32, "DDR5")
disk = Disk(1000, "SSD")
my_pc = Computer("Рабочая станция", cpu, ram, disk)
my_pc.describe()
# 💻 Конфигурация: Рабочая станция
# Процессор : Intel Core i7-13700K, 16 ядер @ 3.4 ГГц
# Память : 32 ГБ DDR5
# Накопитель: 1000 ГБ (⚡ SSD)
my_pc.boot()
# 🚀 Запуск компьютера «Рабочая станция»...
# Инициализация CPU: Intel Core i7-13700K
# Загрузка ОС в RAM: 32 ГБ доступно
# Чтение с диска: 1000 ГБ SSD — быстро ✅
# Система готова к работе!
⬆️ Шаг 4: upgrade_ram() — гибкость замены компонентов
Одно из главных преимуществ агрегации — возможность заменить компонент в любой момент. Достаточно создать новый объект и присвоить его атрибуту. Добавим метод upgrade_ram():
def upgrade_ram(self, new_ram):
"""Заменяет оперативную память на новую.
Старый объект RAM не уничтожается — он просто
перестаёт быть частью этого компьютера.
"""
old_ram = self.ram # сохраним ссылку на старую RAM
self.ram = new_ram # ← простая переприсваивание атрибута!
print(f" RAM обновлена: {old_ram} → {new_ram}")
return old_ram # возвращаем старую RAM — её можно использовать дальше
✅ Вся замена — одна строка: self.ram = new_ram. Вот почему агрегация так ценится: компонент заменяется атомарно, без переписывания всего класса. При композиции такой трюк значительно сложнее — объект принадлежит только своему «хозяину».
# Демонстрация upgrade
cpu = CPU("Intel Core i7-13700K", 16, 3.4)
ram = RAM(16, "DDR4") # изначально скромная конфигурация
disk = Disk(512, "SSD")
my_pc = Computer("Офисный ПК", cpu, ram, disk)
my_pc.describe()
# 💻 Конфигурация: Офисный ПК
# Процессор : Intel Core i7-13700K, 16 ядер @ 3.4 ГГц
# Память : 16 ГБ DDR4
# Накопитель: 512 ГБ (⚡ SSD)
# Решили расширить память
new_ram = RAM(64, "DDR5")
old_ram = my_pc.upgrade_ram(new_ram)
# RAM обновлена: RAM(16 ГБ, DDR4) → RAM(64 ГБ, DDR5)
my_pc.describe()
# 💻 Конфигурация: Офисный ПК
# Процессор : Intel Core i7-13700K, 16 ядер @ 3.4 ГГц
# Память : 64 ГБ DDR5
# Накопитель: 512 ГБ (⚡ SSD)
# Старая RAM по-прежнему живёт в переменной old_ram
print(old_ram) # RAM(16 ГБ, DDR4) — никуда не делась!
# Её можно поставить в другой компьютер
🎮 Шаг 5: GPU как опциональный компонент
Не каждый ПК имеет дискретную видеокарту. Сделаем GPU опциональным параметром через значение по умолчанию gpu=None. Это классический Python-паттерн для необязательных зависимостей.
class GPU:
"""Дискретная видеокарта."""
def __init__(self, model, vram):
self.model = model # например "NVIDIA RTX 4080"
self.vram = vram # объём видеопамяти в ГБ, например 16
def __repr__(self):
return f"GPU({self.model}, {self.vram} ГБ VRAM)"
Теперь обновим класс Computer, добавив поддержку GPU:
class Computer:
def __init__(self, name, cpu, ram, disk, gpu=None):
# ^^^^^^^^^
# gpu=None означает: видеокарта необязательна.
# Если не передать — компьютер без дискретного GPU.
self.name = name
self.cpu = cpu
self.ram = ram
self.disk = disk
self.gpu = gpu # None — интегрированная графика / нет GPU
def describe(self):
print(f"\n💻 Конфигурация: {self.name}")
print(f" Процессор : {self.cpu.model}, {self.cpu.cores} ядер @ {self.cpu.frequency} ГГц")
print(f" Память : {self.ram.size} ГБ {self.ram.memory_type}")
drive_type = "⚡ SSD" if self.disk.ssd_or_hdd == "SSD" else "🐌 HDD"
print(f" Накопитель: {self.disk.size} ГБ ({drive_type})")
# Показываем GPU только если он есть
if self.gpu:
print(f" Видеокарта: {self.gpu.model}, {self.gpu.vram} ГБ VRAM")
else:
print(f" Видеокарта: встроенная (дискретной нет)")
def boot(self):
print(f"\n🚀 Запуск компьютера «{self.name}»...")
print(f" Инициализация CPU: {self.cpu.model}")
print(f" Загрузка ОС в RAM: {self.ram.size} ГБ доступно")
if self.disk.ssd_or_hdd == "SSD":
print(f" Чтение с диска: {self.disk.size} ГБ SSD — быстро ✅")
else:
print(f" Чтение с диска: {self.disk.size} ГБ HDD — медленно ⚠️")
if self.gpu:
print(f" Инициализация GPU: {self.gpu.model} ({self.gpu.vram} ГБ VRAM)")
print(" Система готова к работе!")
def upgrade_ram(self, new_ram):
old_ram = self.ram
self.ram = new_ram
print(f" RAM обновлена: {old_ram} → {new_ram}")
return old_ram
def add_gpu(self, gpu):
"""Устанавливает дискретную видеокарту."""
self.gpu = gpu
print(f" Установлен GPU: {gpu}")
def remove_gpu(self):
"""Снимает видеокарту (например, для продажи или перестановки)."""
if self.gpu:
removed = self.gpu
self.gpu = None
print(f" GPU {removed} снят с компьютера")
return removed
else:
print(" GPU не установлен — нечего снимать")
return None
⚠️ Почему именно gpu=None, а не создавать «пустой» GPU? Потому что None — это языковой способ сказать «объект отсутствует». Проверка if self.gpu: читается как «если GPU установлен» — чисто и понятно. Создавать заглушечный объект было бы сложнее и запутаннее.
🏁 Финальный код и демонстрация
Соберём всё вместе. Полная программа — от определения классов до демонстрации всех возможностей:
# ============================================================
# Классы компонентов
# ============================================================
class CPU:
def __init__(self, model, cores, frequency):
self.model = model
self.cores = cores
self.frequency = frequency
def __repr__(self):
return f"CPU({self.model}, {self.cores} ядер, {self.frequency} ГГц)"
class RAM:
def __init__(self, size, memory_type):
self.size = size
self.memory_type = memory_type
def __repr__(self):
return f"RAM({self.size} ГБ, {self.memory_type})"
class Disk:
def __init__(self, size, ssd_or_hdd):
self.size = size
self.ssd_or_hdd = ssd_or_hdd
def __repr__(self):
return f"Disk({self.size} ГБ, {self.ssd_or_hdd})"
class GPU:
def __init__(self, model, vram):
self.model = model
self.vram = vram
def __repr__(self):
return f"GPU({self.model}, {self.vram} ГБ VRAM)"
# ============================================================
# Класс Computer — агрегирует компоненты
# ============================================================
class Computer:
def __init__(self, name, cpu, ram, disk, gpu=None):
self.name = name
self.cpu = cpu
self.ram = ram
self.disk = disk
self.gpu = gpu
def describe(self):
print(f"\n💻 Конфигурация: {self.name}")
print(f" Процессор : {self.cpu.model}, {self.cpu.cores} ядер @ {self.cpu.frequency} ГГц")
print(f" Память : {self.ram.size} ГБ {self.ram.memory_type}")
drive_type = "⚡ SSD" if self.disk.ssd_or_hdd == "SSD" else "🐌 HDD"
print(f" Накопитель: {self.disk.size} ГБ ({drive_type})")
if self.gpu:
print(f" Видеокарта: {self.gpu.model}, {self.gpu.vram} ГБ VRAM")
else:
print(f" Видеокарта: встроенная (дискретной нет)")
def boot(self):
print(f"\n🚀 Запуск компьютера «{self.name}»...")
print(f" Инициализация CPU: {self.cpu.model}")
print(f" Загрузка ОС в RAM: {self.ram.size} ГБ доступно")
if self.disk.ssd_or_hdd == "SSD":
print(f" Чтение с диска: {self.disk.size} ГБ SSD — быстро ✅")
else:
print(f" Чтение с диска: {self.disk.size} ГБ HDD — медленно ⚠️")
if self.gpu:
print(f" Инициализация GPU: {self.gpu.model} ({self.gpu.vram} ГБ VRAM)")
print(" Система готова к работе!")
def upgrade_ram(self, new_ram):
old_ram = self.ram
self.ram = new_ram
print(f" RAM обновлена: {old_ram} → {new_ram}")
return old_ram
def add_gpu(self, gpu):
self.gpu = gpu
print(f" Установлен GPU: {gpu}")
def remove_gpu(self):
if self.gpu:
removed = self.gpu
self.gpu = None
print(f" GPU {removed} снят с компьютера")
return removed
print(" GPU не установлен — нечего снимать")
return None
# ============================================================
# Демонстрация
# ============================================================
print("=" * 50)
print(" СЦЕНАРИЙ 1: Офисный ПК без дискретного GPU")
print("=" * 50)
office_cpu = CPU("Intel Core i5-13400", 10, 2.5)
office_ram = RAM(16, "DDR4")
office_disk = Disk(512, "SSD")
office_pc = Computer("Офисный ПК", office_cpu, office_ram, office_disk)
office_pc.describe()
office_pc.boot()
print("\n" + "=" * 50)
print(" СЦЕНАРИЙ 2: Игровой ПК с видеокартой")
print("=" * 50)
gaming_cpu = CPU("AMD Ryzen 9 7900X", 12, 4.7)
gaming_ram = RAM(32, "DDR5")
gaming_disk = Disk(2000, "SSD")
gaming_gpu = GPU("NVIDIA RTX 4080", 16)
gaming_pc = Computer("Игровой ПК", gaming_cpu, gaming_ram, gaming_disk, gpu=gaming_gpu)
gaming_pc.describe()
gaming_pc.boot()
print("\n" + "=" * 50)
print(" СЦЕНАРИЙ 3: Апгрейд офисного ПК")
print("=" * 50)
# Расширяем RAM
new_ram = RAM(32, "DDR5")
old_ram = office_pc.upgrade_ram(new_ram)
print(f" Старая RAM сохранена: {old_ram}")
# Добавляем GPU
budget_gpu = GPU("NVIDIA GTX 1650", 4)
office_pc.add_gpu(budget_gpu)
office_pc.describe()
print("\n" + "=" * 50)
print(" СЦЕНАРИЙ 4: Перестановка GPU между ПК")
print("=" * 50)
# Снимаем GPU с офисного ПК и ставим в другой
moved_gpu = office_pc.remove_gpu()
# moved_gpu — это тот же объект, он живёт, просто поменял «хозяина»
budget_pc = Computer("Бюджетный ПК", CPU("Intel Core i3-13100", 4, 3.4), RAM(8, "DDR4"), Disk(256, "HDD"))
budget_pc.add_gpu(moved_gpu)
budget_pc.describe()
# Убедимся: gaming_gpu всё ещё в gaming_pc
print(f"\n gaming_pc GPU: {gaming_pc.gpu}") # GPU жив и на месте
Запустите этот код у себя и убедитесь, что все сценарии работают корректно.
✏️ Самостоятельная работа
Попробуйте выполнить одно или несколько из этих заданий:
🟢 Лёгкое
Добавьте метод upgrade_disk(new_disk) по аналогии с upgrade_ram(). Метод должен вернуть старый диск.
🟡 Среднее
Добавьте класс PowerSupply (блок питания) с атрибутами brand и wattage. Сделайте его опциональным компонентом компьютера. В методе boot() выведите предупреждение, если мощности БП недостаточно для GPU (например, GPU требует > 200 Вт, а БП < 500 Вт).
🔴 Сложное
Создайте класс ComputerStore (магазин), который агрегирует список объектов Computer. Добавьте методы: add_computer(pc), sell_computer(name) (удаляет из списка и возвращает объект), list_inventory(). Это агрегация второго уровня!
💬 Подсказка для сложного задания: когда вы пишете sell_computer(name), помните — компьютер уходит из магазина, но его компоненты продолжают существовать в объекте Computer. Это и есть суть агрегации: ComputerStore не «владеет» компьютерами в смысле их уничтожения.
✅ Итоги урока
- CPU, RAM, Disk, GPU — самостоятельные классы с собственными атрибутами и
__repr__. - Computer принимает компоненты в
__init__как параметры — это агрегация в чистом виде. - boot() и describe() обращаются к атрибутам компонентов через
self.cpu.modelи т.д. — делегирование. - upgrade_ram(new_ram) заменяет компонент одной строкой (
self.ram = new_ram), старый объект не уничтожается. - gpu=None — стандартный Python-паттерн для опционального компонента. Проверка
if self.gpu:— идиоматична. - GPU можно переставить из одного компьютера в другой — агрегация это позволяет, в отличие от композиции.
👉 В следующем уроке мы подведём итоги всего модуля о связях между классами. Вы научитесь смотреть на любую задачу и сразу понимать: это наследование, ассоциация, композиция или агрегация? Выбор правильного паттерна — навык, который отличает хорошего разработчика от начинающего.