Composition vs Inheritance

WMix

герр M:)ller
Партнер клуба
Всегда, когда тебе надо отнаследоваться не от абстрактного класса, это признак архитектурной проблемы
PHP:
class A{
  public function b(){}
  public function c(){}
  public function d(){}
}

interface D{
  public function x();
}

class B extends A implements D{
  public function x(){
     return $this->a($this->b()) +$this->c();
  }
}

// мы тут
function main(D $d){
  // у нас простой интерфейс
}

main( new B() );
@fixxxer, на самом деле это обычная инкапсуляция, тебе не надо знать откуда и как пришел к тебе обещаный интерфейс (composer require blackbox), не вижу я тут ошибки архитектуры.
в том месте где я пишу все уже чисто
 

fixxxer

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

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

WMix

герр M:)ller
Партнер клуба
есть прикольная библиотека, там все функции которые нужны, еще 5 ненужных и одной не хватает, взял унаследовал, нехватающую дописал, лишнее не учел в интерфейсе,
все без шума которую приносит композиция и мокается и заменяется.
к чему это программирование воздуха только чтоб уйти от наследования?
 

fixxxer

К.О.
Партнер клуба
На абстрактных примерах с буквами сложно обсуждать, но я попробую.

class A у нас не абстрактный, соответственно, у нас есть инстансы A, и есть код, работающий с инстансами A (аргументы методов требуют A $a).
Дальше где-то нам потребовалось сделать x(), который вычисляется из публичных методов A. В твоем примере получается, что это можно сделать только с instanceof B. Соответственно, рано или поздно мы либо придем к коду вида if ($a instanceof B), либо начнем извращения вида B::constructFromA(A $a). Никаких объективных причин для этого нет, и если бы мы написали

PHP:
class B implements D {
    public function __construct(A $a) {..}
    public function x() {
        return $this->a->a($this->a->b()) +$this->a->c();
    }
}
проблем бы не возникло.

есть прикольная библиотека
...и эта библилотека (либо иной сторонний код, зависящий от этой библиотеки) наверняка ожидает и возвращает (либо передает дальше по цепочке) instanceof A, теряя тип B. И опять где-то вылезет instanceof.

Заметь, кстати, что в варианте с композицией проблем не будет, даже если A - abstract. :)

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

WMix

герр M:)ller
Партнер клуба
и есть код, работающий с инстансами A (аргументы методов требуют A $a).
как раз таки в моем примере B instanceof A
Соответственно, рано или поздно мы либо придем к коду вида if ($a instanceof B)
нет, ($a instanceof D)
у нас есть инстансы A
ну хочется надеяться что их нет, но запретить действительно не могу, как кстати и ты

наверняка [в моем случае это не важно]ожидает[/в моем случае это не важно] и возвращает
это надо врапить, да
 

fixxxer

К.О.
Партнер клуба

WMix

герр M:)ller
Партнер клуба
Это ничего не меняет. :)
doch, это все меняет, мы не говорим про конкретную реализацию, нас интересует только абстрактный interface
 

fixxxer

К.О.
Партнер клуба
Откуда ты возьмешь реализацию этого своего абстрактного интерфейса, в случае, когда у тебя есть instanceof A, который не реализует этот интерфейс? :)

На том или ином уровне у тебя все равно будет код типа того, который я привел в предыдущем комментарии, где тебе явно надо знать о том, что B реализует D.
 

fixxxer

К.О.
Партнер клуба
При чем тут DI?

Я так понимаю, что ты хочешь сделать как-то так в надежде на то, что все будет хорошо и у тебя везде окажутся instance of B:

PHP:
interface X {
    public function x();
}

class A {
}

class B extends A implements X {
    public function x() {
        echo "x";
    }
}

function doSomethingWithA(A $a): A {
    return $a;
}

function doSomethingWithX(X $x) {
    $x->x();
}

$b = new B();
$a = doSomethingWithA($b);
doSomethingWithX($a);
Во-первых, никто этого не гарантирует, и рано или поздно ты нарвешься на doSomethiingWithX(instanceof A)
Во-вторых, такое говно в нормальном языке программирования вообще не скомпилируется, а обсуждать тупость конкретно PHP и строить архитектуру на недоработках PHP - это несерьезно.
 

fixxxer

К.О.
Партнер клуба
Блин, вот, с одной стороны, ты втащил меня в обсуждение какой-то фигни, к abstract or final это уже отношения мало имеет.
Но, с другой стороны, зато я понял, как сформулировать свое отношение к наследованию вообще.
Наследование ок, когда наследуешься от абстрактного класса, при этом ничего не добавляешь к фактическому публичному интерфейсу класса, а только реализуешь абстрактные методы.
Во всех остальных случаях так или иначе возникнет проблема с LSP и/или DIP.
 

WMix

герр M:)ller
Партнер клуба
пример из моей тестовой модельки
PHP:
// di.conf
return [
  '\BlaBla\D' => function(Di $di){
      return new B();
  },
  'Foo' => function(Di $di){
     return new Foo($di->get('\BlaBla\D'));
  }
];

// Di
   public function get( string $key ){
        if( isset($this->conf[$key]) ){
            if(is_callable($this->conf[$key])){
                return ($this->conf[$key])($this);
            }
        }
    }


function bar(\BlaBla\D $d){

}


use \BlaBla\D;
class Foo{
  // @var D
  private $d; // пользуйся

  public main(){
     bar($this->d)
  }
}

$foo = $di->get('Foo');
 

fixxxer

К.О.
Партнер клуба
В этом случае тебе совершенно несущественно то, что (если использовать нейминг из изначального примера) B реализует контракт A (это лишь особенность реализации).

Для тебя существенно лишь то, что B implements X. Публичными методами A ты снаружи не пользуешься, и пользоваться ими некорреткно, поскольку никто не гарантирует, что реализация не изменится.

В этом случае совершенно очевидно, что тут нужно не наследование, а композиция.

В случае же, если ты где-то в контексте одного и того же объекта пользуешься и контрактом X, и контрактом A, ты, фактически, неявно пользуешься контрактом B, и тогда уж лучше пользоваться им явно, иначе у тебя во все поля полезут instanceof и нарушения LSP/DIP.
 

WMix

герр M:)ller
Партнер клуба
"A" уже не существует, это внутренняя реализация типа "X" (в моем примере это "D")
 

fixxxer

К.О.
Партнер клуба
"A" уже не существует, это внутренняя реализация типа "X" (в моем примере это "D")
Если для тебя всегда снаружи существует только интерфейс, в чем разница между

PHP:
class B implements X {
     public function __construct(A $a) { ... }
     public function x() { ... }
}
и
PHP:
class B extends A implements X {
     public function x() { ... }
}
?

Написать и то, и другое одинаково легко (тем более, у тебя же DI), но первый вариант, в отличие от второго, не замусоривает фактический интерфейс класса левыми публичными методами и защищает от ошибки с прямым использованием публичных методов класса A.

Вообще не понимаю, зачем тут наследоваться.
 
Сверху