Я пытаюсь научиться принципу инверсии зависимости. В настоящее время мой код такой
class Example {
public function __construct( $input, $output ) {
$input_handler = new InputHandler( $input );
$output_handler = new OutputHandler( $output );
$input_handler->doStuff();
$output_handler->doOtherStuff();
}
}
$input = new Input();
$output = new Output();
$example = new Example( $input, $output)
Тем не менее, кажется, используя базовую инъекцию зависимостей, это должно быть больше так?
class Example {
public function __construct( $input_handler, $output_handler ) {
$input_handler->doStuff();
$output_handler->doOtherStuff();
}
}
$input = new Input();
$output = new Output();
$input_handler = new InputHandler( $input );
$output_handler = new OutputHandler( $output);
$example = new Example( $input_handler, $output_handler)
Это правильно?
Я хочу позволить программисту выбрать тип ввода / вывода, который будет использоваться при запуске программы. Так что с внедрением зависимости (насколько я понимаю) это будет выглядеть так;
$input = new ConsoleInput();
$output = new FileOutput();
$input_handler = new ConsoleInputHandler( $input );
$output_handler = new FileOutputHandler( $output);
$example = new Example( $input_handler, $output_handler);
$example->doStuffToOutput();
Тем не менее, я бы предпочел сделать программистов жизнь немного проще всего лишь передать тип ввода и вывода и не беспокоиться о классах, обрабатывающих их;
$input = new ConsoleInput();
$output = new FileOutput();
$example = new Example( $input, $output );
$example->doStuffToOutput();
или даже
$example = new Example( new ConsoleInput(), new FileOutput() );
$example->doStuffToOutput();
Как я могу добиться этого, используя DIP, а не получить мой начальный блок кода? Это хорошая вещь, чтобы сделать?
Пока я читал твой вопрос, я чувствовал, что у тебя две главные цели. Во-первых, улучшить читабельность вашего кода («облегчить жизнь программиста»), а во-вторых, отделить класс «Пример» от обработчиков ввода-вывода. С моей точки зрения, DI — это просто принцип, которому нужно следовать для достижения ваших целей.
Прежде чем прикреплять какой-либо код, я хочу подчеркнуть, что иногда лучше на самом деле соединить ваш код. Код должен быть как-то связан. Не используйте DI везде только потому, что это было сказано. Простота, как описано с принципами KISS и YAGNI, всегда побеждает.
Таким образом, большой вопрос здесь заключается в том, является ли ваша вторая цель (разделение с DI) разумной вещью. Есть ли реальная причина для изменения InputHandler / OutputHandler в классе «Exmaple»? Если «Нет» — ваш ответ, я бы порекомендовал вам сохранить его в этом классе нетронутым. И «возможно, в отдаленном будущем это будет выгодно», на самом деле не считается.
Однако, если ваши обработчики должны быть уникальными для каждого типа (файл, консоль и т. Д.), И ваша развязка поможет вам и другим программистам расширить платформу, вы можете воспользоваться шаблоном Factory. У вас есть несколько способов реализовать этот шаблон (статические / абстрактные / простые / методические фабрики). Основная цель состоит в том, чтобы уменьшить кривую обучения у клиента и отделить класс «Пример», чтобы добавление новых типов или обработчиков не влияло на этот класс.
class HandlerFactory {
protected static function createInputHandler(Input $input)
{
switch ($input)
{
case is_a($input, 'FileInput'):
return new FileInputHandler($input);
case is_a($input, 'ConsoleInput'):
return new ConsoleInputHandler($input);
}
throw new \Exception('Missing Input handler');
}
protected static function createOutputHandler(Output $output)
{
switch ($output)
{
case is_a($output, 'FileOutput'):
return new FileOutputHandler($output);
case is_a($output, 'ConsoleOutput'):
return new ConsoleOutputHandler($output);
}
throw new \Exception('Missing Output handler');
}
public static function createHandler($io)
{
switch ($io)
{
case is_a($io, 'Input'):
return self::createInputHandler($io);
case is_a($io, 'Output'):
return self::createOutputHandler($io);
}
throw new \Exception('Missing I/O handler');
}
}
Теперь ваш первый код в вашем вопросе все еще актуален с небольшим изменением:
class Example {
public function __construct($input, $output) {
$input_handler = HandlerFactory::createHandler($input);
$output_handler = HandlerFactory::createHandler($output);
$input_handler->doStuff();
$output_handler->doOtherStuff();
}
}
$input = new Input();
$output = new Output();
$example = new Example($input, $output);
Используйте класс Abstract Factory для обработки экземпляров объектов, необходимых для обработки ввода-вывода. Вы можете либо внедрить фабрику в примерный класс, либо позволить фабрике создать экземпляр необходимых объектов, а затем внедрить их в примерный класс. Тогда вы можете сделать:
$IOFactory = new IOFactory();
$example = new Example($IOFactory::makeInputHandler($inputType), $IOFactory::makeOutputHandler($outputType));
$example->doStuffToOutput();
IOFactory заботится о создании экземпляров базы входных и выходных объектов в их определенных типах, а затем создает экземпляры обработчиков и внедряет их в объект ввода и вывода. После этого возвращает объекты-обработчики, которые будут вставлены в пример объекта.
В вашем случае вы можете выбрать один из многих доступных шаблонов дизайна. Мое предложение состоит в том, чтобы пойти или с фабричным образцом или с шаблоном объекта
В случае шаблона фабричного метода у вас может быть класс, отвечающий за создание объекта:
class ObjectFactory {
public InputHandler createInputHandlerObject(inputobj){
if( inputobj instanceOf ConsoleInput ) {
return new ConsoleInputHandler();
} else if( inputobj instanceOf FileInput ) {
}
}
// similarly create a method for creating OutputHandler object.
//create the appropriate object by using instanceOf operator.
Так как я знаком с Java, я привел пример на Java. Вы можете изменить синтаксис и использовать соответственно. Это не единственный способ реализовать Factory Pattern.
Если вы хотите снять нагрузку с создания объекта во время выполнения, вы можете использовать шаблон пула объектов. Гибрид прототипа шаблона также становится удобным в вашем csse.