用trait实现多继承,怎么限定参数类型呢?

使用trait实现多继承,但是无法限定参数的类型。例如下面的例子

trait T{
    function t(){
        echo 'trait';
    }
    //省略其他很多代码
}

class B{ } // 假设 B 是一个三方模块

class C extends B{ // 假设 C 必须继承三方模块 B
    use T;
}

// 以下客户端代码里面的某些类里面的某些函数可能需要使用到 T 中的若干方法,类似下面这样
function f(T $t){
    // 确保 t() 可以被调用,于是限定参数类型,但是有问题
    $t->t();
}

f(new C);

沙箱测试发现报错 Fatal error: Uncaught TypeError: f(): Argument #1 ($t) must be of type T, C given

如果把f(T $t)改成f(C $t)则没问题,但这不是我想要的。
如果把trait改成接口的话也不行,因为接口里面不能写函数体
如果把trait改成类的话也不行,因为 C 已经继承了一个类,无法再继承另一个类
究竟怎样做才能既可以实现多继承又可以限定类型呢?

讨论数量: 38
class a extends b
class c extends a

一样多重继承啊?

而且,还有抽象类可以用啊。

抽象类实现方法,主体class实现接口。而且,限定为T的理由是什么?想实现什么?确保有t方法可以被调用吗?

interface A {
    public function t();
}
trait T {
    public function t() {
        echo '我实现了A的约束';
    }
}

class C implements A {use T;}

function f(A $t) {
    $t->t();
}

f(new C());
1年前 评论
zhaiduting (楼主) 1年前
cevin (作者) 1年前
cevin (作者) 1年前
zhaiduting (楼主) 1年前
cevin (作者) 1年前
zhaiduting (楼主) 1年前
cevin (作者) 1年前

说到底还是一个多继承的问题,使用 trait 却不能限定参数类型,不完美

1年前 评论

你想要的是接口来限制,而不是用trait。trait只是负责额外的添加方法

1年前 评论
zhaiduting (楼主) 1年前
deatil (作者) 1年前
╰ゝSakura

trait用法都搞错了,trait是用来解决代码中复用的代码,你要做约束t方法的话,用interface才对,而且你这个想法有点怪异

1年前 评论
╰ゝSakura (作者) 1年前
zhaiduting (楼主) 1年前
╰ゝSakura (作者) 1年前
zhaiduting (楼主) 1年前
zhaiduting (楼主) 1年前
滚球兽进化 1年前

我的错,没有描述清楚。已更新代码注释为:确保 t() 可以被调用,于是限定参数类型,但是有问题

1年前 评论
╰ゝSakura

参考下这个,另外我看你的示例,如果你的t()方法每个子类都是一样的话,那为啥不写在B里面,为啥要搞复杂,弄多继承

interface T
{
    function t();
}

class B
{
}

class C extends B implements T
{
    function t()
    {
        echo "a";
    }
}

function f(T $t)
{
    $t->t();
}

f(new C);
1年前 评论
╰ゝSakura (作者) 1年前
zhaiduting (楼主) 1年前

说白了,trait 是为了解决部分代码多继承复用的问题,不是用来丰富多态的

1年前 评论

参考 PHP: 协变与逆变 - Manual

  • 协变使子类比父类方法能返回更具体的类型;
  • 逆变使子类比父类方法参数类型能接受更模糊的类型。

在以下情况下,类型声明被认为更具体:

  • 在 联合类型 中删除类型
  • 在 交集类型 中添加类型
  • 类类型(class type)修改为子类类型
  • iterable 修改为 array 或者 Traversable
  • 如果情况相反,则类型类被认为是模糊的。
1年前 评论

问题出在 trait 不能实现接口,如果也能规定 trait 这个问题可能就没有了

1年前 评论

感谢大家的回复!1楼已经给出解决方案,就是联合使用接口与 trait 如 class C implements A {use T;} 这样的写法。我只是觉得这样的写法比较麻烦,又是接口又是trait的,而且缺一不可。还不如直接去掉类型提示来得简单,例如这样

// 以下函数干脆就不限制类型
function f($t)
{
    $t->t();
}
1年前 评论

有没办法更新PHP版本,放宽接口的限制,只要接口里面可以直接写函数体,那这个问题就可以轻松解决。如果接口里面可以写函数体,那多继承几乎也就实现了。

或者干脆就实现多继承,那class Z extends X, Y这样的写法就没问题了

1年前 评论
cevin 1年前
zhaiduting (作者) (楼主) 1年前
zhaiduting (作者) (楼主) 1年前

我有个想法,不知道能否实现?也就是写一个专门用来处理 trait 类型异常的模块 TraitTypeError 然后在工程开头全局引入这个模块即可。模块逻辑大致如下:
1、匹配异常信息 must be of type T, C given 里面的关键字 T 和 C
2、使用 ReflectionClass 类获取 C 的代码块;
3、在 C 的代码块搜索关键字 use T,若找到则忽略异常并继续执行代码,否则抛出异常。

捕获异常可以用 set_exception_handler('exception_handler') 问题是如何忽略这个异常并保证程序继续执行呢?求赐教

1年前 评论
xiaochong0302

我只用Triat来复用一些代码,把它看成一个"小型片段库",你用的太高级了,跟不上你们了 :joy:

1年前 评论
秦晓武

换个思路(先抛开 trait):

  1. 限定参数是为了保证入参有可调用的函数,目前用接口是最合适的
  2. 因为多个类需要实现此接口,所以冗余了很多代码
  3. 不想写很多重复的东西,此时把冗余的代码抽象成专门的 trait

结论:

  1. 不是要解决 trait 的限定问题,而是要解决接口的 trait 化问题
  2. 如果先有的 trait T 此时建议分离个 T2 出来,让 T use T2(改动小,无缝重构), T2 负责实现接口的函数逻辑
  3. 也可以直接把T 拆分成 T1 (公共逻辑) 和 T2 (接口实现),让 class A implete I2 use T1, T2, 这样更清晰
1年前 评论
秦晓武 (作者) 1年前
zhaiduting (楼主) 1年前
zhaiduting (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!