Вопрос по теме управление зависимостями

Bambino

Новичок
Вопрос по теме управление зависимостями

Маленькое введение... Пишу приложение с использованием ООП. Есть желание написать его с использованием правильного ООП, т.е. чтобы не было глобальных переменных/функций (я имею в виду global, define), с наследованием, полиморфизмом и т.п.. Случайно наткнулся в одном из топиков на статью про "управление зависимостями в php". Прочитал, понял, что именно такая статья мне была необходима... и как оказалось, я делаю в принципе примерно так как описано в статье, т.е. каждый класс содержит строго свой функционал, "старается" ничего не знать о других классах. Общий смысл статьи я уловил, но с деталями как-то проблематично.. поэтому возникли вопросы:

1. Есть классы, функционирование которых я бы хотел протоколировать или запускать в отладочном режиме (ну т.е. обычный логгер всего, что делает приложение для быстрой отладки, если что). Режим отладки/протоколирования может отключаться для production-версии. Объект одного (основного) класса может создавать объекты других классов, а те в свою очередь могут создавать третьи объекты. Изначально я создал класс Debugger, содержащий метод writeLog, объект которого передавал корневому объекту и далее по цепочке. Мне это показалось сложным (запутанным), т.е. нужно было для каждого отлаживаемого класса создавать свойство $debugger, которому присваивать ссылку на объект Debugger и работать уже через это свойство ($this->debugger->writeLog()). Поэтому я взял и унаследовал класс Debugger всеми другими классами, которым необходима отладка/протоколирование. В голове еще вертелась идея использовать некий интерфейс Traceable (слышал звон, да не знаю где он...), содержащий метод writeLog(). Так что мой класс Debugger реализует этот интерфейс и, соответственно, содержит этот метод. Вследствие чего у каждого класса (который должен "отчитываться") есть свой метод writeLog, который я вызываю как $this->writeLog. Можно ли считать мой подход правильным или я все же намудрил и есть решение попроще?

2. Есть некий класс Core (core.class.php) - основной диспетчер функционала, подключаемый в самом начале любого php-файла приложения, который в зависимости от параметров/условий вызывает те или иные скрипты, создает те или иные общие (и не только) объекты. Например, объект класса Core создается в скрипте index.php, в котором в зависимости от того, проинсталлировано приложение или нет, либо выполняется основной функционал скрипта, либо вызывается новый скрипт install.php для инсталляции, который в свою очередь опять использует объект класса Core. Так вот в этом классе (Core) создаются какие-то общие (глобальный) объекты, например, объект класса Smarty для работы с HTML-шаблонами (в случае инсталляции отображаются параметры инсталляции, иначе целевое содержимое). Но, если при процедурном программировании я мог создать такой глобальный объект и потом использовать его методы везде, где будет подключен core.class.php, то в случае ООП, естественно, мой объект доступен только в области видимости класса Core. Т.е. для того, чтобы какой-нибудь модуль RenderModule отрисовал мне ХТМЛ код, я должен передать ему ссылку на объект Smarty, а в случае с наследованием этот объект придется опять передавать по цепочке. Дабы избежать этого я воспользовался паттерном Singleton (единственный паттерн, смысл которого я осознал). Теперь в любом методе любого класса я могу вызвать Smarty::getInstance(), возвращающий объект Smarty и рабоать с выводом. Правильно ли это? Все вроде работает, но червь сомнения грызет и кажется, что есть какой-то более красивый способ. Т.е. на данный момент все глобальные объекты я реализую через паттерн Singleton.

3. Опять на тему глобализации. Если мне нужны какие-то общие функции, ну, например, безопасное считывание массивов типа $_GET, создание директорий с некими проверками, форматирование вывода и т.п., то как правильно их оформлять? Через статические методы:

PHP:
class Common
  static function GET ( $var,$default = null ) {
    return isset ( $_GET[$var] ) ?  $_GET[$var] : $default;
  }
}

class Foo {
  function __construct () {
    $var = Common::GET('var');
  }
}
или "классическим" способом:

PHP:
class Common
  function GET ( $var,$default = null ) {
    return isset ( $_GET[$var] ) ?  $_GET[$var] : $default;
  }
  static function getInstance () {
  ...
  }
}

class Foo {
  function __construct () {
    $GLO = new Common; // создали глобальный объект (в каком-нибудь общем подключаемом файле)
    $GLO->GET ( 'var' );  // просто вызываем метод класса
  }
}

class Bar {
  function __construct () {
    $var = Common::getInstance()->GET('var'); // через паттерн Singleton используем в других классах
  }
}
4. Я правильно понимаю термины?

наследование

PHP:
class Foo {
}

class Bar extends Foo {
}
композиция

PHP:
class Foo {
}

class Bar {
  private $Foo = null;
  function __construct () {
    $Foo = new Foo ();
  }
}
Есть ли какое-то правило, когда стоит применять наследование, а когда композицию?

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

Недеюсь, написал понятно. Заранее благодарен за ответы.

-~{}~ 27.03.10 02:43:

Еще вопрос возник, связанный с вопросом 4. Как правильнее?

PHP:
class DbDriver extends PDO {
  function query () {
    parent::query ( ... );
    ...
  }
}

class DbConnection {
  protected $conn = null;
  function __construct () {
    $this->conn = new DbDriver ();
  }
  function getConn () {
    return $this->conn;
  }
}
так:

PHP:
class DbManager extends DbConnection {
  function createTable () {
    $this->conn->query ('...');
  }
}
или так

PHP:
class DbManager {
  private $conn = null;
  function __construct () {
    $conn = new DbConnection ();
    $this->conn = $conn->getConn ();
  }
  function createTable () {
    $this->conn->query ('...');
  }
}
 

kirill538

Новичок
Re: Вопрос по теме управление зависимостями

Очень много разных и объемных вопросов в одной теме, имхо.

По п. 1:
Подход неверен. Когда вам в классах понадобится кроме лога отправка почты - вы захотите наследоваться не только от Debugger, но еще и от Mailer. А множественного наследования в php к счастью нету :)
Для задач типа логгеров и прочих похожих функций можно рассмотреть паттерн Observer. Он даже в SPL есть: http://ru2.php.net/manual/en/class.splobserver.php Лично мне больше обсервера нравятся события:

В глобально доступном классе (к примеру, доступном через Registry):
PHP:
public function addEventListener(string $eventName, object $listener);
public function fireEvent(string $eventName, array $eventData=array());
В конструкторе класса (который вы сейчас наследуете от Debugger):
PHP:
Registry('Events')->addEventListener('debug_log_request', new StandaloneDebugger);
Как только по ходу дела нужно пихнуть чего в лог:
PHP:
Registry('Events')->fireEvent('debug_log_request', array('string from write'));
Это сильно упрощенно.
Стоит учесть что модель событий совсем хорошо работает в комплекте с инжектором зависимостей. Но это отдельный вопрос :)
 

AmdY

Пью пиво
Команда форума
3. я предпочитаю использовать наследование, потому что память не настолько хороша, чтобы держать всё в голове
PHP:
abstract class Hepler {
protected function getCommonGet($key) { return Common::GET('var');}
}
class Foo extends Helper {
public function __construct() {
$this->getCommonGet('key');
}
}
или как вариант
PHP:
abstract class Hepler {
protected function getCommon() { return Common::getInstance();}
}
class Foo extends Helper {
public function __construct() {
$this->getCommon()->GET('key');
}
}
 

kirill538

Новичок
3. я предпочитаю использовать наследование, потому что память не настолько хороша, чтобы держать всё в голове
AmdY, действительно отличная метафора для объяснения проблем зависимостей на пальцах :)

Bambino:
Дело не только в плохой памяти, а как минимум в том, что приведенные в примере фабрики можно подменить, не исправляя по всему коду жестко прописанных вызовов статических методов.
Статические методы лучше использовать с крайней осторожностью. Любой вызов статических методов создает зависимость. Внимательно читайте про инверсию зависимостей хотя бы на AgileDev http://wiki.agiledev.ru/doku.php?id=ooad:dependency_injection
 

Bambino

Новичок
Re: Re: Вопрос по теме управление зависимостями

Автор оригинала: kirill538
Очень много разных и объемных вопросов в одной теме, имхо.

По п. 1:
Подход неверен. Когда вам в классах понадобится кроме лога отправка почты - вы захотите наследоваться не только от Debugger, но еще и от Mailer. А множественного наследования в php к счастью нету :)
Как я писал мой class Debugger реализует интерфейс Traceable. Почему я не могу добавить новый метод sendmail() в интерфейс и потом реализовать его в классе Debugger, который будет опять же доступен для потомков?

Для задач типа логгеров и прочих похожих функций можно рассмотреть паттерн Observer. Он даже в SPL есть: http://ru2.php.net/manual/en/class.splobserver.php Лично мне больше обсервера нравятся события:

В глобально доступном классе (к примеру, доступном через Registry):
PHP:
public function addEventListener(string $eventName, object $listener);
public function fireEvent(string $eventName, array $eventData=array());
А сам функционал записи в файл/отправки почты и т.п. реализовывается в этом "глобально доступном классе"?

-~{}~ 29.03.10 12:21:

Автор оригинала: AmdY
3. я предпочитаю использовать наследование, потому что память не настолько хороша, чтобы держать всё в голове
PHP:
abstract class Hepler {
protected function getCommonGet($key) { return Common::GET('var');}
}
class Foo extends Helper {
public function __construct() {
$this->getCommonGet('key');
}
}
или как вариант
PHP:
abstract class Hepler {
protected function getCommon() { return Common::getInstance();}
}
class Foo extends Helper {
public function __construct() {
$this->getCommon()->GET('key');
}
}
А метод __construct использован случайно? Как в этих примерах получить значение параметра, ведь конструктор не возвращает значение? Т.е. раньше я получал значение так:
PHP:
$var = Common::GET('var');
А в вашем случае? Или просто пропущена левая часть равенства? :)

-~{}~ 29.03.10 12:42:

Автор оригинала: AmdY
3. я предпочитаю использовать наследование, потому что память не настолько хороша, чтобы держать всё в голове
PHP:
abstract class Hepler {
protected function getCommonGet($key) { return Common::GET('var');}
}
class Foo extends Helper {
public function __construct() {
$this->getCommonGet('key');
}
}
После анализа возникли еще вопросы..
1. Если мне нужен какой-то новый общий метод, то я добавляю его в класс Helper, например,
PHP:
protected function getCommonPost($key) { return Common::POST('var');}
?

2. class Foo - это любой класс, в котором мне нужно использовать общие функции? Мне не очень понятно использование наследования здесь. Я понимаю наследование в этом случае:
PHP:
class Shape {
  function getSquare ();
  function getPerimeter ();
}
class Circle extends Shape {
  // здесь реализация методов площади и периметра окружности
}
class Square extends Shape {
  // здесь реализация методов площади и периметра квадрата
}
А какая наследственная связь наблюдается в нашем случае с общими методами?
И какой смысл записи, которую предложили Вы? Можно ли делать так:

PHP:
class Foo  {
  $helper = null;
public function __construct() {
  $helper = new Helper();
  $helper->getCommonGet('key');
}
? Этот вариант, конечно, более громоздкий. Но для меня важно уловить суть того или иного использования...
 

Beavis

Banned
Bambino
если ты подставишь сюда "_____ is a ______"
название класса-потомка и название класса-родителя, и получится true, тогда логично применить наследование

т.е. Circle is a Shape
 

Bambino

Новичок
Я знаю, что Circle is a Shape. Я говорил об ответе AmdY, который предлагает использовать наследование для использования глобальных общих функций.
 

Beavis

Banned
Автор оригинала: Bambino
Я знаю, что Circle is a Shape. Я говорил об ответе AmdY, который предлагает использовать наследование для использования глобальных общих функций.
ну у него впринципе так и есть, только класс должен называться не Foo а FooHelper, тогда всё логично
 

AmdY

Пью пиво
Команда форума
статический вызов хорош, краток и удобен, но его можно использовать только когда он не зависит от других методов
Common::set($_GET);
Common::get('key');
--------------------------------
плохо, т.к. зависимость от вызова set, который является аналогом конструктора

$helper = new Helper();
$helper->getCommonGet('key');
------------------------------
вызвался конструктор, вроде всё ок. но приходится держать в голове наличие метода, я предпочитаю скривать, плодя обёртки с лишними вызовами, но упрощая разработку.
 

Bambino

Новичок
AmdY, я правильно понял идею? Чтобы пользоваться общими функциями, то нужно каждый класс, где эти функции необходимы, нужно наследовать от класса Common с этими общими функциями?

Я не понял этот пример кода:
PHP:
abstract class Hepler {
protected function getCommonGet($key) { return Common::GET('var');}
}
class Foo extends Helper {
public function __construct() {
$this->getCommonGet('key');
}
}
почему в конструкторе вызывается метод без присваивания? Это опечатка или действительно так надо?

Теперь по поводу опять этих общих функций. Например, мне нужно использовать какие-то общие функции в классе SomeModule, который уже наследует от Module. Как же я смогу еще наследовать от класса Common? Может есть какой-то другой способ глобализации в одноразово?

Или Helper (в твоем примере) - это дополнительный класс, объект которого нужно создавать в каждом классе, где мне нужны общие функции?

-~{}~ 29.03.10 20:26:

А как быть в такой ситуации? Я использую Smarty в качестве шаблонизатора. Хочется один раз создать объект Smarty, потом в любом классе, где мне нужен вывод ХТМЛ вызывать его методы, например:
PHP:
$tpl->assign ();
$tpl->display();
т.е. не создавать объект каждый раз и инициализировать его в каждом нужном классе, а сделать это один раз? На ум сразу приходит передача объекта $tpl, например, в конструкторе класса, где мне необходим вывод ХТМЛ. Я прав или есть другой вариант?

-~{}~ 30.03.10 17:43:

Уважаемый, AmdY, был бы признателен за ответ... :)
 
Сверху