简体   繁体   English

PHP初学者OOP构建一个对象

[英]PHP Beginner OOP Building an object

Thanks in advance for help and direction. 在此先感谢您的帮助和指导。 I am finally making the switch from linear programming to OOP. 我终于完成了从线性编程到OOP的转换。 I am working on my first class ever and I could use a little direction. 我正在上第一堂课,我可以用一点方向。 My first class is a gallery with the following properties 我的第一堂课是一个具有以下属性的画廊

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;
}

My out put thus looks like: 因此我的出局看起来像:

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

My next step is I would like to make 'revisions' an object as well so that my output would look like 我的下一步是我想将'revisions'作为一个对象,以便我的输出看起来像

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

What direction do I go for something like this and what might the class look like? 我有什么方向可以选择这样的课程以及课程的样子?

Thanks 谢谢

This is more of a long-form comment , since it explains the origin of your dilemma, but provides no solutions. 这更像是一个长篇评论 ,因为它解释了你的困境的根源,但没有提供任何解决方案。

OOP Lesson 1: Just because you're using classes doesn't mean you're writing object-oriented code. OOP第1课:仅仅因为你正在使用类并不意味着你正在编写面向对象的代码。

There is rarely a good use-case for a public property in your objects. 对象中的公共属性很少有很好的用例。 Let's look at the OP's example: 让我们看一下OP的例子:

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

Having defined our properties as public , how do the following two snippets of code differ? 将我们的属性定义为public属性后,以下两个代码片段有何不同?

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

// vs:

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

If you said, "they aren't really different at all," then you'd be correct. 如果你说, “它们根本没有什么不同”,那么你就是对的。 In fact, the object-based code will be slower because of the instantiation overhead involved with new . 事实上,基于对象的代码将会因为涉及new的实例化开销而变慢。 There are some other factors like the ability to pass around references to an object instead of copying a new array, but those don't affect this particular situation. 还有一些其他因素,例如能够传递对象的引用而不是复制新数组,但这些因素不会影响这种特定情况。

OOP Lesson 2: Objects are a black box OOP第2课:对象是一个黑盒子

The problem with creating an object that's nothing more than a collection of mutable properties is that the rest of your code has full knowledge of what happens inside that object. 创建一个只是可变属性集合的对象的问题是,其余代码完全了解该对象内部发生的事情。 Let's talk about why this is bad ... 我们来谈谈为什么这很糟糕......

Humans just just aren't very good when it comes to complexity. 在复杂性方面,人类只是不太好。 Good software aims to minimize complexity in part by encapsulating functionality into discrete units. 良好的软件旨在通过将功能封装到离散单元中来最小化复杂性。 In this case, we want to encapsulate all the logic of a "gallery" entity into the Gallery class. 在这种情况下,我们希望将“库”实体的所有逻辑封装到Gallery类中。 This makes sense as part of a domain-driven design (DDD) approach. 这是域驱动设计(DDD)方法的一部分。 What we want to do is wall off the Gallery from the outside world; 我们想要做的是从外面的世界隔离Gallery ; we want its internal implementation to be opaque to the rest of our code. 我们希望它的内部实现对我们的其余代码不透明。 The rest of our application shouldn't know or care how the Gallery functions, just that it works as expected. 我们的其他应用程序不应该知道或关心Gallery功能,只是它按预期工作。 The added benefit here is that we can focus on making the gallery work how it's supposed to and then forget about it. 这里的额外好处是我们可以专注于使画廊按照预期工作然后忘记它。 We aren't forced to remember how Gallery works to work with an Image or a Revision . 我们不必记住Gallery如何使用ImageRevision This loose-coupling is one of the most powerful tools in OO design. 这种松耦合是OO设计中最强大的工具之一。

While it might work on very small scales, it's impossible to keep the logic of an entire application in your head at the same time. 虽然它可能在非常小的范围内工作,但是不可能同时保持整个应用程序的逻辑。 It doesn't matter how smart you are, our brains just don't have enough RAM. 无论你有多聪明,我们的大脑都没有足够的RAM。

Moving back to the code, if our application code knows how the Gallery assigns itself a name then we've already allowed the logic of "gallery-ness" to leak out into the rest of the program. 回到代码,如果我们的应用程序代码知道Gallery如何为自己命名,那么我们已经允许“gallery-ness”的逻辑泄漏到程序的其余部分。 What happens when we decide that we want to verify new gallery names when they're assigned? 当我们决定在分配时验证新的图库名称时会发生什么? We must now put that validation logic everywhere in our code where we've specified gallery names because we haven't walled off everything about the abstract concept of "gallery-ness." 我们现在必须在我们指定图库名称的代码中随处放置验证逻辑,因为我们没有关于“gallery-ness”抽象概念的所有内容。 A much better design would be to encapsulate the assignment of Gallery properties within the object itself: 更好的设计是将Gallery属性的分配封装在对象本身中:

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

If we structure our class in this manner, we always have a single point of entry when we need to assign a name to a gallery. 如果我们以这种方式构建我们的类,当我们需要为库指定名称时,我们总是只有一个入口点。 Now, when our requirements for a gallery change down the road ( and they will ), all of our application code -- which is blind to the logic behind gallery name assignment -- is isolated from breakage. 现在,当我们对画廊的要求发生变化时( 他们会 ),我们所有的应用程序代码 - 与图库名称分配背后的逻辑无关 - 都与破损隔离开来。 We simply add a new method to our name setter and create minimal upheaval in our program: 我们只需在名称设置器中添加一个新方法,并在程序中创建最小的剧变:

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;
    }
}

Addressing the OP 解决OP问题

To answer the question of how to represent an encapsulated Revision object as a property of the higher-level Gallery instance we need a bit of context. 要回答如何将封装的Revision对象表示为更高级Gallery实例的属性的问题,我们需要一些上下文。 It appears that what the OP is trying to do is model domain entities that will be written to and retrieved from a backend persistence layer (such as a database, flat text file, etc). 看来OP正在尝试做的是模型域实体,它将被写入后端持久层(例如数据库,平面文本文件等)并从中检索。

Anemic domain models are one way to handle this, however it's generally considered an anti-pattern. 贫血领域模型是处理这种情况的一种方式,但它通常被认为是反模式。 Martin Fowler writes: 马丁福勒写道:

The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. 贫血领域模型的基本症状是乍一看它看起来像真实的东西。 There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. 有许多对象,其中许多以域空间中的名词命名,并且这些对象与真实域模型具有的丰富关系和结构相关联。 The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. 当你看到这个行为时会发现这个问题,并且你会发现这些物体几乎没有任何行为,这使得它们只不过是吸气剂和固定剂的一部分。 Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. 实际上,这些模型通常都带有设计规则,表明您不要将任何域逻辑放在域对象中。 Instead there are a set of service objects which capture all the domain logic. 相反,有一组服务对象捕获所有域逻辑。 These services live on top of the domain model and use the domain model for data. 这些服务位于域模型之上,并使用域模型进行数据。

With those arguments in mind, you should consider using something like the DataMapper or Gateway pattern to work with domain objects that need to be persisted to some form of backend storage. 考虑到这些参数,您应该考虑使用类似DataMapperGateway模式的东西来处理需要持久化到某种形式的后端存储的域对象。

Alternatives 备择方案

Let's forget about the Revision object for a minute and imagine that we want to use a Slideshow object to output the images from a gallery. 让我们忘记一分钟的Revision对象,并想象我们想要使用Slideshow对象从图库中输出图像。 This class might look like: 这个类看起来像:

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

Ignore the fact that php code wouldn't actually be used to "play" a slideshow as that's something that would happen in client-side code. 忽略这样一个事实,即php代码实际上不会用于“播放”幻灯片,因为这会在客户端代码中发生。 The important thing here is that the Slideshow is using Composition to access the Gallery . 这里重要的是Slideshow正在使用Composition来访问Gallery This construction is vastly superior to directly new ing a Gallery inside the Slideshow because: 这种结构大大优于直接new荷兰国际集团一Gallery里面Slideshow ,因为:

  1. The Slideshow is now pluggable -- we can insert any object that follows the concepts of "gallery-ness" (usually Gallery will be declared to conform to a specified interface contract). Slideshow现在是可插拔的 - 我们可以插入任何遵循“gallery-ness”概念的对象(通常会声明Gallery符合指定的接口契约)。

  2. The Slideshow is now imminently testable. Slideshow现在可以立即测试。 How do we handle a situation in which the Gallery provided doesn't have appropriate image types? 我们如何处理Gallery提供的没有合适图像类型的情况? If we're directly instantiating a Gallery inside a Slideshow we have no way to simulate such conditions. 如果我们在Slideshow直接实例化Gallery我们就无法模拟这样的条件。 By injecting the dependencies of the Slideshow we allow ourselves the opportunity to test our code's ability to handle different operational conditions. 通过注入Slideshow的依赖关系,我们有机会测试代码处理不同操作条件的能力。

Of course, sometimes it's appropriate to directly instantiate an object inside of another class. 当然,有时直接实例化另一个类中的对象是合适的。 For more guidance on this topic I'd suggest the advice of Miško Hevery in his article To "new" or not to "new" . 有关这个主题的更多指导,我建议MiškoHevery在他的文章To“new”或不是“new”中提出建议

The good thing is PHP is dynamically typed, so your code remains pretty much the same. 好的是PHP是动态类型的,所以你的代码几乎保持不变。 When you initialize revisions , just initialize it to be an instance of Revisions object, like this 初始化revisions ,只需将其初始化为Revisions对象的实例,就像这样

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

As for what the class should look like, based on your var_dump, something like this: 至于类应该是什么样的,基于你的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

}

Since what revisions does is not entirely clear from your question, you might want to make a Revision object instead of Revisions (not the plural), and then have $gallery->revisions be an array of Revision . 由于您的问题所做的revisions并不完全清楚,您可能希望创建一个Revision对象而不是Revisions (而不是复数),然后让$gallery->revisions成为Revision的数组。

Essentially you're creating a separate class for Revisions , just like you did for Gallery . 基本上,您正在为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();

Do some research on setters and getters or constructors. 对定位器和吸气剂或构造器进行一些研究。 I will give an example using each below that has the same end result. 我将给出一个使用下面每个具有相同最终结果的示例。

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

Or using a constructor 或者使用构造函数

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

This way you have more control over what goes in, what comes out, what is readonly (gettable), write only (settable) or both. 通过这种方式,您可以更好地控制输入内容,输出内容,读取内容(gettable),只写(可设置)或两者。 For example in the setter you can see that Revisions is written before $revision. 例如,在setter中,您可以看到Revisions是在$ revision之前编写的。 This ensures that only a Revisions Object may be set to $this->revisions. 这确保只有Revisions对象可以设置为$ this-> revisions。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM