PHP和多重继承; 我知道你不能这样做,那么我应该如何处理?

3
我知道多重继承1在PHP中不被支持,虽然有许多“hack”或变通方法可以模拟它,但我也知道像对象组合这样的方法可能比这些变通方法更加灵活、稳定和易懂。有趣的是,PHP 5.4的traits将成为适当的解决方案,但我们还没有完全到达那里。
现在,这不仅仅是一个“amidoinitrite?”的问题,而是我想确保我的方法对其他人来说是有意义的。
假设我有类ActionEvent(还有更多,但我们会保持简洁),它们都需要非常相似的方法,显然的方法是创建一个共同的基类,进行扩展;毕竟,它们在概念上足够相似,可以构成类层次结构中的兄弟姐妹(我想)。
问题是Event需要扩展一个类(Exception),但该类本身不能扩展任何内容。这些方法(和属性)都与"属性"值有关,我们将它们称为"选项"和"数据",其中"选项"是存储在类级别上的值,而"数据"是存储在实例级别上的值。
除了Exception类(无意中的双关语),我可以简单地创建一个通用类,让所有相关对象扩展以继承必要的功能,但我想知道如何避免在Event中似乎不可避免的代码重复;此外,其他概念上不足以成为兄弟的类也需要这个功能。
到目前为止,答案似乎是使用对象组合方法,创建一个Data类,并在两个点上管理它:
  • 在对象实例化时,创建一个Data实例作为对象使用的"data"。
  • 在某个时间点(可能通过静态initialize()方法),创建一个Data实例作为类使用的"options"。

例如,名为IDataIOption的接口将由需要此功能的类实现。IData只是在消费者上强制执行Data类的实例方法,并且调用将转发到实例Data属性对象,而IOption将强制执行类似命名的方法(用"data"替换"option"),并且这些方法将转发到静态Data属性对象。

我想要的是像这样的东西(这里的方法看起来有些天真,但我已经简化了它们以使其更简洁):

interface IData{

    public function setData($name, $value);

    public function putData($name, &$variable);

    public function getData($name = null);

}

interface IOption{

    public static function initializeOptions();

    public static function setOption($name, $value);

    public static function setOptions(Array $options);

    public static function getOptions($name = null);

}

class Data implements IData{

    private $_values = array();

    public function setData($name, $value){
        $this->_values[$name] = $value;
    }

    public function putData($name, &$variable){
        $this->_values[$name] = &$variable;
    }

    public function getData($name = null){
        if(null === $name){
            return $this->_values;
        }
        if(isset($this->_values[$name])){
            return $this->_values[$name];
        }
        return null;
    }

}

class Test implements IData, IOption{

    private static $_option;
    private $_data;

    public static function initializeOptions(){
        self::$_option = new Data();
    }

    public static function setOption($name, $value){
        self::$_option->setData($name, $value);
    }

    public static function setOptions(Array $options){
        foreach($options as $name => $value){
            self::$_option->setData($name, $value);
        }
    }

    public static function getOptions($name = null){
        return self::$_option->getOptions($name);
    }

    public function __construct(){
        $this->_data = new Data();
    }

    public function setData($name, $value){
        $this->_data->setData($name, $value);
        return $this;
    }

    public function putData($name, &$variable){
        $this->_data->putData($name, $variable);
        return $this;
    }

    public function getData($name = null){
        return $this->_data->getData($name);
    }

}

那么我接下来该怎么办?我无法摆脱这种感觉,觉得我正在远离良好的设计;我在客户端类和存储类之间引入了不可逆的依赖关系,而接口无法明确强制执行。


编辑:或者,我可以将对于Data的引用(无论何时需要)设为公共,从而消除代理方法的需求,简化组合。然而问题是,如果我需要使getData()像这个代码片段所示那样递归地工作,那么我就不能偏离Data类的功能性。
function getData($name = null){
    if(null === $name){
        // $parent_object would refer to $this->_parent
        // in the Test class, given it had a hierarchal
        // implementation
        return array_replace($parent_object->getData(), $this->_values);
    }
    // ...
}

当然,这一切都归结于每个类的单独定义,以支持任何与默认实现不同的偏差。
我想最终问题在于,我很难理解何时代码重复是“可以”(更准确地说,是不可避免的),何时可以将常用功能提取到容器中,并如何跨类引用和使用所包含的功能,在必要时进行微小的偏差。再次强调,特质(在我的beta测试中)似乎非常适合这里,但组成原则早在5.4之前就存在(对于PHP来说也是如此),我确信有一种“经典”的方法可以实现这一点。

有趣的是,维基百科上 多重继承 的页面已被标记为版权调查。钻石问题似乎是一个合适的替代品。


1
真正的问题是,它是否值得? - Robert Harvey
@RobertHarvey - 我想我知道你的意思,但能详细说明一下吗?(简短回答:是的,如果它可以节省我的击键并集中重复的代码,我认为这是可行的,但我也愿意接受影响) - Dan Lugg
你从中获得的微小好处将被你引入的晦涩所淹没。接替你的开发人员会感到困惑,甚至可能会说:“这是什么,我完全不懂…” - Robert Harvey
@RobertHarvey - 除了依赖项引入之外,我的担忧还在于此。我不确定除了直接复制粘贴并在必要时重复方法(或者出去享受新鲜的秋天空气,直到5.4变得可用)之外,还有什么其他方法可以解决这个问题。 - Dan Lugg
你总有一天会发现(可能会像我一样经历很多痛苦),组合比任何类型的继承更能适应时间的推移。继承也存在缺陷,因为并不是每个分类法都可以用简单的层次结构充分表示。这里有一个很好的例子:http://mitpress.mit.edu/sicp/full-text/book/ch2-Z-G-67.gif - Deleted
感谢@ChrisSmith - 是的,我发现这越来越快了。此示例是这样的,即并非所有建议的同级类都应该成为同级;这当然是为什么继承在这里不是答案的原因。组合绝对是正确的方法;但是即使组合可能也不是解决方案,因为每个类(至少对于实例方法而言)的差异都足以证明它们自己的定义。这真的很不幸,因为有很多潜力可以集中否则重复的代码。 - Dan Lugg
1个回答

1

编辑:我刚刚重新阅读了您的问题,您似乎在暗示您实际上正在使用getter和setter来操作数据。如果是这种情况,您能否提供更多关于您试图实现什么的详细信息。我怀疑您决定对对象和数据进行建模的方式导致了这种情况,而另一种方法可能会解决问题。

您不需要多重继承。您甚至不需要大部分编写的代码。

如果类“Data”和“Option”的目的只是存储数据,则使用数组。或者,如果您喜欢对象的语法,请将数组转换为对象或stdClass的实例:

$person = (object)array(
    'name' => 'Peter',
    'gender' => 'Male'
);

OR

$person = new stdClass;
$person->name = 'Peter';
$person->gender = 'Male';

拥有一堆不实际对数据进行任何操作的getter和setter是毫无意义的。


感谢@PeterHorne - 我同意,天真的getter和setter会导致代码膨胀,但我已经为了简洁而精简了它们(具有讽刺意味的是问题本身已经足够长了)。getter做了一些递归,因为对象是树节点(我将使用完整实现进行编辑),但我想我的问题可以指定为静态“选项”方法。由于几乎每个类都有可以静态设置的选项,是否有一种方法(不包括继承,因为我已经意识到它对此用途不合适)可以在类之间共享函数而不使用traits?(我正在不耐烦地等待5.4版本 - Dan Lugg
我不知道在 < 5.4 版本中缺少 traits 的解决方法,但是采用不同的对象建模方法可能会完全避开这个问题。如果您愿意分享您的代码,那么我很乐意查看相关示例的 pastebin。 - Peter Horne
好的@PeterHorne - 正如我所提到的,我现在更关心静态成员(实例成员在类之间变化很大,因此根据需要复制方法可能更简单),因为静态成员在类之间根本不需要改变。再次说明,这个问题似乎是应该用特性来解决的典型问题;无论如何,这是pastebin http://pastebin.com/y3Tzq9cu - 这可能是一个常见的设计问题。我想创建一个公共对象来封装每个对象中的这个功能;但是然后它需要被初始化。 - Dan Lugg
1
我已将该类分成两个不同的类:一个管理选项的类,以及一个执行受选项类设置影响的功能的类。将选项类作为依赖项传递到第二个类中,问题得到解决。您可以将相同的选项对象传递给多个类的多个实例,或者利用不同的选项类实例。http://pastebin.com/QcRDmums - Peter Horne
感谢@PeterHorne-这正是我所追求的方向;问题在于类范围的静态选项对象和初始化。在实践中,实例选项(数组)会传递给相关方法(仅在方法调用的生命周期内存在),这些方法会覆盖类选项,进而覆盖类默认值,并相应地使用它们。在每个类定义文件的结尾,我可以调用一个静态方法来设置静态选项/默认对象;不幸的是,PHP中的静态成员不能初始化为复杂类型,否则这个问题将得到解决。 - Dan Lugg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接