duanliang789262 2013-11-05 11:14
浏览 47
已采纳

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

Warning: might cause TL:DR

I am working with PHP 5.3.10 and have the following problem. I do have an abstract class DataMapper, which is extended for the specific DataModel I want to persist. The following code does this trick:

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

This works fine, but is really quirky for my feeling.

As you can see, I've used an interface IModel to be able to tell the DataMapper, that it does need a certain set of parameters and methods. However, some Models do have extra methods needed by the corresponding DataMapper - in the example, a checkPassword() method, which is used test a password against the stored hash value. This method may also instruct the DataMapper to rehash the just tested password and update it due to new requirements (e.g. an increased difficulty for a password hash function).

So what I actually want is to change the signature of PersonMapper to PersonMapper::save(PersonModel $model) - and e.g. in another DataMapper toPostMapper::save(PostModel $model), etc. This is due to these DataMappers needing a certain signature. So my ideal solution looks like this:

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

Notice the Update save-Methods in the abstract class and its implementation. Since PersonModel is inherited from Model, thus obviously having a common base set of signatures, I would expect this to work just fine. But it doesn't - PHP complains about a changed interface in the childclass PersonMapper

My Questions:

  • Is there another solution working with PHP 5.3.10 that expresses the relationship better?
  • Does it work in a later version of PHP, so that it might be worth upgrading the server?
  • 写回答

1条回答 默认 最新

  • dpb75177 2013-11-06 21:59
    关注

    You might try using interfaces instead.

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

    Your Model Class might implement one or more interfaces...

    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() {
            // ...
        }
    }
    

    Your concrete Mapper Class can use the instanceof to check if this Model does what it should.

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

    Another possible approach might be the following:

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

    Throw an Exception if the save method is not overriden in an inheriting class. Now you can really use a different typehint. This will work:

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

    I could think of another possible approach, by using interfaces to define the implementation. Like for example:

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

    etc. Your abstract method might have a default save method or no save method at all. The inheriting class will implement the interface it needs.

    To sum it up, making your type more specific will not work as the abstract method clearly states it expects a Model.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥30 Windows Server 2016利用兩張網卡處理兩個不同網絡
  • ¥15 Python中knn问题
  • ¥15 使用C#,asp.net读取Excel文件并保存到Oracle数据库
  • ¥15 C# datagridview 单元格显示进度及值
  • ¥15 thinkphp6配合social login单点登录问题
  • ¥15 HFSS 中的 H 场图与 MATLAB 中绘制的 B1 场 部分对应不上
  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源