oop — Как добавить функциональность к объекту в переполнении стека

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

Для этого модель моего домена выглядит следующим образом: (упрощенно)

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.

Кто-нибудь может предложить правильное решение этой головоломки? Возможно, есть другой шаблон дизайна, который я должен использовать.

Спасибо большое,
Дейв

1

Решение

Я бы пошел на декоратор подход.
Аннотация 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 так что вы можете сгруппировать свои компоненты. Вы даже можете добавить несколько рекламных компонентов одного и того же вида в одну рекламу (для этого вам нужно немного изменить методы).

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]