2015-02-07 2 views
4

меня есть два классаSharing зависимости между классами, позволяя зависимость инъекций

ClassA , ClassB 

Классы обычно зависят от двух основных услуг и хранилища

ServiceA , ServiceB 

Классы (ClassA , ClassB) Принципа использования DI, чтобы инъекционная зависимости с помощью конструктора ,

Поскольку все три разделяют некоторые общие услуги, как упоминалось выше, я хочу, чтобы сгруппировать все общие методы и услуги класса Base как этот

базового класса

class Base { 

    protected $A; 
    protected $B; 

    public function __construct(ServiceA $A, ServiceB $B){ 
     $this->A = $A; 
     $this->B = $B; 
    } 
} 

Child Service

class Child extends Base { 

    protected $C;   

    public function __construct(ChildDependency $C){ 
     $this->C = $C; 
    } 

    public function doStuff() 
    { 
     //access the $A (**Its null**) 
     var_dump($this->A); 
    } 

} 

Вопрос

Как я могу иметь общую родительскую зависимость без нарушения принципов IoC?

Возможный случай 1

Я знаю, что я должен назвать parent::__construct() для инициализации базового конструктора. Но тогда я должен определить зависимость родителя во всем дочернем классе, как показано ниже.

(Но для большого количества детей я должен повторить этот процесс. Он побеждает цель иметь общую точку DI).

class Child extends Base { 

    protected $C;   

    public function __construct(ChildDependency $C, ParentDepen $A, ParentDepn $B){ 
     parent::_contruct($A,$B); 
     $this->C = $C; 
    } 
} 

Возможный случай 2

Имея использовать методы получения и установки. Но я думаю, что они нарушают принцип IoC.

+0

Не могли бы вы поделиться с вами фактическим прецедентом? Я считаю, что в зависимости от того, что «Base'' Child» и зависимостей на самом деле, могут быть разные способы решения этой проблемы. – lukasgeiter

+1

Похоже, вы нарушаете [SRP] (http://en.wikipedia.org/wiki/Single_responsibility_principle) (и, возможно, некоторые другие [SOLID] (http://en.wikipedia.org/wiki/SOLID_%28object -oriented_design% 29), а также [код lasagna] (http://en.wikipedia.org/wiki/Spaghetti_code#Lasagna_code). –

+0

'ClassA, ClassB' Где они? – sectus

ответ

4

Это, кажется, как ваш лучший выбор, если у вас есть, чтобы ввести вашу зависимость от того, что создает свой объект:

class Child extends Base { 

    protected $C;   

    public function __construct(ServiceA $A, ServiceB $B, ChildDependency $C){ 
     parent::__contruct($A, $B); 
     $this->C = $C; 
    } 
} 

Вы можете попробовать использовать Traits вместо:

trait ServiceATrait { 
    public $A = new ServiceA(); 
    public function getServiceA() { return $this->A; } 
} 
trait ServiceBTrait { 
    public $B = new ServiceB(); 
    public function getServiceB() { return $this->B; } 
} 
class Base { 
    use ServiceATrait; 
    use ServiceBTrait; 
} 
class Child extends Base { 
    protected $C;   

    public function __construct(ChildDependency $C) { 
     $this->C = $C; 
    } 
} 

function() { 
    $c = new Child(new ChildDependency()); 
    echo $c->getServiceB()->toString(); 
} 
+0

Спасибо, но, как я упоминал в сценарии 1, если у меня есть большое количество классов, а позже, если я хочу изменить зависимость, мне нужно отредактировать все дочерние классы. – Sushant

+0

Это больше похоже на проблему дизайна. Если вы используете PHP 5.4 или выше, вы можете попробовать использовать черты для решения этой проблемы. http://php.net/manual/en/language.oop5.traits.php – Derek

+0

Я ценю вашу идею для черт. Но опять-таки зависимости от Трейтов не могут быть изменены во время выполнения. За этим я должен использовать Getter и Setter, которые снова нарушают принцип IoC, поскольку я не могу переключать класс во время выполнения – Sushant

0

Чтобы сохранить вещи в чистоте и поддерживаемый для сценариев наследования с несколькими зависимостями, я лично предпочитаю инъекцию setter над инъекцией конструктора. Есть также некоторые мысли по Martin Fowler на этом, которые стоит прочитать.

Вы также можете впрыскивать общие зависимости с помощью инъекции конструктора и дочерних зависимостей с инъекцией установщика, чтобы не испортить конструктор.

Тем не менее, если вы используете инъекцию установщика, очень важно убедиться, что объект частично не инициализирован или отсутствует некоторые зависимости. Отличный способ обеспечить это в методе getter службы (хотя вы заметите это только во время выполнения).Продолжая пример с Дереком, вы можете определить свои добытчиками следующим образом:

trait ServiceATrait { 
    private $A; 

    public function initServiceA(ServiceA $serviceA) { 
     $this->A = $serviceA; 
    } 

    public function getServiceA() { 
     if (null === $this->A) { 
      throw new \LogicException("ServiceA has not been initialized in object of type " . __CLASS__); 
     } 
     return $this->A; 
    } 
} 

Какой бы вариант вы выбрали, убедитесь, что использовать его последовательно по вашей базе кода.

+1

Я бы использовал 'if (is_null ($ this-> A)) {...' а не 'if (null === $ this-> A) {', но это только личные предпочтения – Derek

0

Я не уверен, что понимаю, чего вы пытаетесь достичь, используя наследование. Наследование должно использоваться для вещей концептуально одного и того же типа, а не для общей точки инъекции зависимостей. Если у вас есть естественный случай использования наследования, и вы также хотите использовать инъекцию зависимостей, чтобы следовать хорошей практике, тогда это имеет смысл. Но если параметры наследования и конструктора - путь, вы не можете обойтись, чтобы изменить их во многих классах, если у вас много наследования. Но наличие большого количества наследования обычно является признаком того, что вы злоупотребляете наследованием. Наследование часто злоупотребляют. Это делает поведение классов сложным и трудным для чтения и изменения. Всегда ищите альтернативу наследованию и используйте его только тогда, когда он работает лучше, чем альтернативы.

У вас есть еще один запах кода. Если вам нужно передать достаточно параметров конструктора, что создает проблемы ремонтопригодности, то ваши классы, вероятно, нарушают принцип единой ответственности. Это означало бы, что вам нужно разбить свои классы на большее количество классов. Вам также может потребоваться изменить некоторые параметры конструктора как параметры метода для соответствующих методов.

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

Насколько вы обеспокоены тем, что вы можете изменить во время работы, я не уверен, что вы имеете в виду. Все работает во время выполнения. Это все просто переменные в конце. Конструкторы могут вызываться во время выполнения, и вы можете передавать любые значения, которые им нужны. Вы можете использовать сеттеры во время выполнения. Инъекционная инъекция по определению представляет собой метод времени выполнения. Если ваш класс создает типы, о которых идет речь, не предоставляя способ изменить тип с помощью DI, я думаю, что он не изменчив во время выполнения, но никто не предлагает этого.

Вы можете использовать заводские классы или статические заводские методы. Статические методы фабрики могут иметь разные имена, описывающие, как они будут построены. Фабричные классы могут иметь один заводский метод, и в этом случае вы должны установить заводские экземпляры в зависимости от того, какая фабрика будет правильно строить объекты.

Не забывайте о аргументах по умолчанию. Их следует помнить независимо от того, как вы продвигаете инъекцию зависимостей или создаете свои объекты. Пример:

class someClass { 
    public function _construct($serviceA = new serviceA()) { 
     $this->serviceA = $serviceA; 
    } 
} 
Смежные вопросы