【问题标题】:PHP Class / OOP : When to "reference" a class within a class vs extend a class?PHP Class / OOP:何时“引用”类中的类与扩展类?
【发布时间】:2012-01-07 01:33:40
【问题描述】:

什么时候最理想:

class USER {
    // stuff about user
}

class PROFILE extends USER {
    // stuff about user's profile
}

什么时候是理想的:

class USER {
    $id;
    $profile;

    function __construct($id) {
        $this->id = $id;
        $this->profile = new PROFILE($id);
        // set profile variables
    }
}

class PROFILE {
    $id;
    // other variables pertaining to profile

    function __construct($id) {
        $this->id = $id;
    }
}

我觉得第二个例子更舒服?有什么我应该注意的具体警告吗?

我应该将一个视为不互斥的子集而将另一个视为子集吗?

【问题讨论】:

    标签: php class inheritance parent extends


    【解决方案1】:

    这是encapsulationinheritance的经典问题。

    使用封装:

    • 当您想提供一些基类的功能,但又想隐藏其中的一些。
    • 当您想要扩展/修改基类的功能,但不需要类型兼容时。

    使用继承:

    • 当您需要派生类与基类类型兼容时。换句话说,如果客户端代码需要使用 Base 引用来引用 Derived 的实例。

    在您的示例中,PROFILE 从 USER 派生是没有意义的。用户拥有个人资料,但个人资料不是用户。对于这个例子,USER 类包含一个 PROFILE 字段是有意义的。

    【讨论】:

    • 我可以假设封装对于扩展类以处理新协议有好处吗?让 USER 类扩展包含 db 连接功能的 DB 类是否有意义?
    • 当然,但类似的事情需要基类提供派生类将使用的某种通用功能。如果新类完全不同,但您想像对待第一个协议处理类一样对待它,那么您想定义一个接口或抽象基类,这两种协议都派生自。
    • 我喜欢这个解释。对于那些刚接触 OOP 的人,问问自己继承的类是否“是”父类。例如,“个人资料是用户吗?”答案是否定的,因此它不会扩展 USER。如果相反,它与问题“有一个”相匹配,那么它很可能是一个属性/属性。例如,用户“有一个”个人资料。
    【解决方案2】:

    我想说真正的方法是:

    class User {
    
        private $id,
                $profile;
    
        public function __construct($id, Profile $profile) {
            $this->id = $id;
            $this->profile = $profile;
        }
    
    }
    
    class Profile { ... }
    
    new User(42, new Profile(...));
    

    您不想扩展类,除非有明确的层次结构 (Admin extends User)。
    您也不应该像在 User 构造函数中那样耦合类,而应使用如上所示的依赖注入。

    【讨论】:

    • 所以如果我想创建一个用户但推迟创建配置文件,我可以在 USER 构造函数中将 Profile $profile = NULL 作为默认值吗?如果我愿意,以后再手动注入?我确实看到了您关于耦合类的观点以及它为什么不起作用但有时我想创建一个配置文件对象而不将其与用户相关联。或者更确切地说,有时我不想访问用户的个人资料,只想访问用户的基本信息,例如 id 和密码。
    • 好吧,在您的示例中,默认情况下每个用户都有一个配置文件,您甚至无法控制该配置文件是什么,因为它是在用户的构造函数中默认构造的。真的是一样的。但是通过将配置文件注入用户的构造函数,您可以更好地控制该配置文件是什么。您可以为不同类型的配置文件子类化/扩展 Profile 类。您可以模拟它进行单元测试。如果您的业务规则是每个用户都必须有一个配置文件,那么在构造函数中需要一个配置文件。如果它可能是可选的,请通过Profile $profile = null 使其成为可选。
    【解决方案3】:

    通常,除非您可以说 PROFILE 只是 USER 的一种特殊类型,并且在所有情况下都可以像 USER 一样对待,否则您不想使用继承来建模关系. USER 可能拥有 PROFILE,或者PROFILE 可能特定的USER 相关联,但两者是不同的实体——所以两者都不是一个应该从另一个继承。

    【讨论】:

      【解决方案4】:

      通常,父类更像是您将要扩展的泛型类,并且很少单独使用它们。所以继承更像是它应该是的方式:你扩展了一个泛型类。

      我不会再用动物比喻了,而是像这样:

      class USER {} // generic class
      
      class ADMIN extends USER {} // specific class
      
      class MOD extends USER {} // specific class
      

      有一个逻辑,因为我们扩展了一个通用的 USER 类来为每种类型的用户指定一个类。

      对于其他任何非分层的,我会使用封装。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-15
        • 2020-05-31
        • 2015-10-23
        • 2021-01-05
        • 1970-01-01
        • 2011-10-31
        • 2017-09-22
        相关资源
        最近更新 更多