在传递对象/值时理解OOP原则

时间:2010-12-23 20:10:53

标签: php oop

我在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在这里似乎毫无意义。

谢谢, 汉斯

5 个答案:

答案 0 :(得分:4)

为什么要通过?怎么样:

<?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->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和id用于进行数据库查询。传递整个父对象将是过度的。 (另外,PHP有一个糟糕的时间垃圾收集带有循环引用的对象,可能会在5.3系列中修复。)

答案 1 :(得分:4)

虽然我喜欢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. 解决方案不可扩展。 (没有继承的行为。)
  4. 必须保持与数据的行为(紧密耦合)。否则你不是在写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); (b)需要改变(在很大程度上)。

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

    对象,数据和视图

    正如我所理解的那样,这就是问题所在:

    Database -> Object -> Display Content
    

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

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

    评论类

    Comment类可能会提供以下行为:

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

    CommentDB类

    我们可以创建一个知道如何操纵数据库中CommentDB的{​​{1}}对象。 Comment对象具有以下行为:

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

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

    使用示例:

    CommentDB

    随后:

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

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

    CommentView类

    最后, $comment->update( "new text" ); 类将与CommentView类紧密耦合。它可以通过访问器获得Comment类的属性。该信息仍然隐藏在系统的其他部分中。 Comment及其Comment紧密耦合。我们的想法是将格式保存在一个地方,而不是分散在需要使用数据的类中。

    任何需要显示评论但格式略有不同的类都可以从CommentView继承。

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

答案 2 :(得分:0)

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

我的经验法则是以尽可能少的线条来做这件事,这些线条仍然清楚地表明你试图对追随你的人做些什么。大多数对象创建与价值传递的开销是您在现代拱门上可能永远无法处理的事情。

答案 3 :(得分:0)

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

  

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

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

  

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

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

答案 4 :(得分:0)

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

  • 问题
  • 答案
  • 注释

及其数据映射器:

  • QuestionMapper
  • AnswerMapper
  • CommentMapper

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

  • save(object)//创建或更新数据库(或文本文件)中的记录
  • 删除(ID)
  • 得到(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-&gt; render($ question)这样的东西,我更喜欢使用来自域对象的getter来显示数据的Views。如果您将问题传递给HTMLView,它会将其呈现为HTML;如果你将它传递给JSONView,那么你将获得JSON格式的内容。这意味着域对象需要有getter。

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