【问题标题】:Programming Paradigms: Strongly Typed parameter with Inheritance编程范式:具有继承的强类型参数
【发布时间】:2013-11-16 05:59:26
【问题描述】:

警告:可能导致 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 toPostMapper::save(PostModel $model) 等。这是由于这些 DataMapper 需要一定的签名。所以我理想的解决方案是这样的:

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... */ }

注意抽象类中的更新保存方法及其实现。由于PersonModel 继承自Model,因此显然有一组通用的基本签名,我希望这可以正常工作。但事实并非如此——PHP 抱怨子类 PersonMapper 中的接口发生了变化

我的问题:

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

【问题讨论】:

    标签: php oop design-patterns architecture


    【解决方案1】:

    您可以尝试改用接口。

    interface OtherModel {
        public function getThis();
    }
    
    interface OtherOtherModel {
        public function getThat();
    }
    

    您的模型类可能实现一个或多个接口...

    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!');
        }
    }
    

    如果在继承类中未覆盖保存方法,则抛出异常。 现在你真的可以使用不同的类型提示了。 这将起作用:

    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);
    }
    

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

    总而言之,使您的类型更具体是行不通的,因为抽象方法清楚地表明它需要一个模型。

    【讨论】:

    • 是的,但我现在的解决方案也可以做到这一点。问题是,是否有可能在方法的签名中而不是在正文中获得要求。如果您只阅读签名,您仍然不知道保存功能是否需要 PersonModel 而不是其他模型,这就是我想要实现的目标。
    • @Lars 好的。您的方法的问题是您使提示类型更加明确,这将不起作用。我可以想到另一种方法,而不是在抽象类中声明抽象保存方法,而是创建不同的接口 als PersonModelAware 包含 save(PersonModel $model) 等。这是一种有效的方法。
    • 我对 DataMapper 进行了一些更改,使其不包含抽象方法,这些方法现在实现了默认行为。我不完全理解您评论的解决方案 - 您能用一个例子更新您的答案吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-13
    • 1970-01-01
    • 1970-01-01
    • 2011-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多