面向对象:继承 vs 服务

4
Class A
{
  public function __construct(Foo $foo, Bar $bar, MyCustomType1 $mct)
  {
   //...
  }
  //...
  public function getFooBarFunction()
  {
    $this->foo->aMethod();
    $this->bar->anotherMethod();
    //some other execution here
  }
}

Class B
{
  public function __construct(Foo $foo, Bar $bar, MyCustomType2 $mct)
  {
   //...
  }
  //...
  public function getFooBarFunction()
  {
    $this->foo->aMethod();
    $this->bar->anotherMethod();
    //some other execution here (same of Class A)
  }
}

Class C
{
  public function __construct(Foo $foo, Bar $bar, MyCustomType3 $mct)
  {
   //...
  }
  //...
  public function getFooBarFunction()
  {
    $this->foo->aMethod();
    $this->bar->anotherMethod();
    //some other execution here (same of Class B and Class A)
  }
}

如您所见,这段代码并不符合DRY原则。我可以将getFooBarFunction()轻松地合并到另一个类中并使用该方法。

A)创建一个超类并将getFooBarFunction()迁移到其中。我还需要复制__construct()$this->引用)。

优点
- 很容易做到
- 我可以为MyCustomType(s)创建接口,并在构造函数中使用它们来代替具体的类
缺点
- 如果子类需要在构造阶段添加参数怎么办?
- 如果我无法将MyCustomType(s)分组在同一接口下怎么办?

B)创建一个超类并使用setter“注入”FooBar对象

优点
- 也很容易
- 我不需要共享构造函数
缺点
- 如果我忘记注入参数怎么办?我必须向超类添加额外的显式检查并引发异常吗?

C)创建一个超类并让getFooBarFunction()接受FooBar对象

优点
- 也很容易
- 我不需要共享构造函数
缺点
- 在这种情况下,继承真的有必要吗?

D)创建一个独立的类(服务?),让ABC实例化它并直接使用它

哪种方法最好(或“最佳实践”)以及为什么?还有其他方法吗?

更新

类A、类B和类C是一些彼此不共享任何信息的对象(它们表示一个Room,一个Service,一个Supplement)。唯一共同的特点是每个类都与i18n表格相关联(每个类都有自己的表格)。因此,我的getFooBarFunction()只是一个用于检索静态类型的函数(存储在某个地方,它不重要),该类型指示i18n文本的类型(标题、描述、简称等等)。

真实代码更新

private function getTextTypeNameEntity()
{
    $text_type_repository = $this->getTextTypeRepository();
    $text_type_name_id = $this->container->getParameter('foo_project.text_type.name');
    $text_type_name = $text_type_repository->findOneById($text_type_name_id);

    return $text_type_name;
}

这个函数是 getFooBarFunction()


如果您的PHP版本是>=5.4.0,您可以查看traits。http://php.net/manual/en/language.oop5.traits.php - Vick
软件设计与平面或工业设计并无二致。你所问的基本上是“如何最好地设计一本书的封面(或一张桌子)”。如果不知道这本书的内容以及这张桌子将如何使用,我能想到的最好答案就是“要看情况而定”。请描述您手头具体的问题。 - georg
@georg:如果我在我的问题中提供了那种程度的抽象,那是因为我非常确定在这种情况下方法论(此案例中)比涉及简单对象的对象先出现。顺便说一句,我会更新我的问题。 - DonCallisto
1
@DonCallisto:谢谢你的回答,但我仍然认为你的问题在现有形式下无法生存。我的建议是:1)提出一个新问题,2)发布你的真实代码,3)不要让人们投票选择(“选择A、B或C”),而是描述你的问题(“太多重复代码”)并寻求建议。另外,由于你的代码已经可以工作,也许http://codereview.stackexchange.com/会是一个更好的地方。 - georg
@Vogel612:好的,请看上面的 2) - georg
显示剩余5条评论
2个回答

2

更新2的答案

你说这个函数仅通过唯一的id从一个仓库获取文本。在不知道完整类的情况下,这听起来像是代码异味。一个类应该只做一件事情并且做好它。当你实现与其无关的功能时,你会将类扩展到超出其边界。我建议将getFooBarFunction修改为只提供文本id:

public function getTextId() {
    return 'some-awesome-unique-text-id';
}

原始帖子

看到你提供的代码,我只能看到一个区别,即MyCustomType。我建议使用共享接口来进行类型提示,该接口实现可以调用每个方法的签名(接口基础知识)。这也可以应用于FooBar类。通过使用接口,您可以更轻松地交换实际的类实现。

您说:“如果我无法将自定义类型分组到同一个接口下怎么办?”这很棘手,是接口中最难做的事情之一。将接口视为契约。如果更改类的方法签名,但尝试在另一个类的位置使用它,则保证会遇到错误。这也会使您的代码更难维护/阅读,因为您将尝试处理所有边缘情况。请尽量坚持使用接口。

您说:“如果我忘记注入参数怎么办?”首先,这应该被视为一个错误,开发人员要负责,抱歉:D

然后,您稍后说:“如果我需要在构造过程中传递另一个参数呢?”对我来说,这听起来像完全不同的类,并且应该作为这样处理。然后,如果您确实需要另一个参数,可以扩展此类并仅覆盖构造函数。例如以下内容。

abstract class AbstractImplementation {

    public function __construct(FooInterface $foo, BarInterface $bar, CustomInterface $custom) {

        // Initialize properties.

    }

    /*
     * This could also be declared abstract if each implementation
     * is different.
     */
    public function getFooBarFunction() {

        // Perform basic actions

    }

}

class ActualImplementation extends AbstractExample {

    public function getFooBarFunction() {

        // Perform specific actions

    }

}

如果你需要另一个参数,你可以这样做。但这应该被视为边缘情况。

class ExtendedImplementation extends ActualImplementation {

    public function __construct(Extra $extra, FooInterface $foo, BarInterface $bar, CustomInterface $custom) {

        parent::__construct($foo, $bar, $custom);

        $this->extra = $extra;

    }

}

希望我的想法能对您有所帮助,愉快地编写代码!

@DonCallisto - 我无法想象任何情况下你会重载一个方法。PHP(以及许多其他语言)没有实现这样的功能可能有其原因。 - AnotherGuy
@DonCallisto - 首先,不存在超类,只有被覆盖的实际实现。我认为你把方法重载覆盖混淆了。除非定义了final关键字,否则你总是可以覆盖方法/构造函数。你无法特定地重载方法,但可以使用魔术方法对整个类进行重载。在我的例子中,构造函数被覆盖,但父构造函数仍然被使用。 - AnotherGuy
@DonCallisto - 请阅读关于重载的文档。在那里,您可以看到只有使用魔术方法才能进行重载。 - AnotherGuy
@DonCallisto 这是覆盖。新的构造函数_覆盖_旧的构造函数,因为它属于子类。 - MikeSW
@MikeSW:是的,他说得对,我把事情搞糟了。 - DonCallisto

0

我会选择 D + 接口化你的类。如果 getFooBarFunction() 和 A、B 或 C 的内部工作无关,并且在所有类中实现始终相同,则不应该是这些类的成员。如果调用 A、B 和 C 的代码需要知道该方法的存在,我会在接口中提取 getFooBarFunction() 并在 A、B 和 C 中实现它。这样,外部代码可以确保该方法始终存在并以有效的方式实现。


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