Не создавай глупые интефейсы, наследуйся от класса

Краткая мысль: не надо создавать интерфейсы под классы. Это классы должны создаваться под интерфейсы. Если кол-во публичных методов класса = кол-ву методов в интерфейсе, и так будет в течение всей жизни этого класса, то этот интерфейс избыточен. С такми глупыми интерфейсами вы не создаете более гибкий код, а просто засоряете кодовую базу мусорным слоем абстракции.

Определять зависимость от конкретного класса само по себе не нарушает Open-Closed принцип, если мы и так знаем, что у класса нет "братьев".

Конкретный пример, который я вижу из проекта в проект, чаще на Laravel. Есть 100 классов репозиториев (которые сами по себе так себе "хороший код", но об этом потом). И есть 100 интерфейсов этих репозиториев.

<?php
class CityRepository implements CityRepositoryInterface
{
    public function create(array $fields)
    {
        return City::query()->create($fields);
    }

    public function update(City $city, array $fields)
    {
        $city->update($fields);

        return $city;
    }
<?php
interface CityRepositoryInterface
{
    public function create(array $fields)
    public function update(City $city, array $fields)
}

Зачем это сделано? А чтобы, мы умные когда в контроллере зависимость вызывали, писали зависимость от интерфейса, а не от реализации. Гибкость.

И вот нужно добавить прогеру функционал удаления. Идет в класс, пишет метод delete, идет в интерфейс, добавляет метод delete. И так из раза в раз.

И получается тут "инверсия зависимости" курильщика. Класс определяет содержимое интерфейса. Интерфейс просто не может содержать меньше публичных полей, чем его единственная реализация.

В целом уже по названию понятно, что интефейс глупый. CityRepositoryInterface нам прямо говорит: "Я интерфейс класса CityRepository"

В таких случаях можно спокойно удалить эти 100 интерфейсов и прописать зависимости от классов. У таких интерфейсов не будет других имплементаторов, кроме одного конкретного класса.

Можно представлять, что если у класса нет implements, значит у него есть интерфейс "абстрактный", который равен всем публичным методам этого класса.

И напоследок, моя личная боль. Не пытайтесь строить == АРХИТЕКТУРУ == из нереалистичных предпосылок. Лично я слышал аргумент на это "а вдруг мы будем переезжать на другую базу". Кажется это пример из книги Uncle Bob'а? И кажется это то, что крайне редко происходит в реальности, ведь так? Не будете вы переезжать на новую базу. А если такое маловероятное событие случится, то:

  1. берешь и пишешь генерацию интерфейсов на основе содержимого классов (Reflection API в помощь) и делаешь автозамену по всему коду
  2. тебе это все-равно не поможет, т.к. в реальности переезд на новую БД эта та еще боль, и "стандартизированный" sql вместе с "абстрактным" ORM заставят не раз поплакать