【问题标题】:Understanding OOP Principles in passing around objects/values了解传递对象/值的 OOP 原则
【发布时间】:2010-12-23 20:10:52
【问题描述】:

我对 OOP 中的一些事情不太了解,我将使用对 SO 的虚构理解来看看我是否可以得到帮助理解。

所以,在这个页面上我们有一个问题。您可以对问题发表评论。也有答案。您可以对答案发表评论。

Question
 - comment
 - comment
 - comment

 Answer
  -comment

 Answer
  -comment
  -comment
  -comment

 Answer
  -comment
  -comment

所以,我想象对这种类型的系统(在 PHP 中,而不是 .Net 中,因为我还不熟悉 .Net)有一个非常高层次的理解:

$question = new Question;
$question->load($this_question_id); // from the URL probably
echo $question->getTitle();

要加载答案,我想它是这样的(“A”):

$answers = new Answers;
$answers->loadFromQuestion($question->getID()); // or $answers->loadFromQuestion($this_question_id);
while($answer = $answers->getAnswer())
{
    echo $answer->showFormatted();
}

或者,你会做(“B”):

$answers->setQuestion($question); // inject the whole obj, so we have access to all the data and public methods in $question
$answers->loadFromQuestion(); // the ID would be found via $this->question->getID() instead of from the argument passed in
while($answer = $answers->getAnswer())
{
    echo $answer->showFormatted();
}

我想我的问题是,我不知道何时或是否应该传递整个对象,以及何时应该只传递一个值。传入整个对象给了我很大的灵活性,但我猜它更多的内存并且可能会发生变化(比如属性或方法重命名)。如果“A”样式更好,为什么不直接使用函数呢? OOP 在这里似乎毫无意义。

谢谢, 汉斯

【问题讨论】:

    标签: php oop


    【解决方案1】:

    虽然我喜欢 Jason 的回答,但严格来说不是 OO。

    $question = new Question($id);
    $comments = $question->getComments();
    $answers = $question->getAnswers();
    
    echo $question->getTitle();
    echo $question->getText();
    
    foreach ($comments as $comment)
        echo $comments->getText();
    

    问题是:

    1. 没有信息隐藏,这是 OO 的基本原则。
    2. 如果需要更改答案的格式,则必须在与包含数据的对象无关的位置进行更改。
    3. 解决方案不可扩展。 (没有要继承的行为。)

    必须保持行为(紧密耦合)与数据。否则你不是在写 OO。

    $question = new Question($id);
    $questionView = new QuestionView( $question );
    
    $questionView->displayComments();
    $questionView->displayAnswers();
    

    信息的显示方式现在是一个实现细节,并且可以重复使用。

    注意这会带来以下可能性:

    $question = new Question( $id );
    $questionView = new QuestionView( $question );
    $questionView->setPrinterFriendly();
    
    $questionView->displayComments();
    $questionView->displayAnswers();
    

    想法是,现在您可以如何从代码库中的单个位置更改问题的格式。您可以在调用代码 (a) 不知道的情况下支持 cmets 和答案的多种格式; (b) 需要改变(在很大程度上)。

    如果您因为滥用访问器方法而在多个位置编写文本格式详细信息,那么任何未来维护者的生活都将是悲惨的。如果维护者是一个知道你住在哪里的精神病患者,那你就有麻烦了。

    对象、数据和视图

    据我了解,这就是问题所在:

    Database -> Object -> Display Content
    

    您希望使对象的行为以对象固有的逻辑为中心。换句话说,您不希望对象必须做与其核心职责无关的事情。最常见的将包括加载、保存和打印功能。您希望将这些与对象本身分开,因为如果您必须更改数据库或输出格式,您希望在系统中进行尽可能少的更改,并抑制连锁反应。

    为了简化这一点,让我们看一下只加载Comments;一切都适用于QuestionsAnswers

    评论类

    Comment 类可能提供以下行为:

    • 回复
    • 删除
    • 更新(需要许可)
    • 恢复(从删除)

    评论数据库类

    我们可以创建一个CommentDB 对象,它知道如何操作数据库中的Comments。 CommentDB 对象具有以下行为:

    • 创建
    • 加载
    • 保存
    • 更新
    • 删除
    • 恢复

    请注意,这些行为可能在所有对象中都很常见,因此可以进行重构。这也可以让您非常轻松地更改数据库,因为连接信息将被隔离到单个类(所有数据库对象的祖父)。

    示例用法:

      $commentDb = new CommentDB();
      $comment = $commentDb->create();
    

    稍后:

      $comment->update( "new text" );
    

    请注意,有多种可能的方法可以实现这一点,但您始终可以这样做,而不会违反封装和信息隐藏。

    评论视图类

    最后,CommentView 类将与Comment 类紧密耦合。预计它可以通过访问器获取Comment 类的属性。该信息仍然对系统的其余部分隐藏Comment 和它的 CommentView 紧密耦合。这个想法是格式保存在一个地方,而不是分散在需要随意使用数据的类中。

    任何需要显示 cmets 但格式略有不同的类都可以继承自 CommentView

    另请参阅:Allen Holub wrote "You should never use get/set functions", is he correct?

    【讨论】:

    • 将输出直接绑定到模型是一种糟糕的做法。您应该如何更改数据在视图中的输出格式,而不是直接在模型中。
    • 谢谢戴夫。我非常喜欢它,它很有帮助。至于获得评论和答案,理论上在不同的数据库表中,你会怎么做?像 Jason 那样的静态方法,还是从模型类中传入创建的对象?
    • 1.你没有隐藏信息。它现在只是传递给视图。您仍然必须获得该问题的所有 cmets 和答案。这是如何实现的? 2. 现在可以在视图中更改格式。好的。 3. 这里也没有什么可以继承的。
    • @Dave 感谢您编辑的答案。我认为事情正在变得更加清晰。我将一些剩余的云提炼成一个我即将发布的新问题(因为它与这个没有直接关系)。
    【解决方案2】:

    为什么要通过?怎么样:

    <?php
    $question = new Question($id);
    $comments = $question->getComments();
    $answers = $question->getAnswers();
    
    echo $question->getTitle();
    echo $question->getText();
    
    foreach ($comments as $comment)
        echo $comments->getText();
    
    foreach ($answers as $answer)
    {
        $answer_comments = $answer->getComments();
        echo $answer->getText();
    
        foreach ($answer_comments as $comment)
            echo $comment->getText();
    }
    

    getComments()getAnswers() 在哪里使用$this-&gt;id 检索并返回一组评论或回答对象?

    您可以在评论和回答对象中构建实用方法,允许您按父 ID 加载。在这种情况下,只需将 id 作为参数就好了。

    $question = new Question($id);
    $answers = Answer::forQuestion($question->id);
    
    $comments = Comment::forQuestion($question->id);
    $ans_comments = Comment::forAnswer($answer->id);  // or some way to distinguish what the parent object is.
    

    编辑:很可能子模型(在这种情况下为评论或答案)不需要来自父模型的任何东西,除了和 id 来进行数据库查询。传入整个父对象将是矫枉过正。 (另外,PHP 有一个糟糕的时间垃圾收集循环引用的对象,这可能会在 5.3 系列中修复。)

    【讨论】:

    • +1。返回一个可迭代的集合是 PHP 等高级语言的常见做法。
    • 这是我最初所做的,但由于 Answers 和 Comments 位于不同的数据库表中,我会使用不同的模型来获取它们,并且必须在对象中传递这些模型(或者最终嵌入很难测试的依赖关系,对吧)?
    • 我也会这样做。答案所需的只是 question_id,因此无需传递整个 Question 对象。如果您确实觉得您可能需要传递它的引用,以便它不会创建新副本。只是要小心之后破坏对象
    • @Hans,Answer 和 Comment 模型为“getFromParentId”类型的功能提供静态方法。 $question-&gt;getComments() 是一个不错的实用程序,可能只调用 Comment::forQuestion($this-&gt;id)。你当然应该测试这两种方法。
    • @jasonbar:这带来了另一个关于静态方法的问题。为什么不使用常规的 ole 函数?它们本质上是相同的,具有一些命名空间的特征,对吧?出于这个原因,我也不了解静态方法。对不起,我对这些东西很感兴趣。一直在使用带 OOP 的 PHP,因为它是 PHP/FI 并且无法关闭大脑开关。
    【解决方案3】:

    两种样式都可以接受。有时你只需要值,有时你需要对象。在这个例子中,我个人会按照你的第一个例子做一些事情,但是像这样的琐碎程序在野外并不经常存在,所以也许你想要第二个。

    我的经验法则是用最少的行数来做这件事,这仍然清楚地表明你试图对任何追随你的人做些什么。大多数对象创建与值传递的开销是您可能永远不必在现代架构上处理的问题。

    【讨论】:

    • 我想这是我的问题的一部分。我对创建一些函数并使用它们来明确我在做什么、保持单一工作并保持代码模块化感觉非常好。我在尝试将我的功能型大脑放入 OOP 型孔时遇到了太多麻烦。
    【解决方案4】:

    添加到@jasonbar 已经提到的内容:

    我不知道何时或是否应该传入整个对象,以及何时应该只传入一个值。

    这取决于您需要的Coupling 和您想要的Cohesion

    传递整个对象给了我很大的灵活性,但它会占用更多内存并且可能会发生变化。

    当您将对象用作函数的参数时,PHP 不会复制该对象。大多数其他语言也没有(默认情况下,如 C# 和 Java,或根据显式请求,如 C 和 C++)

    【讨论】:

    • 好的,所以我的重点不应该是内存使用,而是“我希望它如何表现”。我不确定答案,但至少我现在知道正确的问题。谢谢。
    【解决方案5】:

    要添加到Dave Jarvisjasonbar 答案,我通常使用DataMappers 在关系数据和对象之间进行转换,而不是使用ActiveRecord 方法。因此,按照您的示例,我们将拥有这些类:

    • 问题
    • 回答
    • 评论

    及其数据映射器:

    • 问题映射器
    • AnswerMapper
    • 评论映射器

    每个映射器实现一个相似的接口:

    • save(object) // 在数据库(或文本文件)中创建或更新记录
    • 删除(id)
    • get(id)

    然后,我们会这样做:

    $q = QuestionMapper::get( $questionid );
    
    // here we could either (a) just return a list of Answers 
    // previously eagerly-loaded by the
    // QuestionMapper, or (b) lazy load the answers by 
    // calling AnswerMapper::getByQuestionID( $this->id ) or similar.
    $aAnswers = $q->getAnswers();
    foreach($aAnswers as $oAnswer){
        echo $oAnswer->getText();
    
        $aComments = $oAnswer->getComments();
        foreach($aComments as $oComment){
            echo $oComment->getText();
        }
    }
    

    关于 QuestionView->render( $question ) 之类的使用,我更喜欢使用来自域对象的 getter 显示数据的视图。如果您将 Question 传递给 HTMLView,它会将其呈现为 HTML;如果将它传递给 JSONView,那么您将获得 JSON 格式的内容。这意味着域对象需要有 getter。

    PS:我们还可以考虑使用 QuestionMapper 加载与问题、答案和评论相关的所有内容。由于 Comments 始终属于 Answers 或 Questions,而 Answers 始终属于 Questions,因此 QuestionMapper 加载所有内容可能是有意义的。当然,我们必须考虑不同的策略来延迟加载问题的答案和评论集,以避免占用服务器。

    【讨论】:

      猜你喜欢
      • 2013-08-03
      • 2018-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-20
      • 1970-01-01
      相关资源
      最近更新 更多