【问题标题】:PHP : Best way to call an object method from another object ?PHP:从另一个对象调用对象方法的最佳方式?
【发布时间】:2013-05-23 17:35:51
【问题描述】:

我正在寻找从另一个类调用类方法的最佳方式,而不必使用 Global 来获取类实例,因为我现在明白“全局是邪恶的”!

这里有一些代码来解释它:

class Assets{
 public $assets = array();
 public function add($asset){
  $this->assets[] = $asset;
 }
}

现在我想在这里调用 Assets/call 方法 ..

$assets = new Assets;
class Form{
 public function __construct(){
  global $assets;
  $assets->add('Form');
 }
}

在这种情况下使用 Global 是不是很糟糕?如果是这样,还有什么其他方式被认为是最好的?

PS:我只需要使用该类的一个实例;意味着我不想在第二个类中创建一个新实例。

【问题讨论】:

  • 在每种情况下使用全局变量都是不好的。即使使用纯程序代码。
  • 依赖注入是“语言提供和标准”的解决方案。你在 OOP 方面真的很糟糕。也许this list 有点帮助。
  • @Dewan159 我认为你从错误的角度看待这个问题,当一个人第一次开始深入研究 PHP 的 OOP 时,从程序方法开始并谈论“实践”时,这很常见.你明白为什么 global 是“坏的”吗?尝试首先关注这一点。在直截了当地批评语言之前发展你对方法的知识,在寻求帮助时你会得到更好的接待。一旦您对注入的重要性有所了解(例如,它在整个 Java 世界中都在使用),优势就会更加明显。
  • @Dewan159 我没有对您的问题做出定性陈述:) 我只是注意到您提供的 sn-ps 证明了设计中的错误目标。您确定的弱点是除 PHP 之外的许多语言(Python、许多 JVM 语言、Ruby 都有一些可用的 DI 变体)几乎普遍接受的编码实践。它是 OOP 的支柱。您倾向于将此模式视为问题,这可能意味着您在编写代码时有一些错误的目标。你提到写一个框架......也许我们可以继续聊天?
  • @Dewan159:如果您不总是希望人们必须传递资产对象,那么就不要强迫他们使用构造函数。提供一个工厂类,您将资产实例传递一次,并让工厂始终构建您的表单类。

标签: php oop


【解决方案1】:

怎么样

<?php
class Assets
{
    protected static $assets = array();

    protected function add($el)
    {
        self::$assets[] = $el;
    }

    public function getAssets()
    {
        return self::$assets;
    }
}

class Form extends Assets
{
    public function __construct()
    {
        parent::add('Form');
    }
}

class Css extends Assets
{
    public function __construct()
    {
        parent::add('Style');
    }
}

$assets = new Assets;
new Form;
new Css;
var_dump($assets->getAssets()); // Prints: array(2) { [0]=> string(4) "Form" [1]=> string(5) "Style" }

【讨论】:

  • 这与global 有什么不同?
【解决方案2】:

依赖注入是要走的路。我将使用这个 Factory mechanize 将对象实例传递给另一个对象。如(只是一个样本,没有测试它):

class UI{

public $somthing = 'Cool';

public function newComponent($name){
    return new $name($this);
}

}

$ui = new UI;
class Form{
    public $ui;

    public function __construct(UI $ui){
        $this->ui = $ui;
    }

    public function say(){
        print $this->ui->somthing;
    }
}

$form = $ui->newComponent('Form');

$form->say(); // print 'Cool'.

【讨论】:

    【解决方案3】:

    我没有直接用提供的元素来回答这个问题,我只是展示了一种在对象之间共享数据而不使用全局变量的方法。

    class A {
    
        private $v;
    
        function __construct ($value)
        {
            $this->v = $value;
        }
    
        function set_v ($value)
        {
            $this->v = $value;
        }
    
        function print_v ()
        {
            echo $this->v;
        }
    
    }
    

    .

    class B {
    
        private $a;
    
        function __construct (&$tha)
        {
            if ($tha) {
              $this->a = $tha;
            }
        }
    
        function print_a ()
        {
            echo $this->a->print_v();
        }
    }
    

    .

    $mya = new A(12);
    $mya->print_v(); // echo 12
    $myb = new B($mya);
    $mya->set_v(13); 
    $myb->print_a(); // echo 13
    

    您可以在另一个对象中保留一个对象的痕迹,并尊重对象的封装。 但是,如果 A 的一个实例被删除,B 的一个实例可能变得不可靠。然后,您应该确保数据在存储它的对象中始终是可靠的(例如,创建一个验证内部数据是否正常的方法)。

    正如 tereško 所注意到的,应避免显式等待引用的参数。事实上,自 PHP 5.0 起,语言中引入了一个新特性,称为类型提示。这可以防止您的程序出现不希望的内存行为,并且可以在编译级别监控任何错误匹配(更多详细信息请参阅Type Hinting (PHP official documentation)

    因此

    function __construct (&$tha) ...
    

    必须重新定义为

    function __construct (A $tha) ..
    

    在示例中。

    【讨论】:

    • 自从 php 5.0 发布(大约在 2004 年左右发布),你不应该通过引用传递对象。它弄乱了引用计数并导致泄漏。
    【解决方案4】:

    如果在您的上下文中有意义,您也可以将 Assets 设为 singleton

    class Assets
    {
        /////////////////////
        // Your stuff      //
        /////////////////////
    
        private $assets = array();
        public function add($asset)
        {
            $this->assets[] = $asset;
        }
    
        /////////////////////
        // Singleton stuff //
        /////////////////////
    
        static private $instance = null;
        static public function getInstance()
        {
            if(self::$instance == null)
                self::$instance = new self;
            return self::$instance;
        }
    
        // To restrict construction of this class outside of this class definition
        private function __construct() {}
    
        // To restrict cloning of this class outside of this class definition
        private function __clone() {}
    }
    

    在您的其他代码中,您会像这样使用单例:

    class Form
    {
        public function __construct()
        {
            Assets::getInstance()->add('Form');
        }
    }
    

    【讨论】:

    • 这很酷,但单例不被认为是一种不好的做法吗?
    • 我认为这总是取决于你想要做什么。考虑到一些相当大的项目(例如开源 3D 引擎Ogre3D)大量使用单例时,我不会说单例通常是一种不好的做法。
    【解决方案5】:

    你可以试试这个,

    class Form{
     public function __construct(Assets $assets){
      //$assets = new Assets;
      $assets->add('Form');
     }
    }
    

    【讨论】:

    • 当然,但我只需要使用该类的一个实例。谢谢。
    • 可以像这样使用构造函数注入,public function __construct(Assets $assets);
    • 注射绝对是要走的路。将构造隐藏在另一个构造函数中是不好的做法,从长远来看只会让事情变得更难(例如,您发现一个用例应该从不同的 Form 实例中使用相同的 Assets 实例)
    【解决方案6】:

    如果您只想拥有 1 个资产实例,则需要使用某种形式的依赖注入。

    在这种情况下,最明显的是通过构造函数传递它:

    class Form{
     public function __construct(Assets $assets){
      $assets->add('Form');
     }
    }
    

    使用全局变量确实是个坏主意。

    【讨论】:

    • 是的,这是一个很好的做法。还有一些其他方式也被认为是“好”的,但globalstatic 或单例在学术 OOP 设计中都被认为有点糟糕。尽可能避免这些。
    • @Evert ,虽然这比其他“答案”要好得多,但您确实应该避免在构造函数中执行任何计算。它往往会把测试变成一场噩梦。
    • @Evert 你能说出其他几种方式吗?谢谢。
    • 只是研究依赖注入。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-14
    • 2012-10-04
    • 1970-01-01
    • 2022-12-09
    • 2019-07-19
    • 2014-10-08
    • 1970-01-01
    相关资源
    最近更新 更多