Наследование vs Агрегация

HraKK

Мудак
Команда форума
Наследование vs Агрегация

У нас с Long завязался идеалогический спор на тему ООП :)
Есть 2 класса.
PHP:
/**
 * Класс TheRegistry.
 * 
 * Реализует паттер Registry - хранилище. Данный класс должны использовать все остальные
 * классы системы, которым необходимо хранить данные.
 * 
 */
class TheRegistry{
    /**
     * Хранилище данных.
     * 
     * В переменной сохраняются данные в виде элементов ассоциативного массива:
     *  - ключ - идентификатор сохраняемых данных;
     *  - значение элемента - сами данные.
     *
     * @var array
     */
    protected $store = array();
    /**
     * Метод, реализующий сохранение данных $object с пометкой $label.
     * 
     * В качестве данных могут выступать различные типы объектов, разрешенных в PHP.
     *
     * @param string $label Метка данных.
     * @param resource $object Данные для сохранения.
     */
    public function register($label, $object) {
        if(!isset($this->store[$label])){
            $this->store[$label] = $object;
        }
    }
    /**
     * Удаляет данные, сохраненые с меткой $label.
     *
     * @param string $label Метка данных.
     */
    public function unregister($label) {
        if(isset($this->store[$label]))
        {
            unset($this->store[$label]);
        }
    }
    /**
     * Возвращает данные, сохраненые с метой $label
     *
     * @param string $label Метка данных.
     * @return resource Сохраненые данные.
     */
    public function get($label) {
        if (is_array($label)) {
            $tmp = &$this->store;
            foreach ($label as $v) {
                if (!isset($tmp[$v])) {
                    return false;
                }
                else {
                    $tmp = &$tmp[$v];
                }
            }
            return $tmp;
        }
        elseif(isset($this->store[$label])){
            return $this->store[$label];
        }
        return false;
    }
    /**
     * Проверяет установлены ли данные для метки $label.
     *
     * @param string $label Метка данных.
     * @return boolean
     */
    public function has($label) {
        if (is_array($label)) {
            $tmp = &$this->store;
            foreach ($label as $v) {
                if (!isset($tmp[$v])) {
                    return false;
                }
                else {
                    $tmp = &$tmp[$v];
                }   
            }
            return true;
        }
        elseif(isset($this->store[$label])){
            return true;
        }
        return false;
    }
}

/**
 * Класс, описывающий подставку под маркеры
 * Реализует паттерн Singleton (предполагается, что на доске имеется только одна 
 * подставка под маркеры) и использует паттерн Регистр.
**/

final class cMarkersBox extends TheRegistry {
    /**
     * Ссылка на $this.
     * 
     * Используется для реализации паттерна Singleton
     *
     * @static 
     * @access private
     */
    static private $thisInstance = null;

    /**
     * Конструктор для реализации паттерна Singleton.
     * @access private
     */
    private function __construct() { }
    /**
     * Метод создания Singleton.
     * 
     * Метод создает и(или) возвращает единственный объект класса cMarkersBox. 
     *
     * @return cMarkersBox
     */
    static public function getInstance() {
        if (self::$thisInstance == null) {
            self::$thisInstance = new cMarkersBox();
        }
        Log::add('Повесили на доску подставку для маркеров');
        return self::$thisInstance;
    }
    /**
     * Добавляет на полочку новый маркер
     *
     * @param cMarker $m
     */
    public function addMarker(cMarker $m) {
        parent::register( $m->getColor(), $m );
        Log::add('Добавили маркер цвета ' . $m->getColor() . ' и типа ' . $m->getType());
    }

}
Мое мнение что в данной реализации это выглядит как: человек сидящий внутри холодильника и полученную еду распихивающий по полкам. Вместо того чтобы открывать его и класть куда надо.
Long говорит что тут как агрегация так и наследование можно применить, но наследование удобнее.
Я же считаю что это грубейшее нарушение основ ООП. Кто прав?:)
 

Scud

Новичок
Мое мнение - классы всегда должны быть final, либо в очень редких случаях abstract так что никакого наследования, кроме очень редких случаев. Нужно вам в классе поведение Registry выделите протокол Regestry реализуете его в классе, если хотите создаете внутри класса базовую реализацию интерфейса Registry и делегируйте соответствующие методы ей.

-~{}~ 10.02.09 02:58:

Так что
interface TheRegistryProto

final class TheRegistry implements TheRegistryProto

и

final class cMarkerBox implenents TheRegistryProto
 

Scud

Новичок
Если я проектирую класс для наследования, то я не только ограничен рамками реализуемого интерфейса, но я ещё и ограничиваю себя набором защищённых методов. После публикации класса я не имею права их менять - это очевидно, и не удобно.
 

HraKK

Мудак
Команда форума
protected И private знаешь такое?)
а полиморфизм или переопределение?)
 

zerkms

TDD infected
Команда форума
про код: имхо наследование там совсем лишнее.
причины: смысл наследования теряется определением частных сеттеров/геттеров. с их наличием родительские методы по установке/получению данных использоваться не будут. я за аггрегацию.

по поводу "классы всегда должны быть final": final как раз затрудняет использование одного из главных принципов ооп - наследования. в случае final разработчик родительского класса зачем-то начинает думать за разработчика класса-наследника.
единственная ситуация, когда final считаю оправданным - реализация The Template Method Pattern.
 

Scud

Новичок
Hrack - знаю. Полиморфизм очень хорошо, переопределение должно быть только при наследовании от абстрактных классаов, так как абстрактный класс специально разработан для того чтобы от него наследовались - суть почти такой же контракт разработчика с пользователем как и интерфейс, т.е. если ввели, то затем менять никак нельзя.

zerkms
Наследование классов это проклятие OOP, наследоваться должны только абстрактные типы данных - суть интерфейсы.
 

zerkms

TDD infected
Команда форума
Наследование классов это проклятие OOP, наследоваться должны только абстрактные типы данных - суть интерфейсы.
"должны". скажите это апологетам ООП.

Hrack - знаю. Полиморфизм очень хорошо, переопределение должно быть только при наследовании от абстрактных классах, так как абстрактный класс специально разработан для того чтобы от него наследовались - суть почти такой же контракт разработчика с пользователем как и интерфейс, т.е. если ввели, то затем менять никак нельзя.
бред. абстрактные методы абстрактного класса не переопределяют, а реализовывают. это разные понятия. да и никто его менять не собирался. изменение поведения/реализации никак не связано с изменением интерфейса.
 

Scud

Новичок
никто не говорил про абстрактные методы абстрактных классов, речь шла про переопределение, а не про реализацию, я и ответил про переопределение.

-~{}~ 10.02.09 03:46:

и потом я обычно не брежу когда говорю, следите за словами :). В русском языке есть такие выражения как "не согласен" или "вы не правы".
 

Long

Новичок
zerkms, зачем мне явно создавать объект хранилища? тем более, что хранилище маркеров, по своей сути, является расширением базового хранилища (в этом примере опущены некоторые специфичные для объекта MarkersBox методы).
 

Scud

Новичок
HraKK
Во первых ты хам, во вторых я высказал свою точку зрения.
 

zerkms

TDD infected
Команда форума
Long
я в ответе сделал предположение (потому как этой инфы вы не дали), что методы базового класса использоваться не будут, а вся работа с данными будет проводиться только через частные реализации геттеров/сеттеров.

продолжение дальнейшей дискуссии вижу только после подтверждения или опровержения моей гипотезы (выделена в прошлом абзаце).

-~{}~ 10.02.09 10:55:

interface TheRegistryProto

final class TheRegistry implements TheRegistryProto

и

final class cMarkerBox implenents TheRegistryProto
набросай, пожалуйста, примерный (хотя бы псевдо-) код этого интерфейса и этих двух классов.
 

Long

Новичок
чего-то в 4 утра я уже слабо соображаю :) методы наследника? или методы базового класса?
 

Scud

Новичок
Кстати в той реализации что дана в первом сообщении я могу в cMarkerBox напихать чего угодно, а не только cMarker. Если я правильно понял такое поведение не желательно, так что наследование не катит, а вот агрегация - вполне.
 

HraKK

Мудак
Команда форума
zerkms
я в ответе сделал предположение (потому как этой инфы вы не дали), что методы наследника использоваться не будут, а вся работа с данными будет проводиться только через частные реализации геттеров/сеттеров.
Верное предположение. И я доказываю что MarkersBox это никакое не хранилище - не морозильник при холодильнике, а именно человек который умеет класть в холодильник.
 

Long

Новичок
Scud, "пихай" - на то оно и хранилище :) ты в реальности можешь положить на полочку кроме маркеров еще и тряпочку для стирания? можешь. :)
 

Scud

Новичок
Хорошо, тогда как поведёт себя клиент cMarkerBox, если я в cMarkerBox положу объект отличного от cMarker класса, а ключём у него сделаю правильное для cMarker::getColor() значение?
 

zerkms

TDD infected
Команда форума
эм, ну если будут только конкретные сеттеры/геттеры, то я за аггрегацию. потому как:
1. наследование тут не нужно.
2. аггрегация добавит гибкости в реализацию, позволив в будущем без проблем заменить хранилище данных на другое, нежели память.

ты в реальности можешь положить на полочку кроме маркеров еще и тряпочку для стирания? можешь.
нуууу.... мы же программисты! а программисты решают те задачи, которые конкретно сейчас стоят перед нами и не пишем ни строки на будущее. (это в той или иной мере пропагандируют Getting Real и Фаулер) С ними согласен - практика расчудесная :)
 
Сверху