将整个类作为另一个类的参数传递

6
到目前为止,我感觉我已经理解了面向对象编程的概念和优势,并且没有什么困难来理解如何在PHP中使用类。然而,这让我有点困惑。我认为我可能明白了,但我仍然不确定。我一直在跟随一系列视频教程(不确定链接到外部资源的规则,但我在YouTube上找到了它们),它们相当简单易懂。除了令人沮丧的是,当教程决定在另一个类中将一个类作为参数传递时。至少我认为是这样。
Class Game
{

    public function __construct()
    {
        echo 'Game Started.<br />';
    }
    public function createPlayer($name)
    {
        $this->player= New Player($this, $name);
    }
}


Class Player
{

    private $_name;

    public function __construct(Game $g, $name)
    {
        $this->_name = $name;
        echo "Player {$this->_name} was created.<br />";
    }
}

然后我实例化了一个Game类的对象并调用了它的方法;

$game = new Game();
$game-> createPlayer('new player');

相当令人沮丧的是,教程并没有真正解释为什么这样做,而且在我看来,代码中也没有任何调用可以证明这样做的理由。
Player类的魔术方法构造函数是否将Game类作为引用传递?这是否意味着整个类都可以通过引用在Player类中访问?当引用$this而不指向任何特定方法或属性时,是否引用整个类?
如果是这样的话,那么我为什么要这样做呢?如果我已经在我的Game类中创建了一个Player,那么我肯定可以在Game类内部访问我的Player属性和方法,对吧?为什么我还要将我的Game类放在我的Player类中呢?比如,我能够在Player类中调用createPlayer()吗?
如果我的解释有些混乱,我向您道歉。
我的问题归结为:我究竟是将什么作为参数传递的,为什么在日常面向对象编程中要这样做?

1
在某个时候,他必须在Player类中使用变量$g。构造函数中的单词Game类型提示,它强制只传递Game类的对象作为该参数。否则会抛出异常。 - Michael Berkowski
Class Player extends Game 添加进去不是更合理吗?虽然我不是 PHP 专家,但为什么要这样做呢? - Angelo A
1
@AngeloA Player并没有继承Game,而是GamePlayer的一个依赖项。在Player构造函数内部,可以通过$g变量访问Game的公共API。 - Phil
“为什么我还想把游戏类放在玩家类里面?”这通常用于创建双向关联。 - Phil
3个回答

4

这被称为类型提示,他并没有将整个类作为参数传递,而是暗示了第一个参数的类Player的类型

PHP 5引入了类型提示。现在函数能够强制参数为对象(通过在函数原型中指定类名),接口,数组(自PHP 5.1起)或可调用(自PHP 5.4起)。然而,如果将NULL用作默认参数值,则它将被允许作为任何后续调用的参数。

(摘自php手册)

这是否意味着整个类都可以通过引用在Player类中访问?

不是整个类,但您可以访问作为参数传递的类的实例。


这个回答相当简洁明了,谢谢。但我仍然不完全清楚如何使用类实例的引用。我可以用print_r将其输出,但我不知道如何访问对象的任何属性。 - Martyn Shutt

3
该方法需要得到一个Game对象的实例,否则会出现错误。
你在这里传递了一个Game对象的实例:New Player($this, $name);关键字$this指的是当前对象实例。
最后……我(或其他人)都不知道作者为什么这样做,因为他在传递之后没有使用Game实例。
为什么要传递一个类的实例?想象一下一个接受用户输入并根据某些逻辑对其进行处理的类。(代码中没有注释,因为函数名和类名应该是自说明的)
class User{
  protected $name,$emp_type,$emp_id;
  protected $department;

  public function __construct($name,$emp_type,$emp_id){
     $this->name = $name;
     $this->emp_type = $emp_type;
     $this->emp_id = $emp_id;
  }

  public function getEmpType(){
     return $this->emp_type;
  }

  public function setDep($dep){
    $this->department = $dep;
  }
}


class UserHub{

  public function putUserInRightDepartment(User $MyUser){
      switch($MyUser->getEmpType()){
           case('tech'):
                $MyUser->setDep('tech control');
                break;
           case('recept'):
                $MyUser->setDep('clercks');
                break;
           default:
                $MyUser->setDep('waiting HR');
                break;
      }
  }
}

$many_users = array(
    0=>new User('bobo','tech',2847),    
    1=>new User('baba','recept',4443), many more
}

$Hub = new UserHub;
foreach($many_users as $AUser){
   $Hub->putUserInRightDepartment($AUser);
}

2
/**
* Game class.
*/
class Game implements Countable {
    /**
    * Collect players here.
    * 
    * @var array
    */
    private $players = array();

    /**
    * Signal Game start.
    * 
    */
    public function __construct(){
        echo 'Game Started.<br />';
    }

    /**
    * Allow count($this) to work on the Game object.
    * 
    * @return integer
    */
    public function Count(){
        return count($this->players);
    }

    /**
    * Create a player named $name.
    * $name must be a non-empty trimmed string.
    * 
    * @param string $name
    * @return Player
    */
    public function CreatePlayer($name){
        // Validate here too, to prevent creation if $name is not valid
        if(!is_string($name) or !strlen($name = trim($name))){
            trigger_error('$name must be a non-empty trimmed string.', E_USER_WARNING);
            return false;
        }
        // Number $name is also not valid
        if(is_numeric($name)){
            trigger_error('$name must not be a number.', E_USER_WARNING);
            return false;
        }
        // Check if player already exists by $name (and return it, why create a new one?)
        if(isset($this->players[$name])){
            trigger_error("Player named '{$Name}' already exists.", E_USER_NOTICE);
            return $this->players[$name];
        }
        // Try to create... this should not except but it's educational
        try {
            return $this->players[$name] = new Player($this, $name);
        } catch(Exception $Exception){
            // Signal exception
            trigger_error($Exception->getMessage(), E_USER_WARNING);
        }
        // Return explicit null here to show things went awry
        return null;
    }

    /**
    * List Players in this game.
    * 
    * @return array
    */
    public function GetPlayers(){
        return $this->players;
    }

    /**
    * List Players in this game.
    * 
    * @return array
    */
    public function GetPlayerNames(){
        return array_keys($this->players);
    }
} // class Game;


/**
* Player class.
*/
class Player{
    /**
    * Stores the Player's name.
    * 
    * @var string
    */
    private $name = null;

    /**
    * Stores the related Game object.
    * This allows players to point to Games.
    * And Games can point to Players using the Game->players array().
    * 
    * @var Game
    */
    private $game = null;

    /**
    * Instantiate a Player assigned to a Game bearing a $name.
    * $game argument is type-hinted and PHP makes sure, at compile time, that you provide a proper object.
    * This is compile time argument validation, compared to run-time validations I do in the code.
    * 
    * @param Game $game
    * @param string $name
    * @return Player
    */
    public function __construct(Game $game, $name){
        // Prevent object creation in case $name is not a string or is empty
        if(!is_string($name) or !strlen($name = trim($name))){
            throw new InvalidArgumentException('$name must be a non-empty trimmed string.');
        }
        // Prevent object creation in case $name is a number
        if(is_numeric($name)){
            throw new InvalidArgumentException('$name must not be a number.');
        }
        // Attach internal variables that store the name and Game
        $this->name = $name;
        $this->game = $game;
        // Signal success
        echo "Player '{$this->name}' was created.<br />";
    }

    /**
    * Allow strval($this) to return the Player name.
    * 
    * @return string
    */
    public function __toString(){
        return $this->name;
    }

    /**
    * Reference back to Game.
    * 
    * @return Game
    */
    public function GetGame(){
        return $this->game;
    }

    /**
    * Allow easy access to private variable $name.
    * 
    * @return string
    */
    public function GetName(){
        return $this->name;
    }
} // class Player;

// Testing (follow main comment to understand this)
$game = new Game();
$player1 = $game->CreatePlayer('player 1');
$player2 = $game->CreatePlayer('player 2');
var_dump(count($game)); // number of players
var_dump($game->GetPlayerNames()); // names of players

我用更好的方式重写了你的代码,并添加了一些缺失的变量,使那些代码变得有意义:
  • 在玩家类中,你没有存储游戏。
  • 在游戏类中,你只支持一个玩家。
  • 没有错误检查... 任何地方都没有。
修复所有这些问题以及:
  • 添加异常(以防止对象创建)
  • Try{} catch(...){} 异常处理,任何面向对象编程开发者都应该知道
  • 实现可计数接口以允许计算 $game 中的玩家数量
  • 还有一些技巧,可以让你读起来更加愉快
请跟随注释,我希望你在阅读后能够更好地理解你的代码。

1
感谢您花费精力重新编写代码。我一定会在我的IDE中尝试它,并尝试理解它所做的一切。面向对象编程对我来说仍然有些令人生畏,因为我已经做了很长时间的过程式编程,但从结构上来看,我发现它更具可扩展性。再次感谢。 - Martyn Shutt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接