Проектирование интерфейса и реализации

CRL

Новичок
Проектирование интерфейса и реализации

Возник вопрос по по воду правильного проектирования интерфейсов и их реализации.
Имеется интерфейс, описывающий взаимодействие с хранилищем данных безотоносительно его типа:

PHP:
interface IStorage
{
        public function db_connect($open_data);
        ...
}
Имеется реализация данного интерфейса для конкретного типа хранилища - MySQL:

PHP:
class MySQLStorage implements IStorage
{
        public function db_connect($open_data)
        {
            $result = array();
            
            if(
                empty($open_data["db_host"]) ||
                empty($open_data["db_user"]) ||
                empty($open_data["db_pass"]) ||
                empty($open_data["db_name"]) ||
                empty($open_data["db_charcode"])
              )
            {
                $result["flag"] = false;
                $result["msg"]  = "Некоректны параметры подключения к хранилищу";
                return $result;
            }
            
            //  устанавливается соединение с БД и возвращается маркер соединения
            
            ...
            
            $result["flag"] = true;
            $result["msg"]  = "Соединение с БД установлено";
            $result["storage_marker"] = $storage_marker;
            
            return $result;
        }
        
        ...
}
Далее, используется реализация:

PHP:
...
$storage = new MySQLStorage;
$clib = new Lib($storage);
...
    $marker = $clib->storage->db_connect($data);
Собственно, вопрос вот в чем: по идее, реализация интерфейса - это "черный ящик" с заданным функционалом и, используя реализацию, я могу лишь ориентироваться на описание, сделанное в интерфейсе. Но - в моём случае - из этого описания непонятно, как должны быть представлены входные данные - я не знаю формата $data, когда пользуюсь реализацией интерфейса (если хранилище - текстовый файл, формат $data будет отличаться от случая с хранением данных в SQL-базе). Т.е., если я явно указываю параметры, специфичные для SQL-базы, в интерфейсе, он терят универсальность, если указываю в общем виде - становится неочевидно использование реализации. Как правильно проектировать подобного рода вещи, чтобы они сохраняли универсальность и при этом были очевидны?
 

zerkms

TDD infected
Команда форума
DSN, посмотри в PDO

-~{}~ 30.05.08 15:54:

и кстати, по DSN же автоматом может и тип стораджа выбираться:

PHP:
$dsn = 'mysql://user:[email protected]/testdb';
$stg = new storage($dsn);
 

CRL

Новичок
Автор оригинала: zerkms
DSN, посмотри в PDO
и кстати, по DSN же автоматом может и тип стораджа выбираться:
Можно ссылочки на мануал? Я сейчас посмотрел на php.net - там есть описанние отдельных коннекторов навроде PDO_сторадж_DSN, но что-то не могу найти описания автоопределения типа хранилища.
 

CRL

Новичок
Автор оригинала: zerkms
$dsn = 'mysql://user:р[email protected]/testdb';

-------> __ТИП://__ <-------
А, теперь я понял тебя. Но только проблемы-то это всё едино не решает, ведь параметры соединения у стораджей могут кардинально различаться (
например:

PHP:
$db = new PDO('sqlite:/tmp/foo.db');
и

PHP:
$db = new PDO('mysql://user:р[email protected]/testdb')
), поэтому всё равно придётся явно указывать их в интерфейсе, а это как раз то, чего я и хотел избежать, сохранив при этом понятность описания, не описывая параметры неопределенно. Может быть в начальном посте я не совсем понятно выразился, попробую описать проблему проще:

в интерфейсе описан метод db_connect($open_data), где $open_data - массив необходимых для соединения параметров. Из этого описания невозможно понять, какой именно сет параметров требуется для каждой конкретной реализации - не ясно, например, какие параметры и в какой последовательности передавать для организации соединения с MySQL. Но если я явно укажу в интерфейсе сет параметров, например, так - db_connect($db_host, $db_user, $db_pass, $db_name), то интерфейс потеряет универсальность, и невозможно будет сделать реализацию, скажем, для SQLite. Может быть, я неверно подошел к решению данной проблемы?
 

zerkms

TDD infected
Команда форума
ведь параметры соединения у стораджей могут кардинально различаться
и что? по "протоколу" ты узнаешь тип стораджа, парсинг, коннект и прочее уже пускай делает конкретная реализация
 

rotoZOOM

ACM maniac
Вообще-то, конечный класс, коим у тебя является MySQLStorage должен принимать конкретные параметры соединения, не выискивая их в массиве, типа $open_data. А вот знать, что именно передавать в db_connect должна фабрика. Фабрика пишется для конкретной реализации интерфейса и поэтому она знает какие- параметры ждет от нее объект. Почитай паттерн "Фабрика".
 

CRL

Новичок
Автор оригинала: rotoZOOM
Почитай паттерн "Фабрика".
Нашел в инете кое-что по Factory. Почитал. Однако, плохо понял - то ли лыжи не едут, то ли я... Нельзя ли кинуть ссылочку на какой-нибудь мощный труд в этой области - с теорией, с примерами? Или выложить свой собственный - очень простой - пример?
 

rotoZOOM

ACM maniac
Мощный труд: "Приемы объектно-ориентированного проектирования" by GoF, "Современное проектирование на С++" Александреску (хотя по С++ но суть перекладывается и на PHP).
Наверняка народ щас еще подскажет.
Очень простой пример.
PHP:
interface IStorage{...}
class MySQLStorage implements IStorage{...}
class MySQLFactory{
    public function getInstance (){
         // == фабрика получает параметры соединения
         // == много способов есть как.
         return new MySQLStorage(параметры_соединения);
    }
}


// ... основная программа
$fst=new MySQLFactory(/* либо с параметрами */);
$Storage=$fst->getInstanse();
// Все ... теперь у тебя есть Сторадж, юзай его как хочешь
Естественно, что фабрика (возможно + реализация), интерфейс, находятся в разных файлах.
Это все таки очень грубый пример, за которым я подозреваю последует куча вопросов.
Почетай еще тут
 

kirill538

Новичок
Автор оригинала: CRL
Или выложить свой собственный - очень простой - пример?
PHP:
abstract class DB {
    static public connect function($dsn){
        if(isset($this->_dsn[$dsn])) return $this->_dsn[$dsn];
        // парсинг строки DSN, инстанс реализации DB_MySQL и инстанса в кеш $this->_dsn;
        return $this->_dsn[$dsn];
    }
    /** реализация по умолчанию основных методов, требующихся для работы с СУБД */
}
ИМХО, в случае использования PDO abstract удобнее interface, поскольку большинство операций с базой могут иметь реализации по умолчанию.
 

CRL

Новичок
Автор оригинала: rotoZOOM
...за которым я подозреваю последует куча вопросов.
Насколько я понял из примера , а так же из материалов
http://developer.co.ua/posts/view/factory_pattern_v_php
http://www.patternsforphp.com/wiki/Factory ,
Фабрике перепоручается создание экземпляра соответствующей реализации, но я всё-таки не совсем понимаю, как в этом случае должен быть описан интерфейс, откуда я узнаю, какие параметры передавать Фабрике для создания экземпляра реализации. Снова вернусь к тому, с чего начал - я знаю описание (интерфейс), а реализция и Фабрика - это то, что мне просто дали со стороны, и я ничего не знаю о их внутреннем утройстве. И я должен создать экземпляр Фабрики с определенными параметрами, на основании которых она должна решить, экземпляр какой реализации создавать. Правильно? Но откуда я узнаю, какие параметры нужно передавать Фабрике - по идее, только из интерфейса. А там они описаны в формальном, не зависящем от реализации виде. Похоже, я всё-таки что-то неправильно понимаю.
 

kode

never knows best
Re: Проектирование интерфейса и реализации

Автор оригинала: CRL
Собственно, вопрос вот в чем: по идее, реализация интерфейса - это "черный ящик" с заданным функционалом и, используя реализацию, я могу лишь ориентироваться на описание, сделанное в интерфейсе. Но - в моём случае - из этого описания непонятно, как должны быть представлены входные данные - я не знаю формата $data, когда пользуюсь реализацией интерфейса (если хранилище - текстовый файл, формат $data будет отличаться от случая с хранением данных в SQL-базе). Т.е., если я явно указываю параметры, специфичные для SQL-базы, в интерфейсе, он терят универсальность, если указываю в общем виде - становится неочевидно использование реализации. Как правильно проектировать подобного рода вещи, чтобы они сохраняли универсальность и при этом были очевидны?
Либо как уже сказали выше используй фабрику + DSN

аля,

PHP:
abstract class DBStorage {

	abstract public function __construct($data);
	abstract public static function getScheme();
}
class MySQLDriver extends DBStorage {
	public function  __construct($dsn){}
	public static function getScheme(){
		return 'mysql';
	}
}
class PostgreSQLDriver extends DBStorage {
	public function  __construct($dsn){}
	public static function getScheme(){
		return 'postgre';
	}
}

class DBFactory {

	public static function getDriver($dsn){
		$params = parse_url($dsn);


		/* не лучший способ, но Quick & Dirty */
		foreach(get_declared_classes() as $classname){
			if(is_subclass_of($classname,"DBStorage") and call_user_func("{$classname}::getScheme") == $dsn['scheme']){
				return new $classname($params);
			}
		}


		return null;
	}
}
 

CRL

Новичок
Re: Re: Проектирование интерфейса и реализации

Автор оригинала: kode
Либо как уже сказали выше используй фабрику + DSN
Я и сам склоняюсь к использованию Фабрики, просто я пока не могу до конца прояснить, как она работает.
 

kode

never knows best
Re: Re: Re: Проектирование интерфейса и реализации

Автор оригинала: CRL
Я и сам склоняюсь к использованию Фабрики, просто я пока не могу до конца прояснить, как она работает.
ответ выше, я код для наглядности написал
 

rotoZOOM

ACM maniac
Как я уже говорил ранее, есть несколько способов у фабрики получить информацию о данных для создания объектов.
1. Иметь глобальный регистр, в котором будут регистрироваться общие сервисы, такие как логгер, конфигуратор, и т.д. И передавать в конструктор фабрики этот регистр, она же знает, что ей нужна конфигурация, выцепляет сервис конфигурации из регистра, и уже с помощью этого сервиса достает нужные данные самостоятельно.
2. Фабрика сама знает в каком конфиге хранится нужная информация и как ее добыть.
3. Фабрике передается сразу ссылка на ТО, откуда она может взять конфигурацию. ТО может быть объектом конфигурации, имя файла и т.д.

Разбивай логику работы объектов на разные уровни.
Бизнес логика не должна ничего знать о каких-то там DSN.
Конечная реализация стораджа не должна знать о типах конфигах, где лежат данные и в каком формате они лежат.
Пусть народ меня поправит, если я не прав.
 

CRL

Новичок
Re: Re: Re: Re: Проектирование интерфейса и реализации

Автор оригинала: kode
ответ выше, я код для наглядности написал
Да, вот теперь мне понятно. Похоже, я зря пытался использовать интерфейс...

-~{}~ 30.05.08 17:11:

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

atv

Новичок
Но откуда я узнаю, какие параметры нужно передавать Фабрике - по идее, только из интерфейса.
Можешь в интерфейсе указать тип принимаемого параметра (например OpenData). Соответственно, нужно будет создать объект этого типа и наполнить его необходимыми параметрами, которые ты сможешь уже проконтролировать в этом объекте.
PHP:
interface IStorage
{
        public function db_connect(OpenData $open_data);
        ...
}
Это ответ на твой вопрос, как правильно делать в таких случаях.

НО, это не тот случай, в котором стоит так усложнять реализацию. DSN достаточно распространённый формат, лёгок для восприятия, и можно обойтись формальным соглашением, а не усложнением интерфейса.
 

kode

never knows best
абстрактный класс и интерфейс очень схожие вещи, со своими недостатками и достоинствами:

1) Можно реализовывать несколько интерфейсов, но можно наследовать только один класс
2) В Абстрактном классе зато можно реализовывать общий функционал.

Использовать Интерефейсы либо Абстрактные Классы нужно тонко, по ситуации....
 

atv

Новичок
Использовать Интерефейсы либо Абстрактные Классы нужно тонко, по ситуации....
Я, например, использую и интерфейс и абстрактный класс, так как классы, реализующий общий интерфейс, могут группироваться по реализации. Вот и получается, классы реализующие общий интерфейс, наследуются от разных абстрактных классов. Так что комбинации бывают самые разные.
 
Сверху