如何在PHP中为对象添加功能


How to add functionality to an object in PHP

我正在创建一个网站,主要销售广告"点"。也就是说,有人可以注册并购买横幅广告,显示在主页上,或者他们可以在自己的个人资料页面上购买广告。我的观点是,尽管所有的广告都有共同的功能,但它们确实不同。

为了实现这一点,我的域模型如下所示:(简化)

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本身及其每个AdvertTraits保存到DB中。

使用这种方法,我可以通过传递不同的"特征"来创建不同类型的广告。然而,我的问题是——我不知道该如何操作广告。根据上面的例子,创建和广告然后立即保存是没有意义的。

我希望能够做到这一点:

$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

我不知道如何让这些"内部"方法变得可访问

我可以为每种广告创建一个不同的"广告"类,即:

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(); }
    ...
}

但我觉得这会变得一团糟;我需要为我需要的每一种特征组合不断创建新的"广告"类,每个广告类都需要自己定义其特征方法,以便可以从广告的实例中调用它们。

我还搞砸了装饰器模式;因此,我没有将这些"trait"类传递给Advert的构造函数,而是将装饰器链接在一起,如:

$advert=new AdvertImageDecorator(new AdvertProfileDecorator,new advert());

然而,这需要装饰器能够使用method_exists和call_user_func_array来"查找"不属于它们的方法,这对我来说似乎是一个古老的大技巧。再加上像这样将大量装饰器链接在一起,这让我很恼火。

我也看了一些合适的PHP特性,但IMVHO我认为它们对我没有帮助。例如,每个AdvertTrait都有一个"save"方法,所有这些方法都需要同时调用。我相信一个合适的特质需要我从一个特质中选择一个"保存"方法。

也许我应该使用普通的旧继承,但我仍然会创建特定类型的Advert,所有这些最终都继承自Advert。然而,我认为这会引起进一步的问题;即,我将无法使AdvertWithProfileAndImageTraits从AdvertWithProfileTraits和AdvertWillImageTraits扩展。

有人能为这个难题提供一个合适的解决方案吗?也许我应该使用另一种设计模式。

非常感谢,Dave

我会选择Decorator方法
抽象的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
    }
}

您可以使用BaseAdvert类来代替NullAdvert类,它实现了所有基本的广告逻辑(就像您在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();

这种结构是IMHO非常灵活的。每个广告成分都可以按任意顺序添加到当前广告中。此外,您还可以使用复合模式扩展此解决方案,并添加AdvertComposite,以便对组件进行分组。你甚至可以在一个广告中添加多个相同类型的广告成分(为此,你必须稍微改变一下方法)。