4 принципа ООП: Фундамент объектно-ориентированного программирования
Объектно-ориентированное программирование (ООП) — это парадигма разработки, основанная на концепции “объектов”, которые содержат данные и код для работы с этими данными. ООП строится на четырех фундаментальных принципах, понимание которых критически важно для любого разработчика.
Что такое ООП?
ООП — это способ организации кода, при котором программа представляется как набор взаимодействующих объектов. Каждый объект является экземпляром класса — шаблона, определяющего структуру и поведение объектов.
Основные понятия:
- Класс — шаблон или чертеж для создания объектов
- Объект — конкретный экземпляр класса
- Свойства (атрибуты) — данные, характеризующие объект
- Методы — функции, определяющие поведение объекта
Принцип 1: Инкапсуляция (Encapsulation)
Что это такое?
Инкапсуляция — это сокрытие внутренней реализации объекта и предоставление доступа к его данным только через публичные методы. Это как автомобиль: вы знаете, как им управлять (руль, педали), но не обязаны знать, как работает двигатель внутри.
Цели инкапсуляции:
- Защита данных от некорректного использования
- Скрытие сложности реализации
- Упрощение изменения внутренней логики без влияния на внешний код
- Контроль доступа к данным
Уровни доступа
PHP:
public— доступен вездеprotected— доступен в классе и его наследникахprivate— доступен только внутри класса
Go:
- Заглавная буква — экспортируемый (публичный)
- Строчная буква — не экспортируемый (приватный)
Пример на PHP
<?php
class BankAccount {
// Приватные свойства - скрыты от внешнего доступа
private float $balance;
private string $accountNumber;
private array $transactionHistory = [];
public function __construct(string $accountNumber, float $initialBalance = 0) {
$this->accountNumber = $accountNumber;
$this->balance = $initialBalance;
$this->addTransaction("Открытие счета", $initialBalance);
}
// Публичный метод для пополнения
public function deposit(float $amount): bool {
if ($amount <= 0) {
throw new InvalidArgumentException("Сумма должна быть положительной");
}
$this->balance += $amount;
$this->addTransaction("Пополнение", $amount);
return true;
}
// Публичный метод для снятия
public function withdraw(float $amount): bool {
if ($amount <= 0) {
throw new InvalidArgumentException("Сумма должна быть положительной");
}
if (!$this->hasSufficientFunds($amount)) {
throw new Exception("Недостаточно средств");
}
$this->balance -= $amount;
$this->addTransaction("Снятие", -$amount);
return true;
}
// Публичный геттер - контролируемый доступ к балансу
public function getBalance(): float {
return $this->balance;
}
public function getAccountNumber(): string {
return $this->accountNumber;
}
public function getTransactionHistory(): array {
return $this->transactionHistory;
}
// Приватный метод - внутренняя логика
private function hasSufficientFunds(float $amount): bool {
return $this->balance >= $amount;
}
// Приватный метод для логирования транзакций
private function addTransaction(string $type, float $amount): void {
$this->transactionHistory[] = [
'date' => date('Y-m-d H:i:s'),
'type' => $type,
'amount' => $amount,
'balance' => $this->balance
];
}
}
// Использование
$account = new BankAccount("123456789", 1000);
// Правильный способ - через публичные методы
$account->deposit(500);
echo "Баланс: " . $account->getBalance(); // 1500
$account->withdraw(300);
echo "Баланс: " . $account->getBalance(); // 1200
// Неправильный способ - прямой доступ запрещен
// $account->balance = 1000000; // ОШИБКА! Fatal error
// $account->addTransaction(...); // ОШИБКА! Приватный метод
Пример на Go
package main
import (
"errors"
"fmt"
"time"
)
// Transaction представляет транзакцию
type Transaction struct {
Date time.Time
Type string
Amount float64
Balance float64
}
// BankAccount - структура с приватными полями (строчные буквы)
type BankAccount struct {
balance float64 // приватное поле
accountNumber string // приватное поле
transactionHistory []Transaction // приватное поле
}
// NewBankAccount - конструктор (публичная функция)
func NewBankAccount(accountNumber string, initialBalance float64) *BankAccount {
account := &BankAccount{
accountNumber: accountNumber,
balance: initialBalance,
transactionHistory: make([]Transaction, 0),
}
account.addTransaction("Открытие счета", initialBalance)
return account
}
// Deposit - публичный метод для пополнения (заглавная буква)
func (a *BankAccount) Deposit(amount float64) error {
if amount <= 0 {
return errors.New("сумма должна быть положительной")
}
a.balance += amount
a.addTransaction("Пополнение", amount)
return nil
}
// Withdraw - публичный метод для снятия
func (a *BankAccount) Withdraw(amount float64) error {
if amount <= 0 {
return errors.New("сумма должна быть положительной")
}
if !a.hasSufficientFunds(amount) {
return errors.New("недостаточно средств")
}
a.balance -= amount
a.addTransaction("Снятие", -amount)
return nil
}
// GetBalance - публичный геттер (заглавная буква)
func (a *BankAccount) GetBalance() float64 {
return a.balance
}
// GetAccountNumber - публичный геттер
func (a *BankAccount) GetAccountNumber() string {
return a.accountNumber
}
// GetTransactionHistory - публичный геттер
func (a *BankAccount) GetTransactionHistory() []Transaction {
// Возвращаем копию, чтобы защитить внутренние данные
history := make([]Transaction, len(a.transactionHistory))
copy(history, a.transactionHistory)
return history
}
// hasSufficientFunds - приватный метод (строчная буква)
func (a *BankAccount) hasSufficientFunds(amount float64) bool {
return a.balance >= amount
}
// addTransaction - приватный метод
func (a *BankAccount) addTransaction(transactionType string, amount float64) {
transaction := Transaction{
Date: time.Now(),
Type: transactionType,
Amount: amount,
Balance: a.balance,
}
a.transactionHistory = append(a.transactionHistory, transaction)
}
func main() {
account := NewBankAccount("123456789", 1000)
// Правильный способ - через публичные методы
account.Deposit(500)
fmt.Printf("Баланс: %.2f\n", account.GetBalance()) // 1500
account.Withdraw(300)
fmt.Printf("Баланс: %.2f\n", account.GetBalance()) // 1200
// Неправильный способ - прямой доступ невозможен из другого пакета
// account.balance = 1000000 // ОШИБКА при компиляции
// account.addTransaction(...) // ОШИБКА при компиляции
}
Преимущества инкапсуляции:
- Нельзя установить отрицательный баланс напрямую
- Все изменения логируются автоматически
- Можно изменить внутреннюю реализацию без изменения внешнего API
- Валидация данных в одном месте
Принцип 2: Наследование (Inheritance)
Что это такое?
Наследование — это механизм создания новых классов на основе существующих. Дочерний класс (наследник) получает все свойства и методы родительского класса и может добавлять свои или переопределять унаследованные.
Аналогия: Биологическое наследование — ребенок наследует черты родителей, но также имеет свои уникальные особенности.
Зачем нужно:
- Переиспользование кода
- Создание иерархии классов
- Расширение функциональности без изменения базового класса
- Реализация принципа DRY (Don’t Repeat Yourself)
Пример на PHP
<?php
// Базовый класс - общие свойства всех животных
abstract class Animal {
protected string $name;
protected int $age;
protected float $weight;
public function __construct(string $name, int $age, float $weight) {
$this->name = $name;
$this->age = $age;
$this->weight = $weight;
}
// Общий метод для всех животных
public function eat(float $foodWeight): void {
$this->weight += $foodWeight * 0.1;
echo "{$this->name} поел(а) и теперь весит {$this->weight} кг\n";
}
public function sleep(): void {
echo "{$this->name} спит...\n";
}
public function getInfo(): string {
return "Имя: {$this->name}, Возраст: {$this->age}, Вес: {$this->weight} кг";
}
// Абстрактный метод - должен быть реализован в наследниках
abstract public function makeSound(): void;
}
// Наследник - Собака
class Dog extends Animal {
private string $breed;
private bool $isTrained;
public function __construct(string $name, int $age, float $weight, string $breed) {
parent::__construct($name, $age, $weight); // Вызов конструктора родителя
$this->breed = $breed;
$this->isTrained = false;
}
// Реализация абстрактного метода
public function makeSound(): void {
echo "{$this->name} гавкает: Гав-гав!\n";
}
// Уникальный метод для собак
public function fetch(): void {
echo "{$this->name} приносит мячик\n";
}
public function train(): void {
$this->isTrained = true;
echo "{$this->name} обучен(а) командам!\n";
}
// Переопределение метода родителя
public function getInfo(): string {
$parentInfo = parent::getInfo(); // Получаем базовую информацию
$trained = $this->isTrained ? "обучен(а)" : "не обучен(а)";
return "$parentInfo, Порода: {$this->breed}, {$trained}";
}
}
// Наследник - Кошка
class Cat extends Animal {
private int $livesLeft;
public function __construct(string $name, int $age, float $weight) {
parent::__construct($name, $age, $weight);
$this->livesLeft = 9; // У кошек 9 жизней
}
public function makeSound(): void {
echo "{$this->name} мяукает: Мяу-мяу!\n";
}
// Уникальный метод для кошек
public function scratch(): void {
echo "{$this->name} точит когти об диван\n";
}
public function useLive(): void {
if ($this->livesLeft > 0) {
$this->livesLeft--;
echo "{$this->name} потерял(а) жизнь. Осталось: {$this->livesLeft}\n";
}
}
public function getInfo(): string {
$parentInfo = parent::getInfo();
return "$parentInfo, Жизней осталось: {$this->livesLeft}";
}
}
// Наследник - Птица
class Bird extends Animal {
private float $wingSpan;
private bool $canFly;
public function __construct(string $name, int $age, float $weight, float $wingSpan, bool $canFly = true) {
parent::__construct($name, $age, $weight);
$this->wingSpan = $wingSpan;
$this->canFly = $canFly;
}
public function makeSound(): void {
echo "{$this->name} чирикает: Чик-чирик!\n";
}
public function fly(): void {
if ($this->canFly) {
echo "{$this->name} летит с размахом крыльев {$this->wingSpan} см\n";
} else {
echo "{$this->name} не может летать\n";
}
}
}
// Использование
$dog = new Dog("Бобик", 3, 15.5, "Лабрадор");
echo $dog->getInfo() . "\n";
$dog->makeSound();
$dog->eat(0.5);
$dog->fetch();
$dog->train();
echo $dog->getInfo() . "\n\n";
$cat = new Cat("Мурка", 2, 4.2);
echo $cat->getInfo() . "\n";
$cat->makeSound();
$cat->scratch();
$cat->useLive();
echo $cat->getInfo() . "\n\n";
$bird = new Bird("Кеша", 1, 0.3, 25);
echo $bird->getInfo() . "\n";
$bird->makeSound();
$bird->fly();
// Полиморфизм через наследование
function feedAnimal(Animal $animal): void {
$animal->eat(0.5);
}
feedAnimal($dog);
feedAnimal($cat);
feedAnimal($bird);
Пример на Go (композиция вместо наследования)
Важно: В Go нет классического наследования. Вместо этого используется композиция и интерфейсы. Это более гибкий подход.
package main
import "fmt"
// Animal - базовая структура (встраиваемая)
type Animal struct {
name string
age int
weight float64
}
// Конструктор для Animal
func NewAnimal(name string, age int, weight float64) Animal {
return Animal{
name: name,
age: age,
weight: weight,
}
}
// Методы Animal
func (a *Animal) Eat(foodWeight float64) {
a.weight += foodWeight * 0.1
fmt.Printf("%s поел(а) и теперь весит %.2f кг\n", a.name, a.weight)
}
func (a *Animal) Sleep() {
fmt.Printf("%s спит...\n", a.name)
}
func (a *Animal) GetInfo() string {
return fmt.Sprintf("Имя: %s, Возраст: %d, Вес: %.2f кг", a.name, a.age, a.weight)
}
// Интерфейс - контракт для всех животных
type SoundMaker interface {
MakeSound()
}
// Dog - композиция: встраивает Animal
type Dog struct {
Animal // встроенная структура (anonymous field)
breed string
isTrained bool
}
// Конструктор для Dog
func NewDog(name string, age int, weight float64, breed string) *Dog {
return &Dog{
Animal: NewAnimal(name, age, weight),
breed: breed,
isTrained: false,
}
}
// Реализация интерфейса SoundMaker
func (d *Dog) MakeSound() {
fmt.Printf("%s гавкает: Гав-гав!\n", d.name)
}
// Уникальные методы Dog
func (d *Dog) Fetch() {
fmt.Printf("%s приносит мячик\n", d.name)
}
func (d *Dog) Train() {
d.isTrained = true
fmt.Printf("%s обучен(а) командам!\n", d.name)
}
// Переопределение метода GetInfo
func (d *Dog) GetInfo() string {
baseInfo := d.Animal.GetInfo() // Вызов метода встроенной структуры
trained := "не обучен(а)"
if d.isTrained {
trained = "обучен(а)"
}
return fmt.Sprintf("%s, Порода: %s, %s", baseInfo, d.breed, trained)
}
// Cat - композиция
type Cat struct {
Animal
livesLeft int
}
func NewCat(name string, age int, weight float64) *Cat {
return &Cat{
Animal: NewAnimal(name, age, weight),
livesLeft: 9,
}
}
func (c *Cat) MakeSound() {
fmt.Printf("%s мяукает: Мяу-мяу!\n", c.name)
}
func (c *Cat) Scratch() {
fmt.Printf("%s точит когти об диван\n", c.name)
}
func (c *Cat) UseLive() {
if c.livesLeft > 0 {
c.livesLeft--
fmt.Printf("%s потерял(а) жизнь. Осталось: %d\n", c.name, c.livesLeft)
}
}
func (c *Cat) GetInfo() string {
baseInfo := c.Animal.GetInfo()
return fmt.Sprintf("%s, Жизней осталось: %d", baseInfo, c.livesLeft)
}
// Bird - композиция
type Bird struct {
Animal
wingSpan float64
canFly bool
}
func NewBird(name string, age int, weight float64, wingSpan float64, canFly bool) *Bird {
return &Bird{
Animal: NewAnimal(name, age, weight),
wingSpan: wingSpan,
canFly: canFly,
}
}
func (b *Bird) MakeSound() {
fmt.Printf("%s чирикает: Чик-чирик!\n", b.name)
}
func (b *Bird) Fly() {
if b.canFly {
fmt.Printf("%s летит с размахом крыльев %.2f см\n", b.name, b.wingSpan)
} else {
fmt.Printf("%s не может летать\n", b.name)
}
}
// Полиморфизм через интерфейс
func makeAnimalSound(animal SoundMaker) {
animal.MakeSound()
}
func main() {
dog := NewDog("Бобик", 3, 15.5, "Лабрадор")
fmt.Println(dog.GetInfo())
dog.MakeSound()
dog.Eat(0.5)
dog.Fetch()
dog.Train()
fmt.Println(dog.GetInfo())
fmt.Println()
cat := NewCat("Мурка", 2, 4.2)
fmt.Println(cat.GetInfo())
cat.MakeSound()
cat.Scratch()
cat.UseLive()
fmt.Println(cat.GetInfo())
fmt.Println()
bird := NewBird("Кеша", 1, 0.3, 25, true)
fmt.Println(bird.GetInfo())
bird.MakeSound()
bird.Fly()
fmt.Println()
// Полиморфизм через интерфейс
animals := []SoundMaker{dog, cat, bird}
for _, animal := range animals {
makeAnimalSound(animal)
}
}
Ключевые отличия:
- PHP: классическое наследование через
extends - Go: композиция через встраивание структур и интерфейсы для полиморфизма
Принцип 3: Полиморфизм (Polymorphism)
Что это такое?
Полиморфизм (от греч. “много форм”) — это возможность объектов с одинаковым интерфейсом иметь различную реализацию. Один и тот же метод может вести себя по-разному в зависимости от типа объекта.
Типы полиморфизма:
- Полиморфизм подтипов — через наследование/интерфейсы
- Параметрический полиморфизм — generic-типы (Go 1.18+)
- Ad-hoc полиморфизм — перегрузка методов (PHP не поддерживает)
Пример на PHP
<?php
// Интерфейс для оплаты
interface PaymentMethod {
public function processPayment(float $amount): bool;
public function getTransactionFee(float $amount): float;
public function getPaymentInfo(): string;
}
// Оплата кредитной картой
class CreditCardPayment implements PaymentMethod {
private string $cardNumber;
private string $cardHolder;
private string $cvv;
public function __construct(string $cardNumber, string $cardHolder, string $cvv) {
$this->cardNumber = $cardNumber;
$this->cardHolder = $cardHolder;
$this->cvv = $cvv;
}
public function processPayment(float $amount): bool {
$fee = $this->getTransactionFee($amount);
$total = $amount + $fee;
echo "Обработка платежа по карте **** " . substr($this->cardNumber, -4) . "\n";
echo "Сумма: {$amount} руб + комиссия: {$fee} руб = {$total} руб\n";
// Имитация обработки платежа
sleep(1);
echo "Платеж успешно проведен!\n\n";
return true;
}
public function getTransactionFee(float $amount): float {
return $amount * 0.025; // 2.5% комиссия
}
public function getPaymentInfo(): string {
return "Кредитная карта: **** " . substr($this->cardNumber, -4);
}
}
// Оплата через PayPal
class PayPalPayment implements PaymentMethod {
private string $email;
public function __construct(string $email) {
$this->email = $email;
}
public function processPayment(float $amount): bool {
$fee = $this->getTransactionFee($amount);
$total = $amount + $fee;
echo "Обработка платежа через PayPal ({$this->email})\n";
echo "Сумма: {$amount} руб + комиссия: {$fee} руб = {$total} руб\n";
sleep(1);
echo "Платеж успешно проведен через PayPal!\n\n";
return true;
}
public function getTransactionFee(float $amount): float {
return $amount * 0.034 + 10; // 3.4% + 10 руб фиксированная комиссия
}
public function getPaymentInfo(): string {
return "PayPal: {$this->email}";
}
}
// Оплата криптовалютой
class CryptoPayment implements PaymentMethod {
private string $walletAddress;
private string $currency;
public function __construct(string $walletAddress, string $currency = "BTC") {
$this->walletAddress = $walletAddress;
$this->currency = $currency;
}
public function processPayment(float $amount): bool {
$fee = $this->getTransactionFee($amount);
$total = $amount + $fee;
echo "Обработка платежа в {$this->currency}\n";
echo "Адрес кошелька: " . substr($this->walletAddress, 0, 10) . "...\n";
echo "Сумма: {$amount} руб + комиссия сети: {$fee} руб = {$total} руб\n";
sleep(2); // Криптоплатежи обрабатываются дольше
echo "Транзакция отправлена в блокчейн!\n\n";
return true;
}
public function getTransactionFee(float $amount): float {
return 50; // Фиксированная комиссия сети
}
public function getPaymentInfo(): string {
return "Криптовалюта {$this->currency}: " . substr($this->walletAddress, 0, 10) . "...";
}
}
// Класс заказа, использующий полиморфизм
class Order {
private string $orderId;
private float $totalAmount;
private array $items;
public function __construct(string $orderId, array $items) {
$this->orderId = $orderId;
$this->items = $items;
$this->totalAmount = array_sum(array_column($items, 'price'));
}
// Метод принимает ЛЮБОЙ объект, реализующий PaymentMethod
// Это и есть полиморфизм!
public function checkout(PaymentMethod $paymentMethod): bool {
echo "========================================\n";
echo "Заказ №{$this->orderId}\n";
echo "Товаров: " . count($this->items) . "\n";
echo "Сумма: {$this->totalAmount} руб\n";
echo "Способ оплаты: " . $paymentMethod->getPaymentInfo() . "\n";
echo "========================================\n\n";
// Вызываем processPayment, но реализация зависит от типа объекта
return $paymentMethod->processPayment($this->totalAmount);
}
}
// Использование
$items = [
['name' => 'Ноутбук', 'price' => 50000],
['name' => 'Мышка', 'price' => 1000],
['name' => 'Клавиатура', 'price' => 3000]
];
$order1 = new Order("ORD-001", $items);
$order2 = new Order("ORD-002", $items);
$order3 = new Order("ORD-003", $items);
// Один и тот же метод checkout работает с разными способами оплаты
$creditCard = new CreditCardPayment("1234567890123456", "Ivan Ivanov", "123");
$order1->checkout($creditCard);
$paypal = new PayPalPayment("user@example.com");
$order2->checkout($paypal);
$crypto = new CryptoPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "BTC");
$order3->checkout($crypto);
// Можно легко добавить новый способ оплаты, не меняя код Order
class BankTransferPayment implements PaymentMethod {
private string $accountNumber;
public function __construct(string $accountNumber) {
$this->accountNumber = $accountNumber;
}
public function processPayment(float $amount): bool {
echo "Банковский перевод на счет {$this->accountNumber}\n";
echo "Сумма: {$amount} руб (без комиссии)\n";
echo "Платеж обрабатывается банком...\n\n";
return true;
}
public function getTransactionFee(float $amount): float {
return 0; // Без комиссии
}
public function getPaymentInfo(): string {
return "Банковский перевод: {$this->accountNumber}";
}
}
$order4 = new Order("ORD-004", $items);
$bankTransfer = new BankTransferPayment("40817810099910004312");
$order4->checkout($bankTransfer);
Пример на Go
package main
import (
"fmt"
"strings"
"time"
)
// PaymentMethod - интерфейс для всех способов оплаты
type PaymentMethod interface {
ProcessPayment(amount float64) bool
GetTransactionFee(amount float64) float64
GetPaymentInfo() string
}
// CreditCardPayment - оплата картой
type CreditCardPayment struct {
cardNumber string
cardHolder string
cvv string
}
func NewCreditCardPayment(cardNumber, cardHolder, cvv string) *CreditCardPayment {
return &CreditCardPayment{
cardNumber: cardNumber,
cardHolder: cardHolder,
cvv: cvv,
}
}
func (c *CreditCardPayment) ProcessPayment(amount float64) bool {
fee := c.GetTransactionFee(amount)
total := amount + fee
lastFour := c.cardNumber[len(c.cardNumber)-4:]
fmt.Printf("Обработка платежа по карте **** %s\n", lastFour)
fmt.Printf("Сумма: %.2f руб + комиссия: %.2f руб = %.2f руб\n", amount, fee, total)
time.Sleep(1 * time.Second)
fmt.Println("Платеж успешно проведен!\n")
return true
}
func (c *CreditCardPayment) GetTransactionFee(amount float64) float64 {
return amount * 0.025 // 2.5% комиссия
}
func (c *CreditCardPayment) GetPaymentInfo() string {
lastFour := c.cardNumber[len(c.cardNumber)-4:]
return fmt.Sprintf("Кредитная карта: **** %s", lastFour)
}
// PayPalPayment - оплата через PayPal
type PayPalPayment struct {
email string
}
func NewPayPalPayment(email string) *PayPalPayment {
return &PayPalPayment{email: email}
}
func (p *PayPalPayment) ProcessPayment(amount float64) bool {
fee := p.GetTransactionFee(amount)
total := amount + fee
fmt.Printf("Обработка платежа через PayPal (%s)\n", p.email)
fmt.Printf("Сумма: %.2f руб + комиссия: %.2f руб = %.2f руб\n", amount, fee, total)
time.Sleep(1 * time.Second)
fmt.Println("Платеж успешно проведен через PayPal!\n")
return true
}
func (p *PayPalPayment) GetTransactionFee(amount float64) float64 {
return amount*0.034 + 10 // 3.4% + 10 руб
}
func (p *PayPalPayment) GetPaymentInfo() string {
return fmt.Sprintf("PayPal: %s", p.email)
}
// CryptoPayment - оплата криптовалютой
type CryptoPayment struct {
walletAddress string
currency string
}
func NewCryptoPayment(walletAddress, currency string) *CryptoPayment {
return &CryptoPayment{
walletAddress: walletAddress,
currency: currency,
}
}
func (cr *CryptoPayment) ProcessPayment(amount float64) bool {
fee := cr.GetTransactionFee(amount)
total := amount + fee
fmt.Printf("Обработка платежа в %s\n", cr.currency)
fmt.Printf("Адрес кошелька: %s...\n", cr.walletAddress[:10])
fmt.Printf("Сумма: %.2f руб + комиссия сети: %.2f руб = %.2f руб\n", amount, fee, total)
time.Sleep(2 * time.Second)
fmt.Println("Транзакция отправлена в блокчейн!\n")
return true
}
func (cr *CryptoPayment) GetTransactionFee(amount float64) float64 {
return 50 // Фиксированная комиссия сети
}
func (cr *CryptoPayment) GetPaymentInfo() string {
return fmt.Sprintf("Криптовалюта %s: %s...", cr.currency, cr.walletAddress[:10])
}
// Item - товар в заказе
type Item struct {
Name string
Price float64
}
// Order - заказ
type Order struct {
orderID string
totalAmount float64
items []Item
}
func NewOrder(orderID string, items []Item) *Order {
total := 0.0
for _, item := range items {
total += item.Price
}
return &Order{
orderID: orderID,
totalAmount: total,
items: items,
}
}
// Checkout - метод принимает ЛЮБОЙ тип, реализующий интерфейс PaymentMethod
// Это полиморфизм!
func (o *Order) Checkout(paymentMethod PaymentMethod) bool {
fmt.Println(strings.Repeat("=", 40))
fmt.Printf("Заказ №%s\n", o.orderID)
fmt.Printf("Товаров: %d\n", len(o.items))
fmt.Printf("Сумма: %.2f руб\n", o.totalAmount)
fmt.Printf("Способ оплаты: %s\n", paymentMethod.GetPaymentInfo())
fmt.Println(strings.Repeat("=", 40))
fmt.Println()
return paymentMethod.ProcessPayment(o.totalAmount)
}
// BankTransferPayment - банковский перевод
type BankTransferPayment struct {
accountNumber string
}
func NewBankTransferPayment(accountNumber string) *BankTransferPayment {
return &BankTransferPayment{accountNumber: accountNumber}
}
func (b *BankTransferPayment) ProcessPayment(amount float64) bool {
fmt.Printf("Банковский перевод на счет %s\n", b.accountNumber)
fmt.Printf("Сумма: %.2f руб (без комиссии)\n", amount)
fmt.Println("Платеж обрабатывается банком...\n")
return true
}
func (b *BankTransferPayment) GetTransactionFee(amount float64) float64 {
return 0 // Без комиссии
}
func (b *BankTransferPayment) GetPaymentInfo() string {
return fmt.Sprintf("Банковский перевод: %s", b.accountNumber)
}
func main() {
items := []Item{
{Name: "Ноутбук", Price: 50000},
{Name: "Мышка", Price: 1000},
{Name: "Клавиатура", Price: 3000},
}
// Создаем заказы
order1 := NewOrder("ORD-001", items)
order2 := NewOrder("ORD-002", items)
order3 := NewOrder("ORD-003", items)
order4 := NewOrder("ORD-004", items)
// Один и тот же метод Checkout работает с разными способами оплаты
creditCard := NewCreditCardPayment("1234567890123456", "Ivan Ivanov", "123")
order1.Checkout(creditCard)
paypal := NewPayPalPayment("user@example.com")
order2.Checkout(paypal)
crypto := NewCryptoPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "BTC")
order3.Checkout(crypto)
bankTransfer := NewBankTransferPayment("40817810099910004312")
order4.Checkout(bankTransfer)
}
Преимущества полиморфизма:
- Единый интерфейс для разных реализаций
- Легко добавлять новые типы без изменения существующего кода
- Код становится более гибким и расширяемым
- Упрощает тестирование (можно создавать mock-объекты)
Принцип 4: Абстракция (Abstraction)
Что это такое?
Абстракция — это выделение существенных характеристик объекта и игнорирование несущественных. Абстракция позволяет работать с объектами на более высоком уровне, не вдаваясь в детали реализации.
Цели абстракции:
- Упростить сложные системы
- Скрыть детали реализации
- Создать общий контракт (интерфейс)
- Сфокусироваться на том, что делает объект, а не как
Инструменты:
- Абстрактные классы (PHP)
- Интерфейсы (PHP, Go)
- Абстрактные методы
Пример на PHP
<?php
// Абстрактный класс - нельзя создать экземпляр напрямую
abstract class DatabaseConnection {
protected string $host;
protected string $database;
protected string $username;
protected string $password;
protected $connection = null;
public function __construct(string $host, string $database, string $username, string $password) {
$this->host = $host;
$this->database = $database;
$this->username = $username;
$this->password = $password;
}
// Абстрактные методы - должны быть реализованы в наследниках
abstract public function connect(): bool;
abstract public function disconnect(): void;
abstract public function query(string $sql): array;
abstract public function execute(string $sql): bool;
abstract public function lastInsertId(): int;
// Конкретный метод - общий для всех БД
public function isConnected(): bool {
return $this->connection !== null;
}
// Шаблонный метод - определяет общий алгоритм
public function executeTransaction(array $queries): bool {
try {
$this->beginTransaction();
foreach ($queries as $query) {
if (!$this->execute($query)) {
$this->rollback();
return false;
}
}
$this->commit();
return true;
} catch (Exception $e) {
$this->rollback();
throw $e;
}
}
abstract protected function beginTransaction(): void;
abstract protected function commit(): void;
abstract protected function rollback(): void;
}
// Конкретная реализация для MySQL
class MySQLConnection extends DatabaseConnection {
public function connect(): bool {
echo "Подключение к MySQL: {$this->host}/{$this->database}\n";
try {
$this->connection = new PDO(
"mysql:host={$this->host};dbname={$this->database}",
$this->username,
$this->password,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
echo "✓ MySQL подключен\n";
return true;
} catch (PDOException $e) {
echo "✗ Ошибка подключения: {$e->getMessage()}\n";
return false;
}
}
public function disconnect(): void {
$this->connection = null;
echo "✓ MySQL отключен\n";
}
public function query(string $sql): array {
echo "MySQL Query: {$sql}\n";
$stmt = $this->connection->query($sql);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function execute(string $sql): bool {
echo "MySQL Execute: {$sql}\n";
return $this->connection->exec($sql) !== false;
}
public function lastInsertId(): int {
return (int)$this->connection->lastInsertId();
}
protected function beginTransaction(): void {
$this->connection->beginTransaction();
echo "→ MySQL транзакция начата\n";
}
protected function commit(): void {
$this->connection->commit();
echo "✓ MySQL транзакция завершена\n";
}
protected function rollback(): void {
$this->connection->rollBack();
echo "✗ MySQL транзакция отменена\n";
}
}
// Конкретная реализация для PostgreSQL
class PostgreSQLConnection extends DatabaseConnection {
public function connect(): bool {
echo "Подключение к PostgreSQL: {$this->host}/{$this->database}\n";
try {
$this->connection = new PDO(
"pgsql:host={$this->host};dbname={$this->database}",
$this->username,
$this->password,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
echo "✓ PostgreSQL подключен\n";
return true;
} catch (PDOException $e) {
echo "✗ Ошибка подключения: {$e->getMessage()}\n";
return false;
}
}
public function disconnect(): void {
$this->connection = null;
echo "✓ PostgreSQL отключен\n";
}
public function query(string $sql): array {
echo "PostgreSQL Query: {$sql}\n";
$stmt = $this->connection->query($sql);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function execute(string $sql): bool {
echo "PostgreSQL Execute: {$sql}\n";
return $this->connection->exec($sql) !== false;
}
public function lastInsertId(): int {
// В PostgreSQL используется другой подход
$result = $this->query("SELECT lastval()");
return (int)$result[0]['lastval'];
}
protected function beginTransaction(): void {
$this->connection->beginTransaction();
echo "→ PostgreSQL транзакция начата\n";
}
protected function commit(): void {
$this->connection->commit();
echo "✓ PostgreSQL транзакция завершена\n";
}
protected function rollback(): void {
$this->connection->rollBack();
echo "✗ PostgreSQL транзакция отменена\n";
}
}
// Конкретная реализация для SQLite
class SQLiteConnection extends DatabaseConnection {
public function __construct(string $filepath) {
parent::__construct($filepath, '', '', '');
}
public function connect(): bool {
echo "Подключение к SQLite: {$this->host}\n";
try {
$this->connection = new PDO("sqlite:{$this->host}");
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "✓ SQLite подключен\n";
return true;
} catch (PDOException $e) {
echo "✗ Ошибка подключения: {$e->getMessage()}\n";
return false;
}
}
public function disconnect(): void {
$this->connection = null;
echo "✓ SQLite отключен\n";
}
public function query(string $sql): array {
echo "SQLite Query: {$sql}\n";
$stmt = $this->connection->query($sql);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function execute(string $sql): bool {
echo "SQLite Execute: {$sql}\n";
return $this->connection->exec($sql) !== false;
}
public function lastInsertId(): int {
return (int)$this->connection->lastInsertId();
}
protected function beginTransaction(): void {
$this->connection->beginTransaction();
echo "→ SQLite транзакция начата\n";
}
protected function commit(): void {
$this->connection->commit();
echo "✓ SQLite транзакция завершена\n";
}
protected function rollback(): void {
$this->connection->rollBack();
echo "✗ SQLite транзакция отменена\n";
}
}
// Класс, использующий абстракцию
class UserRepository {
private DatabaseConnection $db;
// Принимаем любую реализацию DatabaseConnection
public function __construct(DatabaseConnection $db) {
$this->db = $db;
}
public function createUser(string $name, string $email): int {
$sql = "INSERT INTO users (name, email) VALUES ('{$name}', '{$email}')";
$this->db->execute($sql);
return $this->db->lastInsertId();
}
public function getUser(int $id): ?array {
$result = $this->db->query("SELECT * FROM users WHERE id = {$id}");
return $result[0] ?? null;
}
public function updateUser(int $id, string $name, string $email): bool {
$sql = "UPDATE users SET name = '{$name}', email = '{$email}' WHERE id = {$id}";
return $this->db->execute($sql);
}
public function deleteUser(int $id): bool {
return $this->db->execute("DELETE FROM users WHERE id = {$id}");
}
}
// Использование
echo "=== Работа с MySQL ===\n";
$mysql = new MySQLConnection("localhost", "mydb", "root", "password");
$mysql->connect();
$userRepo1 = new UserRepository($mysql);
// ... работа с репозиторием
$mysql->disconnect();
echo "\n=== Работа с PostgreSQL ===\n";
$postgres = new PostgreSQLConnection("localhost", "mydb", "user", "password");
$postgres->connect();
$userRepo2 = new UserRepository($postgres);
// ... работа с репозиторием
$postgres->disconnect();
echo "\n=== Работа с SQLite ===\n";
$sqlite = new SQLiteConnection("database.db");
$sqlite->connect();
$userRepo3 = new UserRepository($sqlite);
// ... работа с репозиторием
$sqlite->disconnect();
// Демонстрация транзакций
echo "\n=== Тестирование транзакций ===\n";
$mysql->connect();
$queries = [
"INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')",
"INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com')",
"INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com')"
];
$mysql->executeTransaction($queries);
$mysql->disconnect();
Пример на Go
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
// DatabaseConnection - интерфейс для работы с БД (абстракция)
type DatabaseConnection interface {
Connect() error
Disconnect() error
Query(sql string) ([]map[string]interface{}, error)
Execute(sql string) error
LastInsertID() (int64, error)
IsConnected() bool
// Транзакции
BeginTransaction() error
Commit() error
Rollback() error
}
// BaseConnection - базовая структура с общей функциональностью
type BaseConnection struct {
host string
database string
username string
password string
connection *sql.DB
tx *sql.Tx
}
func (b *BaseConnection) IsConnected() bool {
return b.connection != nil
}
// ExecuteTransaction - шаблонный метод для транзакций
func ExecuteTransaction(db DatabaseConnection, queries []string) error {
if err := db.BeginTransaction(); err != nil {
return err
}
for _, query := range queries {
if err := db.Execute(query); err != nil {
db.Rollback()
return err
}
}
return db.Commit()
}
// MySQLConnection - реализация для MySQL
type MySQLConnection struct {
BaseConnection
}
func NewMySQLConnection(host, database, username, password string) *MySQLConnection {
return &MySQLConnection{
BaseConnection: BaseConnection{
host: host,
database: database,
username: username,
password: password,
},
}
}
func (m *MySQLConnection) Connect() error {
fmt.Printf("Подключение к MySQL: %s/%s\n", m.host, m.database)
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", m.username, m.password, m.host, m.database)
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Printf("✗ Ошибка подключения: %v\n", err)
return err
}
m.connection = db
fmt.Println("✓ MySQL подключен")
return nil
}
func (m *MySQLConnection) Disconnect() error {
if m.connection != nil {
err := m.connection.Close()
m.connection = nil
fmt.Println("✓ MySQL отключен")
return err
}
return nil
}
func (m *MySQLConnection) Query(sqlQuery string) ([]map[string]interface{}, error) {
fmt.Printf("MySQL Query: %s\n", sqlQuery)
rows, err := m.connection.Query(sqlQuery)
if err != nil {
return nil, err
}
defer rows.Close()
return rowsToMaps(rows)
}
func (m *MySQLConnection) Execute(sqlQuery string) error {
fmt.Printf("MySQL Execute: %s\n", sqlQuery)
var err error
if m.tx != nil {
_, err = m.tx.Exec(sqlQuery)
} else {
_, err = m.connection.Exec(sqlQuery)
}
return err
}
func (m *MySQLConnection) LastInsertID() (int64, error) {
var id int64
err := m.connection.QueryRow("SELECT LAST_INSERT_ID()").Scan(&id)
return id, err
}
func (m *MySQLConnection) BeginTransaction() error {
tx, err := m.connection.Begin()
if err != nil {
return err
}
m.tx = tx
fmt.Println("→ MySQL транзакция начата")
return nil
}
func (m *MySQLConnection) Commit() error {
if m.tx == nil {
return fmt.Errorf("нет активной транзакции")
}
err := m.tx.Commit()
m.tx = nil
fmt.Println("✓ MySQL транзакция завершена")
return err
}
func (m *MySQLConnection) Rollback() error {
if m.tx == nil {
return fmt.Errorf("нет активной транзакции")
}
err := m.tx.Rollback()
m.tx = nil
fmt.Println("✗ MySQL транзакция отменена")
return err
}
// SQLiteConnection - реализация для SQLite
type SQLiteConnection struct {
BaseConnection
}
func NewSQLiteConnection(filepath string) *SQLiteConnection {
return &SQLiteConnection{
BaseConnection: BaseConnection{
host: filepath,
},
}
}
func (s *SQLiteConnection) Connect() error {
fmt.Printf("Подключение к SQLite: %s\n", s.host)
db, err := sql.Open("sqlite3", s.host)
if err != nil {
fmt.Printf("✗ Ошибка подключения: %v\n", err)
return err
}
s.connection = db
fmt.Println("✓ SQLite подключен")
return nil
}
func (s *SQLiteConnection) Disconnect() error {
if s.connection != nil {
err := s.connection.Close()
s.connection = nil
fmt.Println("✓ SQLite отключен")
return err
}
return nil
}
func (s *SQLiteConnection) Query(sqlQuery string) ([]map[string]interface{}, error) {
fmt.Printf("SQLite Query: %s\n", sqlQuery)
rows, err := s.connection.Query(sqlQuery)
if err != nil {
return nil, err
}
defer rows.Close()
return rowsToMaps(rows)
}
func (s *SQLiteConnection) Execute(sqlQuery string) error {
fmt.Printf("SQLite Execute: %s\n", sqlQuery)
var err error
if s.tx != nil {
_, err = s.tx.Exec(sqlQuery)
} else {
_, err = s.connection.Exec(sqlQuery)
}
return err
}
func (s *SQLiteConnection) LastInsertID() (int64, error) {
var id int64
err := s.connection.QueryRow("SELECT last_insert_rowid()").Scan(&id)
return id, err
}
func (s *SQLiteConnection) BeginTransaction() error {
tx, err := s.connection.Begin()
if err != nil {
return err
}
s.tx = tx
fmt.Println("→ SQLite транзакция начата")
return nil
}
func (s *SQLiteConnection) Commit() error {
if s.tx == nil {
return fmt.Errorf("нет активной транзакции")
}
err := s.tx.Commit()
s.tx = nil
fmt.Println("✓ SQLite транзакция завершена")
return err
}
func (s *SQLiteConnection) Rollback() error {
if s.tx == nil {
return fmt.Errorf("нет активной транзакции")
}
err := s.tx.Rollback()
s.tx = nil
fmt.Println("✗ SQLite транзакция отменена")
return err
}
// Вспомогательная функция для конвертации строк в map
func rowsToMaps(rows *sql.Rows) ([]map[string]interface{}, error) {
columns, err := rows.Columns()
if err != nil {
return nil, err
}
var results []map[string]interface{}
for rows.Next() {
values := make([]interface{}, len(columns))
valuePtrs := make([]interface{}, len(columns))
for i := range columns {
valuePtrs[i] = &values[i]
}
if err := rows.Scan(valuePtrs...); err != nil {
return nil, err
}
row := make(map[string]interface{})
for i, col := range columns {
row[col] = values[i]
}
results = append(results, row)
}
return results, nil
}
// UserRepository - репозиторий, использующий абстракцию
type UserRepository struct {
db DatabaseConnection
}
func NewUserRepository(db DatabaseConnection) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) CreateUser(name, email string) (int64, error) {
sql := fmt.Sprintf("INSERT INTO users (name, email) VALUES ('%s', '%s')", name, email)
if err := r.db.Execute(sql); err != nil {
return 0, err
}
return r.db.LastInsertID()
}
func (r *UserRepository) GetUser(id int64) (map[string]interface{}, error) {
sql := fmt.Sprintf("SELECT * FROM users WHERE id = %d", id)
results, err := r.db.Query(sql)
if err != nil {
return nil, err
}
if len(results) == 0 {
return nil, fmt.Errorf("пользователь не найден")
}
return results[0], nil
}
func (r *UserRepository) UpdateUser(id int64, name, email string) error {
sql := fmt.Sprintf("UPDATE users SET name = '%s', email = '%s' WHERE id = %d", name, email, id)
return r.db.Execute(sql)
}
func (r *UserRepository) DeleteUser(id int64) error {
sql := fmt.Sprintf("DELETE FROM users WHERE id = %d", id)
return r.db.Execute(sql)
}
func main() {
// Демонстрация работы с SQLite
fmt.Println("=== Работа с SQLite ===")
sqlite := NewSQLiteConnection(":memory:")
if err := sqlite.Connect(); err != nil {
log.Fatal(err)
}
defer sqlite.Disconnect()
// Создаем таблицу
sqlite.Execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
// Используем репозиторий
userRepo := NewUserRepository(sqlite)
// Создаем пользователей
id1, _ := userRepo.CreateUser("Alice", "alice@example.com")
fmt.Printf("Создан пользователь с ID: %d\n", id1)
id2, _ := userRepo.CreateUser("Bob", "bob@example.com")
fmt.Printf("Создан пользователь с ID: %d\n", id2)
// Получаем пользователя
user, _ := userRepo.GetUser(id1)
fmt.Printf("Получен пользователь: %v\n", user)
// Обновляем пользователя
userRepo.UpdateUser(id1, "Alice Smith", "alice.smith@example.com")
fmt.Println("Пользователь обновлен")
fmt.Println("\n=== Тестирование транзакций ===")
queries := []string{
"INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com')",
"INSERT INTO users (name, email) VALUES ('David', 'david@example.com')",
"INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com')",
}
```go
if err := ExecuteTransaction(sqlite, queries); err != nil {
log.Fatal(err)
}
// Проверяем результаты
allUsers, _ := sqlite.Query("SELECT * FROM users")
fmt.Printf("\nВсего пользователей в базе: %d\n", len(allUsers))
for _, user := range allUsers {
fmt.Printf(" - ID: %v, Name: %v, Email: %v\n", user["id"], user["name"], user["email"])
}
}
Преимущества абстракции:
- Можно легко менять реализацию БД без изменения бизнес-логики
- Упрощает тестирование (можно создать mock-реализацию)
- Код становится более модульным и понятным
- Изменения в одной реализации не влияют на другие
Взаимосвязь принципов ООП
Четыре принципа ООП не существуют изолированно — они дополняют друг друга:
Как они работают вместе
<?php
// АБСТРАКЦИЯ - определяем контракт
interface Logger {
public function log(string $message, string $level): void;
}
// ИНКАПСУЛЯЦИЯ - скрываем детали реализации
class FileLogger implements Logger {
private string $filepath;
private $fileHandle;
public function __construct(string $filepath) {
$this->filepath = $filepath;
$this->openFile(); // Приватная логика
}
// Приватный метод - детали скрыты
private function openFile(): void {
$this->fileHandle = fopen($this->filepath, 'a');
}
private function formatMessage(string $message, string $level): string {
$timestamp = date('Y-m-d H:i:s');
return "[{$timestamp}] [{$level}] {$message}\n";
}
// Публичный интерфейс
public function log(string $message, string $level = 'INFO'): void {
$formatted = $this->formatMessage($message, $level);
fwrite($this->fileHandle, $formatted);
}
public function __destruct() {
if ($this->fileHandle) {
fclose($this->fileHandle);
}
}
}
// НАСЛЕДОВАНИЕ и ПОЛИМОРФИЗМ
class DatabaseLogger implements Logger {
private PDO $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
// Та же сигнатура, но другая реализация
public function log(string $message, string $level = 'INFO'): void {
$sql = "INSERT INTO logs (message, level, created_at) VALUES (?, ?, NOW())";
$stmt = $this->connection->prepare($sql);
$stmt->execute([$message, $level]);
}
}
class ConsoleLogger implements Logger {
private array $colors = [
'INFO' => "\033[0;32m", // зеленый
'WARNING' => "\033[0;33m", // желтый
'ERROR' => "\033[0;31m", // красный
'RESET' => "\033[0m"
];
public function log(string $message, string $level = 'INFO'): void {
$color = $this->colors[$level] ?? $this->colors['INFO'];
$reset = $this->colors['RESET'];
echo "{$color}[{$level}] {$message}{$reset}\n";
}
}
// ПОЛИМОРФИЗМ - одна функция работает с разными реализациями
class Application {
private Logger $logger;
public function __construct(Logger $logger) {
$this->logger = $logger;
}
public function run(): void {
$this->logger->log("Приложение запущено", "INFO");
try {
$this->doSomething();
} catch (Exception $e) {
$this->logger->log("Ошибка: " . $e->getMessage(), "ERROR");
}
$this->logger->log("Приложение завершено", "INFO");
}
private function doSomething(): void {
$this->logger->log("Выполняем операцию...", "INFO");
// ... логика
}
}
// Использование - легко меняем реализацию
$fileLogger = new FileLogger('app.log');
$app1 = new Application($fileLogger);
$app1->run();
$consoleLogger = new ConsoleLogger();
$app2 = new Application($consoleLogger);
$app2->run();
// $dbLogger = new DatabaseLogger($pdo);
// $app3 = new Application($dbLogger);
// $app3->run();
Практические примеры: Реальные задачи
Пример 1: Система уведомлений
<?php
// Абстракция
interface NotificationChannel {
public function send(string $recipient, string $message): bool;
public function supports(string $type): bool;
}
// Инкапсуляция
class EmailNotification implements NotificationChannel {
private string $smtpHost;
private int $smtpPort;
private string $username;
private string $password;
public function __construct(string $host, int $port, string $user, string $pass) {
$this->smtpHost = $host;
$this->smtpPort = $port;
$this->username = $user;
$this->password = $pass;
}
public function send(string $recipient, string $message): bool {
echo "Отправка email на {$recipient}\n";
echo "Сообщение: {$message}\n";
// Логика отправки через SMTP
return true;
}
public function supports(string $type): bool {
return $type === 'email';
}
private function connectSMTP(): void {
// Приватная логика подключения
}
}
class SMSNotification implements NotificationChannel {
private string $apiKey;
private string $provider;
public function __construct(string $apiKey, string $provider = 'twilio') {
$this->apiKey = $apiKey;
$this->provider = $provider;
}
public function send(string $recipient, string $message): bool {
echo "Отправка SMS на {$recipient} через {$this->provider}\n";
echo "Сообщение: {$message}\n";
// Логика отправки через API
return true;
}
public function supports(string $type): bool {
return $type === 'sms';
}
}
class PushNotification implements NotificationChannel {
private string $fcmToken;
public function __construct(string $token) {
$this->fcmToken = $token;
}
public function send(string $recipient, string $message): bool {
echo "Отправка Push-уведомления на устройство {$recipient}\n";
echo "Сообщение: {$message}\n";
// Логика отправки через FCM/APNs
return true;
}
public function supports(string $type): bool {
return $type === 'push';
}
}
// Полиморфизм - единый интерфейс для всех каналов
class NotificationService {
private array $channels = [];
public function addChannel(NotificationChannel $channel): void {
$this->channels[] = $channel;
}
public function notify(string $recipient, string $message, string $type): bool {
foreach ($this->channels as $channel) {
if ($channel->supports($type)) {
return $channel->send($recipient, $message);
}
}
throw new Exception("Канал для типа '{$type}' не найден");
}
public function notifyAll(string $recipient, string $message): void {
foreach ($this->channels as $channel) {
$channel->send($recipient, $message);
}
}
}
// Использование
$notificationService = new NotificationService();
$notificationService->addChannel(new EmailNotification('smtp.gmail.com', 587, 'user', 'pass'));
$notificationService->addChannel(new SMSNotification('api-key-123'));
$notificationService->addChannel(new PushNotification('fcm-token-456'));
// Отправляем через конкретный канал
$notificationService->notify('user@example.com', 'Ваш заказ готов!', 'email');
$notificationService->notify('+79001234567', 'Код подтверждения: 1234', 'sms');
// Отправляем через все каналы
$notificationService->notifyAll('user@example.com', 'Важное уведомление!');
Пример 2: Обработка платежей (расширенный)
<?php
abstract class PaymentGateway {
protected float $processingFee = 0;
protected string $currency = 'RUB';
// Шаблонный метод - определяет алгоритм
final public function process(float $amount, array $details): array {
$this->validate($amount, $details);
$this->beforePayment($amount);
$result = $this->executePayment($amount, $details);
if ($result['success']) {
$this->afterSuccessfulPayment($amount, $result);
} else {
$this->afterFailedPayment($amount, $result);
}
return $result;
}
// Абстрактные методы - реализуются наследниками
abstract protected function executePayment(float $amount, array $details): array;
abstract protected function validate(float $amount, array $details): void;
// Хуки - можно переопределить в наследниках
protected function beforePayment(float $amount): void {
echo "Подготовка к платежу на сумму {$amount} {$this->currency}\n";
}
protected function afterSuccessfulPayment(float $amount, array $result): void {
echo "✓ Платеж успешно проведен. ID транзакции: {$result['transaction_id']}\n";
$this->sendReceipt($result);
}
protected function afterFailedPayment(float $amount, array $result): void {
echo "✗ Платеж отклонен: {$result['error']}\n";
$this->notifyAdmin($result);
}
protected function sendReceipt(array $result): void {
echo "Отправка чека на email...\n";
}
protected function notifyAdmin(array $result): void {
echo "Уведомление администратора об ошибке...\n";
}
public function calculateTotal(float $amount): float {
return $amount + ($amount * $this->processingFee);
}
}
class StripePayment extends PaymentGateway {
protected float $processingFee = 0.029; // 2.9%
private string $apiKey;
public function __construct(string $apiKey) {
$this->apiKey = $apiKey;
}
protected function validate(float $amount, array $details): void {
if ($amount < 0.5) {
throw new Exception("Минимальная сумма для Stripe: 0.5 USD");
}
if (empty($details['card_token'])) {
throw new Exception("Не указан токен карты");
}
}
protected function executePayment(float $amount, array $details): array {
echo "→ Обращение к Stripe API...\n";
sleep(1); // Имитация запроса
// Имитация успешного платежа
return [
'success' => true,
'transaction_id' => 'stripe_' . uniqid(),
'amount' => $amount,
'fee' => $amount * $this->processingFee,
'net_amount' => $amount - ($amount * $this->processingFee)
];
}
}
class PayPalPayment extends PaymentGateway {
protected float $processingFee = 0.034; // 3.4%
private string $clientId;
private string $clientSecret;
public function __construct(string $clientId, string $clientSecret) {
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
}
protected function validate(float $amount, array $details): void {
if ($amount < 1) {
throw new Exception("Минимальная сумма для PayPal: 1 USD");
}
if (empty($details['payer_email'])) {
throw new Exception("Не указан email плательщика");
}
}
protected function executePayment(float $amount, array $details): array {
echo "→ Обращение к PayPal API...\n";
sleep(1);
return [
'success' => true,
'transaction_id' => 'paypal_' . uniqid(),
'amount' => $amount,
'fee' => $amount * $this->processingFee + 10,
'payer_email' => $details['payer_email']
];
}
// Переопределяем хук
protected function afterSuccessfulPayment(float $amount, array $result): void {
parent::afterSuccessfulPayment($amount, $result);
echo "Отправка уведомления PayPal на {$result['payer_email']}\n";
}
}
class RobokassaPayment extends PaymentGateway {
protected float $processingFee = 0.05; // 5%
protected string $currency = 'RUB';
private string $merchantId;
private string $secretKey;
public function __construct(string $merchantId, string $secretKey) {
$this->merchantId = $merchantId;
$this->secretKey = $secretKey;
}
protected function validate(float $amount, array $details): void {
if ($amount < 10) {
throw new Exception("Минимальная сумма для Робокассы: 10 RUB");
}
}
protected function executePayment(float $amount, array $details): array {
echo "→ Перенаправление на страницу Робокассы...\n";
sleep(2);
$signature = md5("{$this->merchantId}:{$amount}:{$this->secretKey}");
return [
'success' => true,
'transaction_id' => 'robokassa_' . uniqid(),
'amount' => $amount,
'fee' => $amount * $this->processingFee,
'signature' => $signature
];
}
}
// Использование
echo "=== Платеж через Stripe ===\n";
$stripe = new StripePayment('sk_test_123456');
$result1 = $stripe->process(100, ['card_token' => 'tok_visa']);
print_r($result1);
echo "\n=== Платеж через PayPal ===\n";
$paypal = new PayPalPayment('client_id', 'client_secret');
$result2 = $paypal->process(100, ['payer_email' => 'user@example.com']);
print_r($result2);
echo "\n=== Платеж через Робокассу ===\n";
$robokassa = new RobokassaPayment('merchant_123', 'secret_key');
$result3 = $robokassa->process(5000, []);
print_r($result3);
Антипаттерны ООП: Чего избегать
1. God Object (Божественный объект)
Плохо:
class Application {
public function connectDatabase() { }
public function sendEmail() { }
public function processPayment() { }
public function generateReport() { }
public function uploadFile() { }
public function resizeImage() { }
public function sendSMS() { }
// ... еще 50 методов
}
Хорошо:
class Database { }
class Mailer { }
class PaymentProcessor { }
class ReportGenerator { }
class FileUploader { }
class ImageProcessor { }
class SMSService { }
class Application {
private Database $db;
private Mailer $mailer;
private PaymentProcessor $payments;
// ... композиция вместо одного большого класса
}
2. Нарушение инкапсуляции
Плохо:
class User {
public $password; // Публичный пароль!
public $balance; // Публичный баланс!
}
$user = new User();
$user->balance = 1000000; // Можно изменить как угодно
Хорошо:
class User {
private string $passwordHash;
private float $balance;
public function setPassword(string $password): void {
$this->passwordHash = password_hash($password, PASSWORD_BCRYPT);
}
public function addFunds(float $amount): void {
if ($amount > 0) {
$this->balance += $amount;
}
}
public function getBalance(): float {
return $this->balance;
}
}
3. Чрезмерное наследование
Плохо:
class Animal { }
class Mammal extends Animal { }
class Carnivore extends Mammal { }
class Feline extends Carnivore { }
class Cat extends Feline { }
class PersianCat extends Cat { }
class LongHairPersianCat extends PersianCat { }
// Слишком глубокая иерархия!
Хорошо:
class Animal { }
class Cat extends Animal {
private Breed $breed;
private FurType $furType;
// Композиция вместо глубокого наследования
}
Краткая шпаргалка
Инкапсуляция
// Скрываем данные, предоставляем методы
class BankAccount {
private float $balance;
public function deposit(float $amount) { }
public function getBalance(): float { }
}
Наследование
// Базовый класс
abstract class Vehicle {
abstract public function move();
}
// Наследник
class Car extends Vehicle {
public function move() {
echo "Еду по дороге";
}
}
Полиморфизм
// Один интерфейс - разные реализации
interface Shape {
public function calculateArea(): float;
}
class Circle implements Shape {
public function calculateArea(): float {
return pi() * $this->radius ** 2;
}
}
class Rectangle implements Shape {
public function calculateArea(): float {
return $this->width * $this->height;
}
}
// Полиморфное использование
function printArea(Shape $shape) {
echo $shape->calculateArea();
}
Абстракция
// Абстрактный класс с общим интерфейсом
abstract class DataStorage {
abstract public function save(string $key, $value): void;
abstract public function load(string $key);
// Общая логика для всех хранилищ
public function exists(string $key): bool {
return $this->load($key) !== null;
}
}
class FileStorage extends DataStorage { }
class DatabaseStorage extends DataStorage { }
class RedisStorage extends DataStorage { }
Когда использовать ООП?
ООП подходит для:
✅ Больших и сложных приложений - легче организовать код ✅ Проектов с командной разработкой - понятная структура ✅ Приложений с много повторяющейся логикой - переиспользование кода ✅ Систем, требующих расширяемости - легко добавлять новые функции ✅ Enterprise-приложений - стандартизированный подход
ООП может быть избыточным для:
❌ Простых скриптов - процедурный код проще ❌ Прототипов и MVP - можно начать проще ❌ Высоконагруженных систем - ООП может добавлять overhead ❌ Функциональных задач - лучше подходит ФП
Заключение
Четыре принципа ООП — это фундамент, на котором строится качественный, поддерживаемый и масштабируемый код:
- Инкапсуляция — защищает данные и скрывает сложность
- Наследование — позволяет переиспользовать код и создавать иерархии
- Полиморфизм — обеспечивает гибкость через единый интерфейс
- Абстракция — упрощает сложные системы через абстрактные контракты
Ключевые выводы:
- Используйте инкапсуляцию для защиты данных и контроля доступа
- Применяйте наследование разумно, избегайте глубоких иерархий
- Полиморфизм через интерфейсы лучше, чем через наследование
- Абстракция помогает думать о системе на высоком уровне
- Все принципы работают вместе для создания качественного кода
Помните: ООП — это не цель, а инструмент. Используйте его там, где он действительно упрощает задачу, а не усложняет её.
Хотите углубиться в программирование? Читайте наши статьи о программировании и архитектуре.