Вопрос по наследованию: помогите разобраться и понять...

Bambino

Новичок
Понадобилось мне залезть внутрь фреймворка Yii (заинтересовала реализация кэширования). Рассмотрел код и один момент мне не ясен, видимо не хватает знаний. Очень прошу старших товарищей помочь... У меня правда есть одна догадка... Тогда может скажете верна она или нет.

Итак, рассмотрим код (примерный):

PHP:
interface ICache
{
  public function set($id,$value,$expires);
}

class CCache implements ICache
{
  public function set($id,$value,$expires)
  {
    return $this->setValue($this->generateUniqueKey($id),serialize($data),$expire);
  }

  protected function generateUniqueKey($key)
  {
    return md5($key);
  }

  protected function setValue($key,$value,$expire)
  {
    throw new Exception('Реализуйте этот метод в потомке');
  }
}

class CApcCache extends CCache
{
  protected function setValue($key,$value,$expire)
  {
    return apc_store($key,$value,$expire);
  }
}
Теперь вопрос: зачем в классе CCache создается метод setValue, который должен быть переопределен в каждом потомке (CApcCache,CFileCache и т.т.)? Получается для каждого метода интерфейса ICache необходимо создать "обертку". Почему в данном случае в классе-потомке (CApcCache) не переопределень тот же самый метод set?:

PHP:
class CApcCache extends CCache
{
  protected function set($key,$value,$expire)
  {
    return apc_store($this->generateUniqueKey($id),$value,$expire);
  }
}
Собственно это я и хочу прояснить. На данный момент я придумал, что такое "проксирование" было сделано для того, чтобы класс-потомок CAppCache не был привязан к методам класса-родителя CCache, т.е. не зависил напрямую. Но с другой стороны меня смущает тот факт, а зачем же тогда нужно наследование и protected-методы, если я не могу из использовать в потомках?
 

zerkms

TDD infected
Команда форума
1. Обязательная реализация методов делается через абстрактные классы и методы, соответственно, или интерфейсы
2. Такой подход, если бы там было несколько вызовов, назывался бы the template method. В текущем виде, естественно, смысла не имеет
3. В Yii много странного и сомнительного
 

Bambino

Новичок
2. Такой подход, если бы там было несколько вызовов, назывался бы the template method. В текущем виде, естественно, смысла не имеет
Несколько вызовов чего? И что такое template method?

В конечном итоге мне нужно, чтобы у меня была поддержка всех доступных на сервере методов кэширования, между которыми я мог бы переключаться, вызывая фабрику объетов:

PHP:
if(extension_loaded('apc'))
  $cache = Cache::factory('apc');
elsif(extension_loaded('memcache'))
  $cache = Cache::factory('memcache');
else
  $cache = Cache::factory('file');
А какое может быть другое объяснения такого кода, кроме как "много странного и непонятного"?
 

zerkms

TDD infected
Команда форума
Bambino
Безусловно, какое-то другое объяснение может быть, но я в существование адекватного оного не верю.

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

Bambino

Новичок
Т.е. такой вариант работоспособен и правилен с точки зрения ООП?

PHP:
interface ICache
{
  public function set($id,$value,$expires);
  public function get($id);
  ...
}

abstract class CCache implements ICache
{
  // просто объявляем пустышки, а реализация будет в нужных потомках
  public function set($id,$value,$expires) {}
  public function get($id) {}
  ...
  // этот метод сервисный, для уникализации ключей в кэше
  protected function generateUniqueKey($key)
  {
    return md5($key);
  }
}

// APC кэш
class CApcCache extends CCache
{
  public function set($id,$value,$expire)
  {
    return apc_store($this->generateUniqueKey($id),serialize($value),$expire);
  }

  public function get($id)
  {
    return apc_fetch($this->generateUniqueKey($id));
  }

}

// Кэш на файлах
class CFileCache extends CCache
{
  public function set($id,$value,$expire)
  {
    return file_put_contents('cache'.$this->generateUniqueKey($id),$value);
  }

  public function get($id)
  {
    return file_get_contents('cache'.$this->generateUniqueKey($id));
  }

}
Приемлимо ли использование метода generateUniqueKey родительского класса CCache в классе-потомке? Просто я когда-то читал, что, мол, классы нужно разрабатывать так, чтобы они были самодостаточны и не сильно зависели от других классов, а то вдруг, если что-то изменится в классе-родителе, то придется вносить изменения по всем классам-потомкам... Вдруг возникнет необходимость использования другого метода, генерирующего уникальный ключ (generateUniqueKey2 вместо generateUniqueKey) и тогда в первом случае (1-й пост) нужно будет изменить только класс-родитель СCache, а во втором (этот пост) уже нужно будет править код всех потомков.. Вот что меня интересует :) Или мои домыслы надуманы?
 

zerkms

TDD infected
Команда форума
"// просто объявляем пустышки, а реализация будет в нужных потомках"
не нужно объявлять пустышки - нужно объявлять абстрактные методы

"Приемлимо ли использование метода generateUniqueKey родительского класса CCache в классе-потомке?"
Притянуто за уши. Пусть сторадж сам решает, как ему хранить ключи. К тому же md5() может привести к проблеме коллизий (маловероятно, но тем не менее).

"return apc_store($this->generateUniqueKey($id)"
Зачем ты вообще дёргаешь этот метод generateUniqueKey? Есть какие-то проблемы с хранением данных в ключах равным $id как есть?
 

Bambino

Новичок
zerkms

"// просто объявляем пустышки, а реализация будет в нужных потомках"
не нужно объявлять пустышки - нужно объявлять абстрактные методы
Так они уже объявлены абстрактыми, потому что это методы интерфейса (посмотри иерархию). Разве можно объявлять абстрактными методы, которые и так абстрактные, т.к. объявлены в интерфейсе?

PHP:
interface ICache
{
  public function set();
}

abstract CCache implements ICache
{
  abstract function set(); 
  // ?! Разве не должна здесь уже присутствовать реализация метода интерфейса (пусть даже и пустышка)
  // и разве можно тут использовать abstract... насколько я помню пхп выдает ошибку
}
"Приемлимо ли использование метода generateUniqueKey родительского класса CCache в классе-потомке?"
Притянуто за уши. Пусть сторадж сам решает, как ему хранить ключи. К тому же md5() может привести к проблеме коллизий (маловероятно, но тем не менее).
Т.е. ты предлагаешь, чтобы каждый механизм кэширования имел свой метод получения уникального ключа? Но если устраивает какой-то один метод, то ведь правильнее реализовать его именно в классе-родителе, что собственно и сделано в коде-оригинале?

"return apc_store($this->generateUniqueKey($id)"
Зачем ты вообще дёргаешь этот метод generateUniqueKey? Есть какие-то проблемы с хранением данных в ключах равным $id как есть?
Ну, а где гарантия, что другой процесс не будет иметь то же самое название ключа? Ну вопрос в том, а приемлимо ли это вообще. Я просто рассматриваю конкретную реализацию. ЧТобы не быть голословным, то вот реализация класса CCache, тут - класса CApcCache, а тут класса CFileCache.

Все, что меня интересует, почему создаются дополнительные методы setValue, getValue, addValue и т.д., которые используются в реализации методов интерфейса ICache (set, get, add,...) и почему в классах-потомках переопределяются именно методы setValue, getValue,... вместо set, get,... Если нет никакого хотя бы мало мальски логичного ответа, то тогда обсуждение можно закончить и сослаться на "гениальность" автора, но ведь он [автор] при написании чем-то же руководствовался... причем вариант рабочий. Просто хочется понять о чем он думал... :)

zerkms, если не сложно мог бы ты набросать несколько строк кода, как ты это видишь? Может быть это сможет помочь мне... Т.е. напоминаю, мне нужна возможность использовать механизмы кэширования, в зависимости от наличия. Если apc - используем apc, есть memcache - используем memcache, иначе - по старинке используем файлы.
 

zerkms

TDD infected
Команда форума
"Так они уже объявлены абстрактыми, потому что это методы интерфейса (посмотри иерархию)"
Ну и зачем тогда вообще создавать пустышки?

"Т.е. ты предлагаешь, чтобы каждый механизм кэширования имел свой метод получения уникального ключа? Но если устраивает какой-то один метод, то ведь правильнее реализовать его именно в классе-родителе, что собственно и сделано в коде-оригинале?"
Эм, а зачем вообще этот механизм нужен?!?!?

"Ну, а где гарантия, что другой процесс не будет иметь то же самое название ключа?"
А где гарантия, что другой процесс не будет его также получать из md5()? Ради чего вообще все эти па?
 

zerkms

TDD infected
Команда форума
"zerkms, если не сложно мог бы ты набросать несколько строк кода, как ты это видишь?"
То что у тебя есть - вполне себе ок, только:
1. Убрать ненужные методы-пустышки
2. Убрать md5 из класса, работающего с APC (либо объяснить, зачем оно там всё таки нужно).
 

Bambino

Новичок
"Так они уже объявлены абстрактыми, потому что это методы интерфейса (посмотри иерархию)"
Ну и зачем тогда вообще создавать пустышки?
Гм... ты прав. Я только что еще раз проверил. Попутал с абстрактными методами в абстрактном классе, когда "пых" требовал реализовывать метод. Спасибо. :)

"Т.е. ты предлагаешь, чтобы каждый механизм кэширования имел свой метод получения уникального ключа? Но если устраивает какой-то один метод, то ведь правильнее реализовать его именно в классе-родителе, что собственно и сделано в коде-оригинале?"
Эм, а зачем вообще этот механизм нужен?!?!?
Ну насколько я понял это нужно для предотвращения использования куска памяти несколькими процессами. Например, у меня 2 скрипта, у каждого есть переменная $key, которая несет различный функционал, если я буду сохранять переменную в кэше, то есть вероятность, что какой-то из скриптов получит неверное значение по запросу apc_fetch(). Я ошибаюсь?

"Ну, а где гарантия, что другой процесс не будет иметь то же самое название ключа?"
А где гарантия, что другой процесс не будет его также получать из md5()? Ради чего вообще все эти па?
Ну так там хэш строится как md5($appname.$key), т.е. принимаем, что вероятность того, что 2 разных человека назовут свое ПО одинаково... :) Вообщем, я тебя услышал. В данном случае способ получения идентификатора для хранения в кэше не критичен.
 

Bambino

Новичок
То что у тебя есть - вполне себе ок, только:
1. Убрать ненужные методы-пустышки
Угу.

2. Убрать md5 из класса, работающего с APC (либо объяснить, зачем оно там всё таки нужно).
Да можно и убрать. Просто меня интересовала именно эта реализация классов кэширования в Yii, а там md5 присутствует.
 

zerkms

TDD infected
Команда форума
Ну насколько я понял это нужно для предотвращения использования куска памяти несколькими процессами. Например, у меня 2 скрипта, у каждого есть переменная $key, которая несет различный функционал, если я буду сохранять переменную в кэше, то есть вероятность, что какой-то из скриптов получит неверное значение по запросу apc_fetch(). Я ошибаюсь?
Ну так и добавь к имени префикс с именем приложения. "appname_" . $id, без всяких md5

Ну так там хэш строится как md5($appname.$key), т.е. принимаем, что вероятность того, что 2 разных человека назовут свое ПО одинаково.
Эээээээ, нет, не надо тут нам :) У тебя в коде везде: return md5($key);

Итого: зачем md5(), если можно оставить как есть и оставить ключи читаемыми.
 

Bambino

Новичок
Ну так и добавь к имени префикс с именем приложения. "appname_" . $id, без всяких md5
Эээээээ, нет, не надо тут нам :) У тебя в коде везде: return md5($key);
Ну... я же не полный код приводил :)... просто чтобы меньше писать. Скорее всего ты прав насчет имен переменных. Думаю можно закруглиться... Спасибо.
 

Adelf

Administrator
Команда форума
Все, что меня интересует, почему создаются дополнительные методы setValue, getValue, addValue и т.д., которые используются в реализации методов интерфейса ICache (set, get, add,...) и почему в классах-потомках переопределяются именно методы setValue, getValue,... вместо set, get,... Если нет никакого хотя бы мало мальски логичного ответа, то тогда обсуждение можно закончить и сослаться на "гениальность" автора, но ведь он [автор] при написании чем-то же руководствовался... причем вариант рабочий. Просто хочется понять о чем он думал...
В родительском классе можно будет добавить какой-то функционал на set/get - методы. и он навесится на все потомки. Например логгинг...
 

zerkms

TDD infected
Команда форума
Adelf
Это делается через the decorator pattern, например.
 

Adelf

Administrator
Команда форума
zerkms
Делается. И так даже правильнее. Но данный вариант - более простой.
 

zerkms

TDD infected
Команда форума
А ещё проще написать 50 строк лапши, чем сидеть и "проектировать" иерархию классов, которую потом легко будет поддерживать и расширять :-P
 
Сверху