я понял, наконец

fixxxer

К.О.
Партнер клуба
ошибка копипасты, сэр!
Разница в том что пример неправильный.
Должно быть
PHP:
interface LoggerInterface { // контракт наружу
    public function logError(string $message): void;
}
abstract class Logger { // прототип внутрь
    abstract public function logError(string $message): void;
}
class SyslogLogger extends Logger implements LoggerInterface{
    public function logError(string $message): void {
        syslog(LOG_ERR, $message);
    }
}
Ну то есть тут, на самом деле, должно быть так?
PHP:
interface LoggerInterface { // контракт наружу
    public function logError(string $message): void;
}
abstract class Logger implements LoggerInterface { // прототип внутрь
}
class SyslogLogger extends Logger {
    public function logError(string $message): void {
        syslog(LOG_ERR, $message);
    }
}
 

fixxxer

К.О.
Партнер клуба
ничего абстрактного в abstract class Logger я лично не увидел
Абстракция, конечно же, будет видна при соблюдении принципа DIP у пользователей логгера:

PHP:
class SomeService {
    private $logger;
    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
    public function doSmth() {
       $this->logger->logError('We failed');
    }
}
 

fixxxer

К.О.
Партнер клуба
я о том что там нет абстрактных методов
PHP:
public function __construct(Logger $logger)
с таким же успехом может быть обявлен как
PHP:
public function __construct(LoggerInterface $logger)
Эээ, я специально не уточнил, что такое Logger - абстрактный класс или интерфейс.
Погоди, не мешай! ;)
 

Фанат

oncle terrible
Команда форума
А тебе не кажется это избыточным? Зачем вообще вводить понятие интерфейса, если абстрактный класс прекрасно справляется с обеими ролями?
Ну я жеуже отвечал на этот вопрос Флопу
И код написал.
Ну вот ещё
Код:
interface LoggerInterface { // для внешних клиентов
    public function logError(string $message): void;
}
abstract class Logger implements LoggerInterface { // для потомков
    public function logError(string $message): void {
        try {
            fwrite($this->destination, $message);
        } catch (\Throwable $e) {
            $this->fallback($message, $e);
        }
    }
    protected function fallback($message, $e) {
        error_log("Looger service failed: ". $e);
        error_log($message);
    }
}
class ScreenLogger extends Logger  {
    public function __construct() {
        $this->destination = fopen("php://stdout", 'w');
    }
}
 

fixxxer

К.О.
Партнер клуба
Тут, к сожалению, нет ответа на вопрос.
Ты добавил protected метод, который для потомков и так - просто потому что он protected. А для внешних клиентов контракт - это совокупность public-методов. Если убрать интерфейс и использовать в его качестве абстрактный класс, что по сути-то изменится?

...окей, спрошу в лоб - почему бы из PHP вообще не убрать интерфейсы? Зачем они понадобились? Не, ну в PHP понятно - просто скопировали с Java. А вот в Java зачем их придумали? В С++ не было же.
 

Фанат

oncle terrible
Команда форума
Зачем они понадобились?
Чтобы не светить кишки наружу. Абстрактный класс - это реализация. А интерфейс обеспечивает инкапсуляцию. Это способ опубликовать контркат но скрыть реализацию
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Чтобы не светить кишки наружу. Абстрактный класс - это реализация. А интерфейс обеспечивает инкапсуляцию. Это способ опубликовать контркат но скрыть реализацию
Нет, класс тоже может обеспечивать инкапсуляцию. Инкапсуляция — это, если упрощать, невозможность напрямую влиять на данные: они меняются самим классом, который содержит логику управления ими.

Что до сокрытия, то задумайся над тем, что реализация нативных классов в PHP тоже тебе не видна.

Принципиальное отличие лишь в том, что класс — это когда не предполагается другой реализации. Например, DateTime-объект с Григорианским календарем. Реализации отличаться не будут и не должны, поэтому не имеет никакого смысла делать это интерфейсом.
 

fixxxer

К.О.
Партнер клуба
Так где же они светятся? Вот есть пользователь:

PHP:
class SomeService {
    private $logger;
    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
    public function doSmth() {
        $this->logger->logError('We failed');
    }
}
Все, что он знает, что есть контракт Logger, у которого есть метод с сигнатурой logError(string): void. Какая разница, каким образом этот контракт определен? Об этом можно вообще не задумываться, вот мы заимпортили Logger, мы даже можем не знать, класс это или интерфейс, какая разница?

Если тебя смущает, что ты в PhpStorm можешь тыкнуть и перейти к реализации, давай возьмем Java, в которой у нас вообще может не быть исходного кода, а только скомпилированный jar библиотеки. Или, допустим, представим себе, что класс реализован не на PHP, а сишным расширением (типа того же PDO). Концептуально это этого ничего не меняется. Ну и, да, это все не отвечает на вопрос "зачем интерфейсы в Java" (где исходного кода мы можем и не иметь).
 

fixxxer

К.О.
Партнер клуба
Не совсем для множественности, но косвенно - да.

Если посмотреть классическую литературу по ООП в Java 90-х годов, то все дело в том, какое отношение мы моделируем. (Пытался сейчас найти хороший пример, но то, что сходу гуглится, больше запутывает, чем объясняет, так что обойдусь своими словами).

Отношение между абстрактным классом и его наследником - это отношение вида "strong is-a", определяющее некую суть, основное назначение. SyslogLogger is a Logger. MysqlDriver is a DatabaseDriver.

Отношение между интерфейсом и его реализациями - это отношение вида "can" или "weak is-a" (а скорее даже is ..., без артикля), или, там, "capable of", тут речь идет о том, что "вот это умеет то-то", ну или "поддерживает протокол", либо "является чем-то, но это не основное назначение". ArrayObject is Serializable - можно получить сериализованное представление штуки, ничего не зная о том, что это за штука. Address is Equatable - штука умеет себя сравнивать с другой штукой, при этом для сравнения двух штук нам не надо ничего знать про них. А раз эти "штуки" прям совсем разные, то никакой там общей реализации и не может быть**.

При чем тут множественность? Логика была такая. Основное назначение одно, потому множественное наследование от разных абстрактных классов ни к чему. А вот "уметь что-то" можно в любых комбинациях.

И, да, по этой логике многое из того, что сейчас принято моделировать интерфейсом, должно бы, по-хорошему, моделироваться абстрактным классом. Поскольку в PHP (или Java)* ничто не мешает использовать интерфейс вместо pure abstract класса, сложилась такая практика, что если нет (частичной) реализации, то делается не pure abstract class, а интерфейс.

*А вот, скажем, в Typescript разница принципиальная - интерфейс существует только в момент компиляции и отсутствует в рантайме, потому использовать его для конфигурации dependency injection контейнера не получится, и там само устройство компилятора навязывает классический подход.

**Появление default methods в интерфейсах Java явно свидетельствует о том, что эта логика не без изъянов, ога.
 
Последнее редактирование:

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Есть у меня идея.
Сложность в том, что мы думаем о себе и о своем коде, а интерфейс или абстрактный класс пишется не для себя и не для компьютера. Для программирования задачи они вообще не нужны.
Это правила взаимодействия с другими людьми, как пропуска в метро.
Все примеры показывают как писать классы и интерфейсы, но мало кто объясняет как их надо понимать.

Когда я открываю чужую библиотеку и вижу у параметра тип Psr\Log\LoggerInterface - я понимаю, что автор обо мне заботится, и я могу использовать PSR-совместимый логгер для своей инфраструктуры.
Когда в приложении вижу, что передается класс, который расширяет Psr\Log\AbstractLogger - понимаю, что тут набросали логгер-заглушку в 10 строк, и надо подумать над полноценной реализацией.
Когда вижу ActiveRecord extends BaseActiveRecord - понимаю, что тут я смогу сделать только то, что придумал автор, и ни шагу в сторону.
А когда в каждом методе приложения у каждого параметра вижу тип - интерфейс, понимаю, что автор пишет книгу про ООП, и разобраться в этом коде сложновато :)
 

fixxxer

К.О.
Партнер клуба
Понятное дело, что компьютеру все эти наши абстракции не нужны, у него совершенно конкретные инструкции процессора.
Все это нужно нам, разработчикам, чтобы как-то справляться со сложностью современного ПО.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Еще есть важный момент, который почему-то не рассказывают в учебниках, но всегда уточняют при обсуждении архитектуры: речь про библитеку/фреймвок, или про userland, приложение с логикой.
Aggregation over inheritance, интерфейсы, generics - это применимо к общему коду, который пишется для других разработчиков, и к параметрам, которые этот общий код принимает.
Наша любимая тема про final, read-only/write-once - это контекст приложения. Строить небольшое наследование своих классов в рамках конечного приложения вполне удобно. Тех классов, которые можно переименовать, и никто не заметит. Указывать в параметре метода интерфейс для одного класса стораджа или api-драйвера, который, конечно final, можно в основном ради тестов ЧСВ.
 

ksnk

прохожий
Тоесть, принципиальное отличие интерфейса от абстрактного предка, по уму, было бы в том что бы можно было делать несколько интерфейсов для одного класса. Ну вот например итерабле и задно драйвер базы данных. Что-то вроде
Код:
class MyOwnClass extend MyAbstractParent implements ArrayAccess, Closure, MyOwnInterface {
...
}
Но в реализации множественные интерфейсы в таком явном виде куда то утратились... Ну, если не генерить промежуточный абстрактный интерфейс-предок, как вот тут https://www.php.net/manual/ru/language.oop5.interfaces.php - Пример #3 Множественное наследование интерфейсов
 

fixxxer

К.О.
Партнер клуба
Куда что утратилось? Обычное дело. На тот же стандартный пхпшный ArrayObject посмотри.
Но множественность, опять же, к сути отношения не имеет. Суть именно в отношениях. Да даже по именам видно. class Countable в Java это что-то явно странное, а interface Countable выглядит абсолютно естественно. Даже если в C++ с его множественным наследованием мы увидим class Countable, по одному имени понятно, что имеется в виду интерфейс.
Вообще эта джавовская модель, придуманная в попытке избавиться от проблем множественного наследования, не такая уж и удачная. Уже никто не понимает разницу между pure abstract class и interface так, как оно было задумано, ну и во многом это потому, что если не запрещать множественное наследование, разницы и нет.
 
Последнее редактирование:

AnrDaemon

Продвинутый новичок
Но в реализации множественные интерфейсы в таком явном виде куда то утратились
Они не утратились, они просто мало применимы на уровне бизнес-логики.
Ты не будешь создавать интерфейсы SenderAddress/TransshipmentAddress/DestinationAddress/StorageAddress, ты создашь интерфейс AddressInterface.
Но на уровне сервисного слоя такие вещи как https://www.php.net/arrayobject - обычное дело. Ты пользуешься ими каждый день, не задумываясь.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Вообще эта джавовская модель, придуманная в попытке избавиться от проблем множественного наследования, не такая уж и удачная. Уже никто не понимает разницу между pure abstract class и interface
😁 https://youtu.be/QHnLmvDxGTY?t=2489
"they were too lazy to deal with (multiple inheritance) so they invented some horable hack called interface ... it's not some special thing ... a hack, a lazy hack, they shouldn't have done that, they should've solved the problem instead of just throwing it out"

синдром поиска глубинного смысла, господа?
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Ну тут дядюшка Боб набрасывает. :)
Сама идея спецификации контракта/протокола очевидная и существовала задолго до Java, еще в железе, и к концепции наследования никакого отношения это не имеет.
Я точно так же могу набросить, что проблема не в интерфейсах, а в концепции наследования, которую просто не хватило смелости выбросить целиком, как это сделали в том же golang. ;)
 
Сверху