4

提前感谢您的帮助和指导。我终于从线性编程切换到 OOP。我正在上我的第一堂课,我可以使用一点方向。我的第一堂课是具有以下属性的画廊

class Gallery
    {
        //Gallery Name
        public $galleryID;
        public $galleryName;

        //Client Name
        public $clientName;

        //Gallery Options
        public $bg_color;
        public $albumAgreement;
        public $maxChanges;
        public $sharing_on;

        //Revisions
        public $revisions;
}

我的输出看起来像:

Gallery Object
(
    [galleryID] => 
    [galleryName] => 
    [clientName] => 
    [bg_color] => 
    [albumAgreement] => 
    [maxChanges] => 
    [sharing_on] => 
    [revisions] => 
)

我的下一步是我想让“修订”成为一个对象,以便我的输出看起来像

Gallery Object
(
    [galleryID] => 
    [galleryName] => 
    [clientName] => 
    [bg_color] => 
    [albumAgreement] => 
    [maxChanges] => 
    [sharing_on] => 
    [revisions] => Revisions Object (
        [revisionID] =>
        [revisionName] =>
    )
)

对于这样的事情,我应该朝着什么方向前进,这门课会是什么样子?

谢谢

4

4 回答 4

22

这更像是一个长篇评论,因为它解释了你的困境的根源,但没有提供任何解决方案。

OOP 第 1 课:仅仅因为您正在使用类并不意味着您正在编写面向对象的代码。

对象中的公共属性很少有很好的用例。让我们看一下OP的示例:

class Gallery {
    public $galleryID;
    public $galleryName;
    // ...
}

将我们的属性定义为public之后,以下两段代码有何不同?

$gallery = new Gallery;
$gallery->galleryId = 42;
$gallery->galleryName = 'some name';

// vs:

$gallery = array(
    'galleryId' => 42,
    'galleryName' => 'some name'
);

如果您说“它们根本没有什么不同”,那么您是对的。事实上,基于对象的代码会因为new. 还有一些其他因素,例如传递对对象的引用而不是复制新数组的能力,但这些不会影响这种特殊情况。

OOP 第 2 课:对象是一个黑盒子

创建一个只是可变属性集合的对象的问题在于,您的其余代码完全了解该对象内部发生的情况。让我们谈谈为什么这很糟糕......

在复杂性方面,人类并不是很好。好的软件旨在通过将功能封装到离散单元中来部分降低复杂性。在这种情况下,我们希望将“图库”实体的所有逻辑封装到Gallery类中。作为领域驱动设计 (DDD) 方法的一部分,这很有意义。我们要做的是Gallery与外界隔绝;我们希望它的内部实现对我们的其余代码是不透明的。我们应用程序的其余部分不应该知道或关心这些Gallery功能是如何工作的,只要它按预期工作即可。这里的额外好处是我们可以专注于让画廊按照它应该的方式工作,然后忘记它。我们不必被迫记住如何Gallery与一个Image或一个一起工作Revision. 这种松耦合是面向对象设计中最强大的工具之一。

虽然它可能适用于非常小的规模,但不可能同时将整个应用程序的逻辑保持在您的脑海中。不管你有多聪明,我们的大脑只是没有足够的内存。

回到代码,如果我们的应用程序代码知道如何Gallery为自己分配一个名称,那么我们已经允许“画廊”的逻辑泄漏到程序的其余部分。当我们决定要在分配新画廊名称时对其进行验证时会发生什么?我们现在必须将验证逻辑放在我们指定画廊名称的代码中的任何地方,因为我们还没有隔离关于“画廊性”这个抽象概念的所有内容。更好的设计是将Gallery属性分配封装在对象本身中:

class Gallery {
    private $galleryId;
    private $name;
    public function setName($name) {
        $this->name = $name;
    }
    public function getName($name) {
        return $this->name;
    }
}

如果我们以这种方式构建我们的类,当我们需要为画廊指定名称时,我们总是有一个单一的入口点。现在,当我们对画廊的需求在未来发生变化时(他们将会),我们所有的应用程序代码——对画廊名称分配背后的逻辑视而不见——与破损隔离开来。我们只需向我们的名称设置器添加一个新方法,并在我们的程序中创建最小的动荡:

class Gallery {
    private $galleryId;
    private $name;
    public function setName($name) {
        $this->validateName($name);
        $this->name = $name;
    }
    private function validateName($name) {
        if (!preg_match('/^[a-z]+$/', $name)) {
            throw new Exception;
        }
    }
    public function getName($name) {
        return $this->name;
    }
}

解决 OP

要回答如何将封装Revision对象表示为更高级别Gallery实例的属性的问题,我们需要一些上下文。看来 OP 试图做的是模型域实体,这些实体将被写入后端持久层(例如数据库、平面文本文件等)并从中检索。

贫血域模型是处理这个问题的一种方法,但是它通常被认为是一种反模式。马丁福勒写道:

贫血域模型的基本症状是乍一看它看起来像真实的东西。有对象,许多以领域空间中的名词命名,这些对象与真正的领域模型所具有的丰富关系和结构相关联。当您查看行为时,问题就来了,您会意识到这些对象几乎没有任何行为,这使得它们只不过是成袋的 getter 和 setter。实际上,这些模型通常带有设计规则,即不要将任何域逻辑放入域对象中。相反,有一组服务对象捕获所有域逻辑。这些服务位于域模型之上,并使用域模型来存储数据。

考虑到这些论点,您应该考虑使用诸如DataMapperGateway模式之类的东西来处理需要持久化到某种形式的后端存储的域对象。

备择方案

让我们暂时忘掉Revision对象,想象一下我们想使用一个Slideshow对象来输出画廊中的图像。这个类可能看起来像:

class Slideshow {
    private $gallery;
    public function __construct(Gallery $gallery) {
        $this->gallery = $gallery;
    }
    public function play() {
        // do something with the gallery here
    }
}

忽略 php 代码实际上不会用于“播放”幻灯片的事实,因为这会在客户端代码中发生。这里重要的Slideshow是使用Composition来访问Gallery. 这种结构大大优于直接在内部使用newa ,因为:GallerySlideshow

  1. 现在Slideshow是可插入的——我们可以插入任何遵循“画廊”概念的对象(通常Gallery会被声明为符合指定的接口契约)。

  2. 现在Slideshow即将进行测试。我们如何处理Gallery提供的图像类型不合适的情况?如果我们直接在Gallery内部实例化 a,Slideshow我们将无法模拟这种情况。通过注入依赖项,Slideshow我们有机会测试我们的代码处理不同操作条件的能力。

当然,有时在另一个类中直接实例化一个对象是合适的。有关此主题的更多指导,我建议 Miško Hevery 在他的文章To "new" or not to "new"中提出建议。

于 2012-11-16T20:02:34.437 回答
10

好消息是 PHP 是动态类型的,因此您的代码几乎保持不变。当您初始化时revisions,只需将其初始化为Revisions对象的实例,就像这样

$gallery = new Gallery();
$gallery->revisions = new Revisions()  // assuming you have defined the Revisions class

至于类应该是什么样子,根据您的 var_dump,如下所示:

class Revisions {
  public $revisionID;
  public $revisionName;

  public function __construct($id, $name) {
     $this->revisionID = $id;
     $this->revisionName = $name;
  }

  // if required, define a default constructor as well that does not take any parameters

}

由于revisions您的问题并不完全清楚,您可能想要创建一个Revision对象而不是Revisions(不是复数),然后$gallery->revisionsRevision.

于 2012-11-16T19:28:01.597 回答
0

本质上,您正在为 创建一个单独的类Revisions,就像您为Gallery.

class Gallery
{
    //Gallery Name
    public $galleryID;
    public $galleryName;

    //Client Name
    public $clientName;

    //Gallery Options
    public $bg_color;
    public $albumAgreement;
    public $maxChanges;
    public $sharing_on;

    //Revisions
    public $revisions;
}
class Revisions {
    public $revisionID;
    public $revisionName;
}

//Set the revisions within gallery
$gallery = new Gallery();
$gallery->revisions = new Revisions();
于 2012-11-16T19:31:05.057 回答
0

对 setter 和 getter 或构造函数做一些研究。我将给出一个使用下面每个具有相同最终结果的示例。

class Gallery
{
    //Gallery Name
    protected $galleryID;
    protected $galleryName;

    //Client Name
    protected $clientName;

    //Gallery Options
    protected $bg_color;
    protected $albumAgreement;
    protected $maxChanges;
    protected $sharing_on;

    //Revisions
    protected $revisions;


    public function setGalleryID($id)
    {
         $this->galleryID = $id;
    }

    public function getGalleryID()
    {
         return $this->galleryID;
    }

    public function setRevisions(Revisions $revisions)
    {
         $this->revisions = $revisions;
    }

    public function getRevisions()
    {
         return $this->revisions;
    }
}

$gallery = new Gallery();
$gallery->setRevisions(new Revisions());
var_dump($gallery->getRevisions());

或者使用构造函数

class Gallery
{
    //Gallery Name
    protected $galleryID;
    protected $galleryName;

    //Client Name
    protected $clientName;

    //Gallery Options
    protected $bg_color;
    protected $albumAgreement;
    protected $maxChanges;
    protected $sharing_on;

    //Revisions
    protected $revisions;


    public function __construct($id, Revisions $revisions)
    {
         $this->galleryID = $id;
         $this->revisions = $revisions;
    }

    public function getGalleryID()
    {
         return $this->galleryID;
    }

    public function getRevisions()
    {
         return $this->revisions;
    }
}

$gallery = new Gallery(1, new Revisions());
var_dump($gallery->getRevisions());

通过这种方式,您可以更好地控制输入内容、输出内容、只读(可获取)、只写(可设置)或两者兼而有之。例如,在 setter 中,您可以看到 Revisions 写在 $revision 之前。这确保只有一个修订对象可以设置为 $this->revisions。

于 2012-11-16T19:37:15.033 回答