编程范例:具有继承的强类型参数

时间:2013-11-05 11:14:10

标签: php oop design-patterns architecture

警告:可能导致TL:DR

我正在使用PHP 5.3.10并遇到以下问题。我有一个抽象类DataMapper,它针对我想要保留的特定DataModel进行了扩展。以下代码可以解决这个问题:

abstract class DataMapper {
    public abstract function findById($id);
    public abstract function fetchAll();
    public abstract function save(IModel $model);               // DISCUSSION
    /* more helper functions here */
}

class PersonMapper extends DataMapper {
    public function findById($id) { /* ...magic ... */ }
    public function fetchAll() { /* ...magic ... */ }
    public function save(IModel $model) { /* ...magic ... */ }  // DISCUSSION
}

interface IModel {
    public function setOptions(array $options);
    public function toArray();
}

abstract class Model implements IModel {
    protected $_fields = array();
    protected $_data = array();

    public function setOptions(array $options) { /* ...magic ... */ }
    public function toArray() { /* ...magic ... */ }

    public function __construct(array $options = null) { /* ...magic ... */ }
    public function __set($name, $value) { /* ...magic ... */ }
    public function __get($name) { /* ...magic ... */ }
}

class PersonModel extends Model {
    protected $_fields = array('id', 'name', 'passhash', /*...*/);

    public function setId($value) {
        /* ...Validation happening... */
        $this->_data['id'] = $value;
        return $this;
    }

    public function checkPassword($password) { /* ...magic... */ }
}

这很好,但对我的感觉真的很古怪。

正如您所看到的,我使用了一个接口IModel来告诉DataMapper,它确实需要一组参数和方法。但是,某些模型确实具有相应DataMapper所需的额外方法 - 在该示例中,使用checkPassword()方法,该方法用于针对存储的哈希值测试密码。此方法还可以指示DataMapper重新更新刚刚测试的密码并根据新要求更新密码(例如,密码哈希函数的难度增加)。

所以我真正想要的是将PersonMapper的签名更改为PersonMapper::save(PersonModel $model) - 例如在另一个DataMapper中PostMapper::save(PostModel $model)等等。这是由于这些DataMappers需要一定的签名。所以我的理想解决方案如下:

abstract class DataMapper {
    public abstract function findById($id);
    public abstract function fetchAll();
    public abstract function save(Model $model);                   // UPDATED
}

class PersonMapper extends DataMapper {
    public function findById($id) { /* ...magic... */ }
    public function fetchAll() { /* ...magic... */ }
    public function save(PersonModel $model) { /* ...magic... */ } // UPDATED
}

abstract class Model { /* ...unchanged... */ }

class PersonModel extends Model { /* ...unchanged... */ }

注意抽象类及其实现中的Update save-Methods。由于PersonModel继承自Model,因此显然有一个共同的基本签名集,我希望这可以正常工作。但事实并非如此 - PHP抱怨子类PersonMapper中的更改接口

我的问题:

  • 是否有另一个使用PHP 5.3.10的解决方案更好地表达了这种关系?
  • 它是否适用于更高版本的PHP,因此可能值得升级服务器?

1 个答案:

答案 0 :(得分:1)

您可以尝试使用接口。

interface OtherModel {
    public function getThis();
}

interface OtherOtherModel {
    public function getThat();
}

您的Model Class可能会实现一个或多个接口......

class PersonModel extends Model implements OtherModel {
    protected $_fields = array('id', 'name', 'passhash', /*...*/);

    public function setId($value) {
        /* ...Validation happening... */
        $this->_data['id'] = $value;
        return $this;
    }

    public function checkPassword($password) { /* ...magic... */ }

    public function getThis() {
        // ...
    }
}

您的具体Mapper类可以使用instanceof来检查此模型是否完成了它应该做的事情。

class PersonMapper extends DataMapper {
    public function findById($id) { /* ...magic... */ }
    public function fetchAll() { /* ...magic... */ }
    public function save(Model $model) { 
       // verify that certain methods are implemented...
       // throw an exception or reacting accordingly
       print ($model instanceof PersonModel)? 'yes' : 'no';
       print ($model instanceof OtherOtherModel)? 'yes' : 'no';
    } 
}

另一种可能的方法可能如下:

<?php

abstract class DataMapper {
    public abstract function findById($id);
    public abstract function fetchAll();

    public  function save(Model $model) {
        throw new Exception('You have to implement this!');
    }
}

如果在继承类中未覆盖save方法,则抛出异常。 现在你可以真正使用不同的typehint。 这将有效:

class PersonMapper extends DataMapper {
    public function findById($id) { /* ...magic... */ }
    public function fetchAll() { /* ...magic... */ }
    public function save(PersonModel $model)  {
        // do something
    }
}

通过使用接口来定义实现,我可以想到另一种可能的方法。 例如:

interface PersonModelAware {
   public function save(PersonModel $model);
}

interface OtherModelAware {
   public function save(OtherModel $model);
}

等。您的抽象方法可能具有默认保存方法或根本没有保存方法。继承类将实现它所需的接口。

总结一下,让你的类型更具体是不行的,因为抽象方法清楚地表明它需要一个模型。