Как организовать семейство классов?

AnrDaemon

Продвинутый новичок
Это снова я… я всё ещё мучаю свою семейку классов. Где-то я её уже приводил, но мне лень искать, так что опишу сначала.

Есть семейство классов, каждый является уточнением предыдущего, со слегка большим набором параметров, чем родительский класс.
Грубо, это
Галактика -> Система -> Звезда -> Планета
У всех классов есть общие методы (__toString, implements Countable, ArrayAccess, такая хрень).

Сначала я так и поступил, сделал абстрактнй класс, описал там общую логику, и поунаследовал от него всех потомокв, но…
Но захотелось более строгой валидации входных параметров некоторых функций, таких как XXX::addChild(). Если определять addChild в абстранктном классе ( function XXX::addChild(self $child); ), то либо галактики будут чилдами систем, либо придётся наворачивать дополнительные проверки внутри метода в каждом классе.
Есть правильное решение этой проблемы? Может, вынести эти функции в интерфейсы?
И делать что-то типа
PHP:
interface ISolarsystem {
  function addChild(ICelestial $child);
}

class Solarsystem extends XXX implements ISolarsystem {
  function addChild(ICelestial $child) {};
};
 

Redjik

Джедай-мастер
хм, а с чего ты вообще взял, что вся эта цепочка наследуется от одного класса?
ты заставляешь барбару грустить
 

Sufir

Я не волшебник, я только учусь
Для чего здесь вообще наследование? Интерфейсы и трейты.
 

Adelf

Administrator
Команда форума
Галактика -> Система -> Звезда -> Планета - вот вообще не подходит под иерархию. Планета это не звезда. Даже если у них иногда общее поведение. Такие мелкие поблажки себе всегда приводят к большим проблемам в будущем.
Но допустим у тебя другое и такая иерархия подходит.
Попробую посоветовать немного. Юзай правильные имена методов.
PHP:
interface ISolarsystem {
 function addCelestial(ICelestial $child);
}
Как дивиденд ты получишь отсутствие конфликтов, если будут обьединены два интерфейса в каком-нибудь классе.

Очень спорный совет, но таки дам. Иерархия.
abstract class AbstractSpaceObject
|
abstract class AbstractPlanet -> final class Planet
|
abstract class AbstractStar -> final class Star
...

Т.е. ни одного наследования от реального класса. Об этом принципе как-то намекнул здесь Вурдалак. Мне понравился и до сих пор везде юзаю(хотя и раньше юзал неосознанно).
 

AnrDaemon

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

AnrDaemon

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

Итак, исходные данные.

Каждая система имеет одну главную звезду.
Вокруг звезды могут вращаться другие звёзды, газовые гиганты и планеты.
Вокруг газового гиганта могут вращаться планеты и спутники.
Вокруг планеты могут вращаться спутники, если эта планета не вращается вокруг газового гиганта.

XXX::addChild() на самом деле несущественен.
Проблема с XXX::setParent().
Вот тут должны проверяться входные типы, причём не только самого потенциального парента, но иногда и парента того парента.

Пока надумалось что-то такое.

PHP:
<?php

abstract class Celestial {
  private $parent;
  function getParent() {
    return $this->parent;
  }
}

// Prime celestials (Stars, Gas giants)
interface IPrime {
  function setParent(IStar $parent);
}

// Planets (largely)
interface ICelestial {
  function setParent(IPrime $parent);
}

// Stars
interface IStar {
}

interface IPlanet {
}

interface ISatellite {
  function setParent(IPlanet $parent);
}

class Star extends Celestial implements IStar, IPrime {
  function setParent(IStar $parent)
  {
    $this->parent = $parent;
  }
}

class GasGiant extends Celestial implements IPlanet, IPrime {
  function setParent(IStar $parent)
  {
    $this->parent = $parent;
  }
}

class Planet extends Celestial implements IPlanet, ICelestial {
  function setParent(IPrime $parent)
  {
    $this->parent = $parent;
  }
}

class Satellite extends Celestial implements IPlanet, ISatellite {
  function setParent(IPlanet $parent)
  {
    if($parent->getParent() instanceof IPlanet)
      throw new Exception('Can\'t nest this orbit, please recheck your data source.');
    $this->parent = $parent;
  }
}
 

WMix

герр M:)ller
Партнер клуба
ааааа,... зачем?
я типо про композиции... типо тело имеет..
 

AnrDaemon

Продвинутый новичок
Да, всё замечетельно, конструкторы. Но реальность имеет свои правила.
У меня может НЕ БЫТЬ парента, который я бы мог скормить конструктору. Просто прислали скан одной планеты. (Или вообще одного астероидного пояса.)

P.S.
Про геттеры и сеттеры вообще можно спорить до бесконечности.
В языке, который не даёт возможности управления присвоением свойств напрямую, использовать сеттеры придётся, явно или неявно - это другой вопрос.
Геттеры - это сахар, в 99% случаев, но последний 1% заставляет использовать и их тоже, для единообразия кода.
В моём примере, мне НУЖЕН геттер, чтобы получить главную звезду системы. (IStar with parent = NULL, для чего надо взять любого чилда с типом IStar и проследить его парентов до топа.)
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
это вам в школе задание такое дали?
 

AnrDaemon

Продвинутый новичок
это вам в школе задание такое дали?
:) Спасибо. Я аж помолодел…
Просто откажитесь от сеттеров и жизнь станет легче.
If you don't understand both sides of an issue, you cannot make an intelligent choice; in fact, if you don't understand all the ramifications of your actions, you're not designing at all. You're stumbling in the dark. It's not an accident that every chapter in the Gang of Four's Design Patterns book includes a "Consequences" section that describes when and why using a pattern is inappropriate.
Из той самой статьи.
 

AnrDaemon

Продвинутый новичок
@Lionishy, кто вообще эту статью писал?… Дурдом через слово.
Data abstraction - зашибись, а у меня вот класс - value object, содержащий коллекцию.
Каковы мои действия для получения доступа к элементам коллекции?…
Вообще у автора позиция "класс ничего не должен делать, потому что любое действие с классом раскрывает его внутреннюю сущность, и ведёт к потенциальным проблемам".
Кретинизм.
 

Вурдалак

Продвинутый новичок
А нельзя ли начать с задачи, какой функционал требуется? Потому что сеттеры и геттеры — это не функционал.
 

AnrDaemon

Продвинутый новичок
@Вурдалак, имеется в виду тема топика или обсуждение статьи? А то мы уже слегка в оффтопик съехать успели.
 
Сверху