Я создаю веб-сайт, который по сути продает рекламные ролики. Т.е. кто-то может зарегистрироваться и купить баннерную рекламу, которая будет отображаться на главной странице, или он может купить рекламу там, где он получит свою страницу профиля. Я хочу сказать, что, хотя все объявления имеют общую функциональность, они различаются.
Для этого модель моего домена выглядит следующим образом: (упрощенно)
class Advert {
protected
$uID,
$startTime,
$traits = array();
public function __construct($_traits) {
$this->traits = $_traits;
}
public function getUID() { return $this->startTime; }
public function getStartTime() { return $this->startTime; }
public function setStartTime($_startTime) { $this->startTime = $_startTime; }
public function save() {
MySQLQuery 'UPDATE adverts SET startTime = $this->startTime WHERE uID = $this->uID';
foreach($this->traits as $trait) {
$trait->save($this->uID);
}
}
....
}
—
interface IAdvertTrait {
public function save($_advertUID);
}
—
class AdvertTraitProfile implements IAdvertTrait {
protected $url;
public function getURL() { return $this->url; }
public function setURL($_url) { $this->url = $_url; }
public function save($_advertUID) {
MySQLQuery 'UPDATE advertdata_profile SET url = $this->url WHERE advertUID = $_advertUID';
}
....
}
—
class AdvertTraitImage implements IAdvertTrait {
protected $image;
public function getImage() { return $this->image; }
public function setImage($_image) { $this->image = $_image; }
public function save($_advertUID) {
MySQLQuery 'UPDATE advertdata_image SET image = $this->image WHERE advertUID = $_advertUID';
}
....
}
На самом деле существует несколько классов AdvertTrait …, каждый из которых реализует IAdvertTrait.
Как вы можете видеть, если я создаю рекламу, как это:
$advert = new Advert(
array(
new AdvertTraitProfile(),
new AdvertTraitImage()
...
)
);
Я могу тогда сделать это:
$advert->save();
И вся необходимая информация будет сохранена в БД самой рекламой и каждым ее рекламным пакетом.
Используя этот метод, я могу создавать различные виды рекламы, просто передавая разные «черты». Однако, к моей проблеме — я понятия не имею, как мне следует манипулировать рекламой. Как показано в примере выше, на самом деле нет смысла создавать и рекламировать, а затем сразу же сохранять его.
Я хотел бы быть в состоянии это:
$advert->getStartTime(); # Works
$advert->getURL(); # Doesn't work of course, as the getURL method is encapsulated within a property of the Advert's 'traits' array
$advert->setImage('blah.jpg'); # Also does not work
Я не уверен, как сделать эти «внутренние» методы доступными.
Я мог бы просто создать отдельный класс ‘Advert’ для каждого вида рекламы, т.е.
AdvertProfile extends Advert {
$this->traitProfile = new AdvertTraitProfile();
public function getURL() { return $this->traitProfile->getURL(); }
...
}
AdvertImage extends Advert {
$this->traitImage = new AdvertTraitImage();
public function getImage() { return $this->traitImage->getImage(); }
...
}
AdvertProfileImage extends Advert {
$this->traitProfile = new AdvertTraitProfile();
$this->traitImage = new AdvertTraitImage();
public function getURL() { return $this->traitProfile->getURL(); }
public function getImage() { return $this->traitImage->getImage(); }
...
}
Но я чувствую, что это станет грязным; Мне нужно было бы продолжать создавать новые классы Advert для каждой комбинации характеристик, которые мне нужны, и каждый класс рекламы должен был бы сам определять свои методы признаков, чтобы их можно было вызывать из экземпляра объявления.
Я также запутался с шаблоном декоратора; поэтому вместо того, чтобы передавать эти классы ‘trait’ в конструктор Advert, я объединяю декораторы, например:
$ advert = new AdvertImageDecorator (new AdvertProfileDecorator (new Advert ()));
Однако для этого требуется, чтобы декораторы могли «искать» методы, которые не принадлежат им, с использованием method_exists и call_user_func_array, что просто кажется мне большим старым взломом. Плюс объединение множества декораторов вместе, как это, просто терзает меня.
Я также взглянул на правильные черты PHP, но я не думаю, что они мне помогут. Например, у каждого AdvertTrait есть метод save, который необходимо вызывать одновременно. Я полагаю, что для правильной черты мне потребуется выбрать только один метод «сохранения» из одной черты.
Может быть, я должен использовать простое старое наследование — но тогда я все равно буду создавать конкретные типы Advert, все из которых в конечном итоге наследуются от Advert. Однако я считаю, что это вызовет дополнительные проблемы; Т.е. я не смог бы сделать расширение AdvertWithProfileAndImageTraits как из AdvertWithProfileTraits, так и из AdvertWithImageTraits.
Кто-нибудь может предложить правильное решение этой головоломки? Возможно, есть другой шаблон дизайна, который я должен использовать.
Спасибо большое,
Дейв
Я бы пошел на декоратор подход.
Аннотация AdvertDecorator
класс может выглядеть так:
abstract class AdvertDecorator implements IAdvertTrait {
protected $child;
public function __construct($child=null) {
if(!$child) {
$child = new NullAdvert();
}
$this->child = $child;
}
/**
* With this function all calls to non existing methods gets catched
* and called on the child
*/
public function __call($name, $args) {
return call_user_func_array(array($this->child, $name), $args);
}
}
/**
* This class is for convenience so that every decorator
* don't have to check if there is a child
*/
class NullAdvert implements IAdvertTrait {
public function save($_advertUID) {
// do nothing
}
}
Вместо NullAdvert
класс вы можете использовать BaseAdvert
класс, который реализует всю вашу основную логику рекламы (как вы сделали в Advert
учебный класс).
Теперь все остальные классы выходят из этого AdvertDecorator
учебный класс:
class AdvertProfile extends AdvertDecorator {
public function getProfileURL() { ... }
public function save($_advertUID) {
// save own advert
MySQLQuery 'UPDATE advertdata_profile SET url = $this->url WHERE advertUID = $_advertUID';
// save advert of child
$this->child->save($_advertUID);
}
}
class AdvertImage extends AdvertDecorator {
public function getImage() { ... }
public function save($_advertUID) {
// save own advert
MySQLQuery 'UPDATE advertdata_image SET image = $this->image WHERE advertUID = $_advertUID';
// save advert of child
$this->child->save($_advertUID);
}
}
class AdvertProfileImage extends AdvertDecorator {
public function getProfileImageURL() { ... }
public function getProfileImage() { ... }
public function save($_advertUID) {
// save own advert ...
// save advert of child
$this->child->save($_advertUID);
}
}
Вы можете использовать это так:
$advert = new AdvertProfile();
$advert = new AdvertImage($advert);
$advert = new AdvertProfileImage($advert);
// save all advert components
$advert->save('uid');
// call functions
$advert->getProfileURL();
$advert->getImage();
$advert->getProfileImageURL();
$advert->getProfileImage();
Это структура ИМХО очень гибкий. Каждый компонент рекламы может быть добавлен к текущей рекламе в произвольном порядке. Кроме того, вы можете расширить это решение с помощью составного шаблона и добавить AdvertComposite
так что вы можете сгруппировать свои компоненты. Вы даже можете добавить несколько рекламных компонентов одного и того же вида в одну рекламу (для этого вам нужно немного изменить методы).
Других решений пока нет …