Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «ООП»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

Інтерфейс - це лише контракт: перелік публічних методів, які клас зобов'язується мати. Реалізації в ньому немає (крім констант), і клас може реалізувати скільки завгодно інтерфейсів.

Абстрактний клас - це недобудований клас: у ньому можуть бути властивості, готові методи й абстрактні методи, які мають дописати нащадки. Успадкувати можна лише один клас.

interface PaymentGateway
{
    public function charge(Money $amount): Receipt;
}

abstract class HttpGateway implements PaymentGateway
{
    public function __construct(protected HttpClient $http) {}

    protected function post(string $path, array $data): array
    {
        return $this->http->post($this->baseUrl() . $path, $data);
    }

    abstract protected function baseUrl(): string;
}

Коли що:

  • Інтерфейс - коли потрібна взаємозамінність: код залежить від PaymentGateway, а конкретний шлюз підставляється ззовні.
  • Абстрактний клас - коли кілька реалізацій ділять спільний код і стан.

Часто вони йдуть разом, як у прикладі: залежать від інтерфейсу, а абстрактний клас лише прибирає повтор між реалізаціями.

Докладніше в документації: Інтерфейси об'єктів

Це модифікатори видимості - хто може звертатися до властивості чи методу:

  • public - будь-хто, зокрема код поза класом.
  • protected - сам клас і його нащадки.
  • private - лише сам клас, де член оголошено. Нащадок його не бачить.
class Account
{
    public function __construct(private int $balance = 0) {}

    public function deposit(int $amount): void
    {
        $this->guard($amount);
        $this->balance += $amount;
    }

    protected function guard(int $amount): void
    {
        if ($amount <= 0) {
            throw new InvalidArgumentException('Сума має бути додатною');
        }
    }
}

Тут баланс не можна змінити напряму ззовні - лише через deposit(), який перевіряє суму. У цьому й сенс інкапсуляції: клас сам стежить за тим, щоб його стан лишався коректним.

Правило за замовчуванням: усе private, доки не з'явиться причина відкрити більше. Відкриту властивість потім важко закрити - на неї вже покладається чужий код.

У PHP 8.4 з'явилася асиметрична видимість: public private(set) int $balance - читати можна звідусіль, а змінювати лише всередині класу.

Докладніше в документації: Видимість

Просування властивостей (PHP 8.0) дозволяє оголосити властивість і присвоїти їй значення прямо в параметрах конструктора - додаючи модифікатор видимості.

// До PHP 8
class Invoice
{
    private string $number;
    private int $total;

    public function __construct(string $number, int $total)
    {
        $this->number = $number;
        $this->total = $total;
    }
}

// PHP 8+
class Invoice
{
    public function __construct(
        private string $number,
        private int $total,
    ) {}
}

Обидва варіанти створюють однакові властивості. Друге - втричі коротше, і неможливо забути присвоєння.

Що варто знати:

  • Просувається лише параметр з модифікатором (public, protected, private, readonly). Звичайні параметри поруч лишаються просто параметрами.
  • Можна поєднувати з readonly: public function __construct(public readonly string $email) {} - незмінний value object у кілька рядків.
  • Тип обов'язковий не формально, але без нього властивість буде нетипізованою - на практиці тип пишуть завжди.
  • Значення за замовчуванням дозволені: private int $attempts = 3.
  • Тіло конструктора може бути й не порожнім - там зручно перевіряти інваріанти:
public function __construct(public readonly int $amount)
{
    if ($amount < 0) {
        throw new InvalidArgumentException('Сума не може бути від\'ємною');
    }
}

У Laravel так оголошують залежності сервісів і контролерів: контейнер підставляє їх через параметри конструктора.

Докладніше в документації: Конструктори й деструктори

Звичайний метод працює з конкретним об'єктом і має доступ до $this - його стану.

Статичний метод належить класу, а не об'єкту: викликається без створення екземпляра (Money::fromCents(100)), і $this у ньому немає.

final class Money
{
    private function __construct(private int $cents) {}

    public static function fromCents(int $cents): self   // статичний: створює об'єкт
    {
        return new self($cents);
    }

    public function add(Money $other): self               // звичайний: працює зі станом
    {
        return new self($this->cents + $other->cents);
    }
}

Коли static доречний:

  • Іменовані конструктори й фабричні методи: Money::fromCents(), Carbon::parse().
  • Чисті допоміжні функції без стану, логічно прив'язані до класу: Str::slug().
  • Константи й кеш на рівні класу - обережно (див. нижче).

Чому зі статикою обережні:

  • Важко підмінити в тестах. Код, що викликає PaymentGateway::charge(), жорстко прив'язаний до класу; залежність, передана в конструктор, легко замінюється на фейк.
  • Статичні властивості - глобальний стан. Вони живуть увесь процес: у довгоживучих воркерах і Octane значення «перетікає» між запитами.
  • Приховані залежності: зі сигнатури класу не видно, що він користується статикою іншого класу.

Фасади Laravel (Cache::get()) лише виглядають статичними: насправді це виклик методу об'єкта з контейнера, і в тестах його можна підмінити (Cache::fake(), Cache::shouldReceive()).

Докладніше в документації: Ключове слово static