Абстрактный класс - детский тип

Я пытаюсь разработать некоторую иерархию классов, и я «застрял» в этой части.

Допустим, у меня есть следующие классы

abstract class Video 
{
    const TYPE_MOVIE = 1;
    const TYPE_SHOW  = 2;

    abstract public function getTitle();
    abstract public function getType();
}

class Movie extends Video 
{
    // ...

    public function getType() 
    {
        return self::TYPE_MOVIE;
    }
}

class Show extends Video 
{
    // ...

    public function getType() 
    {
        return self::TYPE_SHOW;
    }
}

В другой части системы у меня есть класс (Parser), который инкапсулирует создание фильмов и объектов шоу и возвращает obj. клиенту.

Вопрос: Как лучше всего получить тип объекта. возвращается из парсера/фабричного класса, так что клиент может сделать что-то вроде

$video = $parser->getVideo('Dumb and Dumber');

echo $video->getTitle();

// Way 1
if($video->getType == 'show') {
    echo $video->getNbOfSeasons();
}

// Way 2
if($video instanceof Show) {
    echo $video->getNbOfSeasons();
}

// Current way
if($video->getType == Video::TYPE_SHOW) {
    echo $video->getNbOfSeasons();
}

Есть ли лучший способ, чем мое решение (читай: мое решение отстой?)?


person Marko Jovanović    schedule 23.04.2012    source источник
comment
+1 Прекрасный пример для объяснения любого вопроса...   -  person OM The Eternity    schedule 23.04.2012


Ответы (4)


Есть ли лучший способ, чем мое решение (читай: мое решение отстой?)?

Ваше решение само по себе не отстой. Однако всякий раз, когда кто-то пытается определить подтип для выполнения каких-либо действий, я склонен задаваться вопросом; Почему? Этот ответ может быть немного теоретическим и, возможно, даже немного педантичным, но вот.

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

  1. Вы пытаетесь выполнить действие на основе подтипа. Обычно можно было бы выбрать перенос этого действия в сам класс, а не «вне» класса. Это также делает код более управляемым.

  2. Вы пытаетесь решить проблему, которую создали сами, используя наследование, где наследование не гарантируется. Если есть родитель и есть дети, каждый из которых должен использоваться по-разному, каждый из которых имеет разные методы, просто перестаньте использовать наследование. Они не одного типа. Фильм - это не то же самое, что сериал, даже близко. Конечно, вы можете видеть оба по телевизору, но на этом сходство заканчивается.

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

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

person Berry Langerak    schedule 23.04.2012
comment
Я полностью согласен с тобой. 2. правильно указывает на разницу между расширением и реализацией. Фильм и сериал могут следовать общему интерфейсу, чтобы их можно было воспроизводить на телевизоре, но они могут не иметь ничего общего. - person Boris Guéry; 23.04.2012
comment
@Berry Langerak Дело в том, что клиент запрашивает видео, передавая имя какого-то фильма, сзади я ищу / извлекаю / анализирую данные, а затем создаю и заполняю нужный объект. в зависимости от извлеченных данных (это может быть фильм, телешоу, а в будущем, возможно, и какой-то дополнительный тип), дело в том, что мне все равно, какой тип объекта. Я создаю, но клиент, который запросил объект. заботы. Ему нужно знать, какие методы он может вызывать. - person Marko Jovanović; 23.04.2012
comment
... Таким образом, даже если фильм и телешоу не имеют одного и того же базового класса, клиенту все равно нужно проверить, какой объект. (тип видео) он получил. Может быть, я оберну все это в классе Facade. В любом случае, вы заставили меня остановиться и подумать обо всем с самого начала :) - person Marko Jovanović; 23.04.2012
comment
@MarkoJovanovic Если для объектов такое много разных методов, наследование — неправильный инструмент. У вас просто две разные сущности ;) - person Berry Langerak; 23.04.2012

Я бы пошел по пути 2. Он абстрагирует вас от необходимости добавить еще одну константу в Video на случай, если вы захотите добавить class SoapOpera extends Show (например).

Со способом № 2 вы меньше зависите от констант. Любая информация, которую вы можете получить без жесткого кодирования, означает, что в будущем, вероятно, возникнет меньше проблем, если вы захотите расширить. Прочтите об усилить слабую связь.

person Madara's Ghost    schedule 23.04.2012
comment
Согласен - единственный плюс в том, чтобы делать вещи так, как он делает их сейчас, заключается в том, что это позволяет добавить дополнительный уровень управления-причудливости, если у вас есть клиентские разработчики, которым не разрешено изменять абстрактный класс - использование констант в абстрактном может пусть другие разработчики знают, как им разрешено расширять базу. - person CD001; 23.04.2012

Я думаю, что второй вариант лучше, используя instanceof. В целом это характерно для всего ООП-дизайна, а не только для PHP.

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

Если оставить базовый класс как есть при добавлении новых производных классов, это способствует повторному использованию кода.

person Brady    schedule 23.04.2012

Если есть «правильный» способ, и все, конечно, субъективно в кодировании (если это не влияет отрицательно на производительность/ремонтопригодность;)), то это второй путь, как указали «Правда» и «Брэди». .

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

Например:

$oKillerSharkFilm = Video::factory(Video::MOVIE, 'Jaws', 'Dundundundundundun');
$oKillerSharkDocumentary = Video::factory(Video::DOCUMENTARY, 'Jaws', 'A Discovery Shark Week Special');

Конечно, недостатком является то, что вы должны поддерживать «допустимые расширения» в абстрактном классе.

Вы все еще можете использовать метод instanceof, как показано в вашем вопросе, и поддерживать список допустимых расширений в аннотации преимущественно для исправления управления/типа.

person CD001    schedule 23.04.2012