在PHP中构建MVC框架后,我遇到了一个问题,可以使用Java样式泛型轻松解决。抽象的Controller类可能看起来像这样:
abstract class Controller {
abstract public function addModel(Model $model);
在某些情况下,Controller类的子类应仅接受Model的子类。例如,ExtendedController应该仅将ReOrderableModel接受到addModel方法中,因为它提供了ExtendedController需要访问的reOrder()方法:
class ExtendedController extends Controller {
public function addModel(ReOrderableModel $model) {
在PHP中,继承的方法签名必须完全相同,因此即使类继承了超类中提示的类类型,也不能将类型提示更改为其他类。在Java中,我只需执行以下操作:
abstract class Controller<T> {
abstract public addModel(T model);
class ExtendedController extends Controller<ReOrderableModel> {
public addModel(ReOrderableModel model) {
但是PHP中没有泛型支持。是否有任何解决方案仍会遵循OOP原则?
编辑
我知道PHP根本不需要类型提示,但它可能是糟糕的OOP。首先,从接口(方法签名)尚不清楚应接受哪种对象。因此,如果另一个开发人员想要使用该方法,则很明显,需要X类型的对象,而不必通过不好的封装(破坏了信息隐藏原理)来查看实现(方法主体)。其次,因为没有类型安全性,所以该方法可以接受任何无效变量,这意味着到处都需要手动类型检查和异常抛出!
它对于以下测试用例似乎很有效(尽管确实会发出严格警告):
class PassMeIn
{
}
class PassMeInSubClass extends PassMeIn
{
}
class ClassProcessor
{
public function processClass (PassMeIn $class)
{
var_dump (get_class ($class));
}
}
class ClassProcessorSubClass extends ClassProcessor
{
public function processClass (PassMeInSubClass $class)
{
parent::processClass ($class);
}
}
$a = new PassMeIn;
$b = new PassMeInSubClass;
$c = new ClassProcessor;
$d = new ClassProcessorSubClass;
$c -> processClass ($a);
$c -> processClass ($b);
$d -> processClass ($b);
如果严格警告不是您真正想要的,则可以这样解决。
class ClassProcessor
{
public function processClass (PassMeIn $class)
{
var_dump (get_class ($class));
}
}
class ClassProcessorSubClass extends ClassProcessor
{
public function processClass (PassMeIn $class)
{
if ($class instanceof PassMeInSubClass)
{
parent::processClass ($class);
}
else
{
throw new InvalidArgumentException;
}
}
}
$a = new PassMeIn;
$b = new PassMeInSubClass;
$c = new ClassProcessor;
$d = new ClassProcessorSubClass;
$c -> processClass ($a);
$c -> processClass ($b);
$d -> processClass ($b);
$d -> processClass ($a);
不过,您应该记住一件事,这绝对不是OOP方面的最佳实践。如果超类可以接受特定类的对象作为方法参数,则其所有子类也应也可以接受该类的对象。防止子类处理超类可以接受的类意味着您不能使用子类代替超类,并且要100%确信它在所有情况下都可以使用。相关实践称为Liskov替代原理,它指出,除其他外,方法参数的类型只能在子类中变弱,而返回值的类型只能变强(输入只能变得更通用,输出可以仅获得更具体的信息)。
这是一个非常令人沮丧的问题,我本人已经多次尝试反对它,因此,如果在特定情况下忽略它是最好的选择,那么我建议您忽略它。但是请不要养成习惯,否则您的代码将开始开发各种微妙的相互依赖关系,这将成为调试的噩梦(单元测试无法捕获它们,因为各个单元的行为均符合预期,这是它们之间的相互作用问题所在)。如果您确实忽略了它,那么请注释该代码以使其他人知道它,这是一个有意的设计选择。
无论Java世界发明了什么,都不一定总是正确的。我想我在这里检测到违反Liskov替换原理的问题,PHP在E_STRICT模式下抱怨它是正确的:
引用维基百科:“如果S是T的子类型,则程序中T类型的对象可以用S类型的对象替换,而无需更改该程序的任何所需属性。”
T是您的控制器。S是您的ExtendedController。您应该能够在Controller工作的每个地方使用ExtendedController而不会破坏任何东西。更改addModel()方法上的typehint会破坏事情,因为在每个传递了Model类型对象的地方,如果不是偶然地通过ReOrderableModel,typehint现在将阻止传递同一对象。
如何逃避这个?
您的ExtendedController可以保留类型提示,然后检查他是否获得ReOrderableModel实例。这绕过了PHP的抱怨,但是就Liskov替代而言,它仍然使事情破裂。
更好的方法是创建一个新方法addReOrderableModel()
,该方法旨在将ReOrderableModel对象注入ExtendedController。此方法可以具有所需的typehint,并且可以在内部调用addModel()
以将模型放置在期望的位置。
如果需要使用ExtendedController而不是Controller作为参数,则可以知道可以使用添加ReOrderableModel的方法。您明确声明Controller在这种情况下不适合。每个期望传递控制器的方法都不会期望addReOrderableModel()
存在,并且永远不会尝试调用它。每个期望ExtendedController的方法都有权调用此方法,因为它必须在那里。
class ExtendedController extends Controller
{
public function addReOrderableModel(ReOrderableModel $model)
{
return $this->addModel($model);
}
}
我的解决方法如下:
/**
* Generic list logic and an abstract type validator method.
*/
abstract class AbstractList {
protected $elements;
public function __construct() {
$this->elements = array();
}
public function add($element) {
$this->validateType($element);
$this->elements[] = $element;
}
public function get($index) {
if ($index >= sizeof($this->elements)) {
throw new OutOfBoundsException();
}
return $this->elements[$index];
}
public function size() {
return sizeof($this->elements);
}
public function remove($element) {
validateType($element);
for ($i = 0; $i < sizeof($this->elements); $i++) {
if ($this->elements[$i] == $element) {
unset($this->elements[$i]);
}
}
}
protected abstract function validateType($element);
}
/**
* Extends the abstract list with the type-specific validation
*/
class MyTypeList extends AbstractList {
protected function validateType($element) {
if (!($element instanceof MyType)) {
throw new InvalidArgumentException("Parameter must be MyType instance");
}
}
}
/**
* Just an example class as a subject to validation.
*/
class MyType {
// blahblahblah
}
function proofOfConcept(AbstractList $lst) {
$lst->add(new MyType());
$lst->add("wrong type"); // Should throw IAE
}
proofOfConcept(new MyTypeList());
尽管这仍然与Java泛型有所不同,但它几乎可以将模仿行为所需的额外代码最小化。
而且,它比其他示例提供的代码要多一些,但是-至少对我而言-它似乎比大多数示例更干净(和Java对应版本更相似)。
我希望你们中的一些人会觉得有用。
欢迎对此设计进行任何改进!
我以前确实经历过相同类型的问题。我用这样的东西来解决它。
Class Myclass {
$objectParent = "MyMainParent"; //Define the interface or abstract class or the main parent class here
public function method($classObject) {
if(!$classObject instanceof $this -> objectParent) { //check
throw new Exception("Invalid Class Identified");
}
// Carry on with the function
}
}
You can consider to switch to Hack and HHVM. It is developed by Facebook and full compatible to PHP. You can decide to use <?php
or <?hh
It support that what you want:
http://docs.hhvm.com/manual/en/hack.generics.php
I know this is not PHP. But it is compatible with it, and also improves your performance dramatically.
您可以通过将类型作为构造函数的第二个参数传递来完成此操作
<?php class Collection implements IteratorAggregate{
private $type;
private $container;
public function __construct(array $collection, $type='Object'){
$this->type = $type;
foreach($collection as $value){
if(!($value instanceof $this->type)){
throw new RuntimeException('bad type for your collection');
}
}
$this->container = new \ArrayObject($collection);
}
public function getIterator(){
return $this->container->getIterator();
}
}
为了提供高水平的静态代码分析,严格的键入和可用性,我提出了以下解决方案:https : //gist.github.com/rickhub/aa6cb712990041480b11d5624a60b53b
/**
* Class GenericCollection
*/
class GenericCollection implements \IteratorAggregate, \ArrayAccess{
/**
* @var string
*/
private $type;
/**
* @var array
*/
private $items = [];
/**
* GenericCollection constructor.
*
* @param string $type
*/
public function __construct(string $type){
$this->type = $type;
}
/**
* @param $item
*
* @return bool
*/
protected function checkType($item): bool{
$type = $this->getType();
return $item instanceof $type;
}
/**
* @return string
*/
public function getType(): string{
return $this->type;
}
/**
* @param string $type
*
* @return bool
*/
public function isType(string $type): bool{
return $this->type === $type;
}
#region IteratorAggregate
/**
* @return \Traversable|$type
*/
public function getIterator(): \Traversable{
return new \ArrayIterator($this->items);
}
#endregion
#region ArrayAccess
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset){
return isset($this->items[$offset]);
}
/**
* @param mixed $offset
*
* @return mixed|null
*/
public function offsetGet($offset){
return isset($this->items[$offset]) ? $this->items[$offset] : null;
}
/**
* @param mixed $offset
* @param mixed $item
*/
public function offsetSet($offset, $item){
if(!$this->checkType($item)){
throw new \InvalidArgumentException('invalid type');
}
$offset !== null ? $this->items[$offset] = $item : $this->items[] = $item;
}
/**
* @param mixed $offset
*/
public function offsetUnset($offset){
unset($this->items[$offset]);
}
#endregion
}
/**
* Class Item
*/
class Item{
/**
* @var int
*/
public $id = null;
/**
* @var string
*/
public $data = null;
/**
* Item constructor.
*
* @param int $id
* @param string $data
*/
public function __construct(int $id, string $data){
$this->id = $id;
$this->data = $data;
}
}
/**
* Class ItemCollection
*/
class ItemCollection extends GenericCollection{
/**
* ItemCollection constructor.
*/
public function __construct(){
parent::__construct(Item::class);
}
/**
* @return \Traversable|Item[]
*/
public function getIterator(): \Traversable{
return parent::getIterator();
}
}
/**
* Class ExampleService
*/
class ExampleService{
/**
* @var ItemCollection
*/
private $items = null;
/**
* SomeService constructor.
*
* @param ItemCollection $items
*/
public function __construct(ItemCollection $items){
$this->items = $items;
}
/**
* @return void
*/
public function list(){
foreach($this->items as $item){
echo $item->data;
}
}
}
/**
* Usage
*/
$collection = new ItemCollection;
$collection[] = new Item(1, 'foo');
$collection[] = new Item(2, 'bar');
$collection[] = new Item(3, 'foobar');
$collection[] = 42; // InvalidArgumentException: invalid type
$service = new ExampleService($collection);
$service->list();
即使这样感觉会好很多:
class ExampleService{
public function __construct(Collection<Item> $items){
// ..
}
}
希望泛型将很快进入PHP。
一种选择是splat运算符+类型提示+私有数组的组合:
<?php
class Student {
public string $name;
public function __construct(string $name){
$this->name = $name;
}
}
class Classe {
private $students = [];
public function add(Student ...$student){
array_merge($this->students, $student);
}
public function getAll(){
return $this->students;
}
}
$c = new Classe();
$c->add(new Student('John'), new Student('Mary'), new Student('Kate'));
文章标签:generics , inheritance , java , oop , php
版权声明:本文为原创文章,版权归 admin 所有,欢迎分享本文,转载请保留出处!
评论已关闭!