Jon*_*han 33 php java oop generics inheritance
在用PHP构建MVC框架后,我遇到了一个问题,可以使用Java样式泛型轻松解决.抽象的Controller类可能如下所示:
abstract class Controller {
abstract public function addModel(Model $model);
Run Code Online (Sandbox Code Playgroud)
可能存在类Controller的子类只应接受Model的子类的情况.例如,ExtendedController应该只接受ReOrderableModel到addModel方法中,因为它提供了一个reRerder()方法,ExtendedController需要有权访问:
class ExtendedController extends Controller {
public function addModel(ReOrderableModel $model) {
Run Code Online (Sandbox Code Playgroud)
在PHP中,继承的方法签名必须完全相同,因此类型提示不能更改为其他类,即使该类继承了超类中提示的类类型.在java中我会这样做:
abstract class Controller<T> {
abstract public addModel(T model);
class ExtendedController extends Controller<ReOrderableModel> {
public addModel(ReOrderableModel model) {
Run Code Online (Sandbox Code Playgroud)
但PHP中没有泛型支持.有没有任何解决方案仍然符合OOP原则?
编辑 我知道PHP根本不需要类型提示,但它可能是糟糕的OOP.首先,从界面(方法签名)接收什么样的对象并不明显.因此,如果另一个开发人员想要使用该方法,那么显然需要类型为X的对象,而不必查看实现(方法体),这是一个糟糕的封装并打破了信息隐藏原则.其次,因为没有类型安全性,该方法可以接受任何无效变量,这意味着手动类型检查和遍布整个地方的异常抛出!
Gor*_*onM 12
它似乎适用于我(虽然它确实抛出严格的警告)与以下测试用例:
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);
Run Code Online (Sandbox Code Playgroud)
如果严格的警告是你真正不想要的,你可以像这样解决它.
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);
Run Code Online (Sandbox Code Playgroud)
但是你应该记住的一件事是,这绝对不是OOP术语中的最佳实践.如果超类可以接受特定类的对象作为方法参数,那么它的所有子类也应该能够接受该类的对象.防止子类处理超类可以接受的类意味着你不能使用子类代替超类,并且100%确信它在所有情况下都能工作.相关的实践被称为Liskov替换原则,它指出,除其他外,方法参数的类型只能在子类中变弱,返回值的类型只能变得更强(输入只能变得更通用,输出可以只有更具体).
这是一个非常令人沮丧的问题,我自己已经多次反对它,所以如果在特定情况下忽略它是最好的事情,那么我建议你忽略它.但是不要养成它的习惯,否则你的代码会开始形成各种微妙的相互依赖性,这将是调试的噩梦(单元测试不会捕获它们,因为单个单元将按预期运行,这是它们之间的相互作用问题出在哪里).如果你忽略它,那么评论代码让别人知道它,这是一个刻意的设计选择.
我的解决方法如下:
/**
* 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());
Run Code Online (Sandbox Code Playgroud)
虽然这仍然与Java泛型不同,但它几乎最小化了模仿行为所需的额外代码.
此外,它比其他人给出的一些例子更多一些代码,但是 - 至少对我而言 - 它似乎比大多数代码更干净(并且对Java来说更简洁).
我希望你们中的一些人会发现它很有用.
欢迎对此设计进行任何改进!
无论Java世界发明什么,都不一定总是正确的.我认为我在这里检测到违反了Liskov替换原则,并且PHP在E_STRICT模式中抱怨它是正确的:
引用维基百科:"如果S是T的子类型,那么程序中类型T的对象可以用类型S的对象替换,而不会改变该程序的任何所需属性."
T是你的控制器.S是你的ExtendedController.您应该能够在Controller工作的每个地方使用ExtendedController而不会破坏任何内容.更改addModel()方法上的typehint会破坏一些东西,因为在传递类型为Model的对象的每个地方,如果不是意外的ReOrderableModel,typehint现在会阻止传递同一个对象.
怎么逃避这个?
您的ExtendedController可以按原样保留typehint,然后检查他是否获得了ReOrderableModel的实例.这避免了PHP投诉,但它仍然在Liskov替换方面打破了局面.
更好的方法是创建一个新方法addReOrderableModel()
,用于将ReOrderableModel对象注入ExtendedController.此方法可以具有您需要的类型提示,并且可以在内部调用addModel()
以将模型放置在预期的位置.
如果您需要使用ExtendedController而不是Controller作为参数,则您知道添加ReOrderableModel的方法存在且可以使用.您明确声明Controller不适合这种情况.期望传递Controller的每个方法都不会addReOrderableModel()
存在,也永远不会尝试调用它.期望ExtendedController的每个方法都有权调用此方法,因为它必须在那里.
class ExtendedController extends Controller
{
public function addReOrderableModel(ReOrderableModel $model)
{
return $this->addModel($model);
}
}
Run Code Online (Sandbox Code Playgroud)