【问题标题】:How to add functionality to an object in PHP如何在 PHP 中为对象添加功能
【发布时间】:2014-11-11 12:03:00
【问题描述】:

我正在创建一个主要销售广告“广告位”的网站。即,某人可以注册并购买要在主页上显示的横幅广告,或者他们可以在获得自己的个人资料页面的地方购买广告。我的观点是,虽然所有广告都有共同的功能,但它们确实不同。

为此,我的域模型如下所示:(简化)

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 保存到数据库中。

使用这种方法,我可以通过传入不同的“特征”来创建不同类型的广告。但是,对于我的问题-我不知道应该如何操作广告。按照上面的例子,创建广告然后立即保存是没有意义的。

我希望能够做到这一点:

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

    ...
}

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

我也弄乱了装饰器模式;因此,我没有将这些“特征”类传递给 Advert 的构造函数,而是将装饰器链接在一起,如下所示:

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

但是,这要求装饰器能够使用 method_exists 和 call_user_func_array 来“查找”不属于它们的方法,这对我来说似乎是一个老套路。再加上像这样将大量装饰器链接在一起,这让我很恼火。

我也看过适当的 PHP Traits,但恕我直言,我认为它们不会帮助我。例如,每个 AdvertTrait 都有一个“保存”方法,所有这些方法都需要同时调用。我相信一个合适的 Trait 需要我从一个 trait 中选择一个“保存”方法。

也许我应该使用普通的旧继承 - 但我仍然会创建特定类型的 Advert,所有这些最终都继承自 Advert。但是我相信这会导致更多问题;即我将无法从 AdvertWithProfileTraits 和 AdvertWithImageTraits 扩展 AdvertWithProfileAndImageTraits。

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

非常感谢, 戴夫

【问题讨论】:

  • 对于投反对票的人,如果你给我一个理由,我会很乐意编辑我的问题,如果它在某种程度上是错误的。
  • 我投了赞成票。那些开车经过的,“我也是”的投票者真的让我很生气。我相信他们会把人们从网站上赶走。现在你回到了 0,你可能会得到一些有用的答案,我认为这是你应该得到的,因为你发布了这么多代码。 IMO 做得更好的唯一方法是在 phpfiddle.org 上发布您的代码,以便我们可以使用它。祝你好运!
  • @Mawg 感谢您的友好评论;我认为这只是一些随机的。
  • 而且,看看那个,你回到了加一和一个接受的答案。很高兴帮助了一点。我希望你在 S.O 上取得更大的成功,也希望你也能帮助别人

标签: php oop domain-driven-design


【解决方案1】:

我会选择 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();

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

【讨论】:

  • 非常感谢;我现在将在我自己的代码中实现它。一旦一切正常,我将发表另一条评论并接受答案。
  • 我已经实现了。这确实非常灵活;多么棒的设计模式——我认为我之前使用装饰器的尝试是错误的,因为它运行得非常好!非常感谢!
猜你喜欢
  • 2017-04-14
  • 2012-03-17
  • 1970-01-01
  • 1970-01-01
  • 2018-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-24
相关资源
最近更新 更多