【问题标题】:Save object clone as protected property将对象克隆保存为受保护的属性
【发布时间】:2014-02-08 21:46:46
【问题描述】:

我打算在父对象的构造函数中创建对象父对象的克隆。简而言之:

class ParentClass {
    protected $property;

    public function __construct() {
        $this->property = clone $this;
    }
}

class ChildClass extends ParentClass {

}

这一切都很好,但是这段代码的问题是受保护的属性被一个 ChildClass 的实例填充,以防 ChildClass 被实例化。但是,无论$this 指的是什么类,我都希望它成为 ParentClass 的一个实例。

我当然可以结合debug_backtracenew self()(为了避免构造函数调用的无休止递归)并将生成的 ParentClass 实例分配给属性,尽管这样的解决方案很冗长,因为调试回溯只返回字符串名称调用者类和方法。

最后,我可以结合new self() 和为对象的实例化提供一个参数来指示是否应该创建一个“新自我”,但我不喜欢这个解决方案,因为它的丑陋和冗余。

有没有办法在 PHP 中找到“自我克隆”?

【问题讨论】:

  • 你实际上想用这个实现什么?为什么一个对象需要一个自己的克隆作为成员,如果它是一个不同的类,它在什么意义上是一个克隆?对此的传统解读是“ChildClass is-a ParentClass”和“ChildClass has-a ParentClass”,但由于某种原因not“ChildClass has-a ChildClass”......我怀疑有一种不那么令人费解的方式来做任何打算实现的目标。
  • @IMSoP 我的消息记录器对象可以被其他对象扩展。任何“公共”消息记录器(任何脚本都可以实例化)也需要它自己的私有消息记录器,以便记录它自己的——例如——错误消息。现在,当我实例化一个从基本消息记录器继承的邮件记录器时,我不希望私人消息记录器属于邮件记录器类型,而是保留基本消息记录器类型。
  • 在这种情况下,也许“内部记录器”首先不应该是基类,而是完全不同的对象。你可以有一个抽象的PublicLogger 类,它的部分定义是实例化一个PrivateLogger 对象(或者在构造函数中提供它,如“依赖注入”)。即使没有内部记录器,邮件记录器也没有从“基本”(日志到文件?)记录器继承的业务,而不是它们都具有共同的 abstract 基础或 interface
  • 另外,忘记内部记录器的整个概念,只需在(抽象)基本记录器类上使用protected 方法,例如log_internal_logging_error,用于任何最后的错误处理或调试记录器需要记录。
  • @IMSoP 消息记录器记录消息并可能对它们执行某些操作(取决于已在对象上设置的标志)。最后,如果要求,它可以以某种方式导出消息。邮件记录器也是如此,尽管它的某些方法已稍作修改。我经常在需要记录自己的错误的类中注入基本消息记录器的实例。对我来说,如果我对消息记录器类本身做同样的事情是有意义的。

标签: php this clone self


【解决方案1】:

正如 cmets 中所讨论的,我认为这种模式不适合您的原因是您的对象层次结构设计不佳。在示例中,ChildClassParentClass 的“类型”,但也在内部引用 ParentClass 的副本以执行一些委托工作。

从 cmets 来看,你所拥有的应该是这样的:

class BasicLogger {
    protected $delegated_logger;

    public function __construct() {
         // initialise $this->delegated_logger somehow
    }

    public function logMessage($message, $flags) {
    {
         $prepared_message = $this->prepareMessage($message, $flags);
         $this->deliverMessage($prepared_message);
    }

    private function prepareMessage($message, $flags) {
         // implementation here
    }

    protected function deliverMessage($prepared_message) {
         // implementation here
    }
}

class MailLogger extends BasicLogger {
    protected function deliverMessage($prepared_message) {
         // different implementation here
         if ( $mail_sending_failed ) {
             $this->delegated_logger->logMessage('Oops, MailLogger failed...');
         }
    }
}

但是,BasicLogger 实际上在对象层次结构中扮演多个角色:

  1. 定义所有记录器应遵循的接口(此处表示为单个 logMessage 方法)
  2. 提供所有记录器都将使用的prepareMessage 的共享实现,以及依赖它的logMessage 的实现以及deliverMessage 函数
  3. 提供deliverMessage 的特定实现,该实现将被子类完全覆盖
  4. 为复杂的实现提供一种机制以委托给更简单的实现,而无需区分两者

前三个角色应该分为interfaceabstract基类和一个简单的实现:

interface Logger {
    public function logMessage($message, $flags = null);
}

abstract class BaseLogger implements Logger {
    public function logMessage($message, $flags = null) {
    {
         $prepared_message = $this->prepareMessage($message, $flags);
         $this->deliverMessage($prepared_message);
    }

    private function prepareMessage($message, $flags) {
         // implementation here
    }

    abstract protected function deliverMessage($prepared_message);
}

class BasicTextLogger extends BaseLogger {
    protected function deliverMessage($prepared_message) {
         // implementation here
    }
}

然后,您可以在任何需要的地方使用BasicTextLogger 的实例,包括在BaseLogger 的其他实现中。

您可能希望将委托记录器的逻辑(上面我BasicLogger 的第四个角色)放入另一个类中以供重用。 BasicTextLogger 不应该继承这种行为,否则您最终需要将一个记录器提供给一个记录器到一个记录器再提供一个记录器,ad infinitum

 abstract class ComplexLogger extends BaseLogger {
     protected $delegated_logger;

     public function __construct( Logger $delegated_logger ) {
         if ( $delegated_logger instanceOf ComplexLogger ) {
              throw new Exception('Attempted to delegate one complex logger to another; danger of recursion, so disallowed.');
         } else {
              $this->delegated_logger = $delegated_logger;
         }
     }
 }

 class MailLogger extends ComplexLogger {
     protected function deliverMessage($prepared_message) {
         // different implementation here
         if ( $mail_sending_failed ) {
             $this->delegated_logger->logMessage('Oops, MailLogger failed...');
         }
     }
 }

这允许您执行依赖注入,为您的复杂记录器提供一个简单的记录器以委托给:

 $my_logger = new MailLogger( new BasicTextLogger() );
 $my_logger->logMessage('Hello World!');

这可能看起来有很多不同的classes 和interfaces,但现在每个人都有明确的责任。您可以将整个$delegated_logger 逻辑放入MailLogger,但如果您以后有另一个复杂的记录器,则必须复制并粘贴它。您也可以忽略Logger 接口,只对派生自BaseLogger 类的类进行类型提示,但您可能需要一个根本不使用prepareMessage 的实现——例如,DoNothingLogger

【讨论】:

  • 如果有人能想出更好的术语来代替 BaseBasic,请告诉我,因为两者之间的区别对于这个答案至关重要。
猜你喜欢
  • 2018-08-08
  • 2014-08-10
  • 1970-01-01
  • 2020-03-31
  • 2011-03-11
  • 2011-06-06
  • 2016-02-19
  • 2016-08-05
  • 2019-10-06
相关资源
最近更新 更多