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()).