【问题标题】:Using Interfaces in an custom OO PHP framework在自定义 OO PHP 框架中使用接口
【发布时间】:2011-06-21 11:54:14
【问题描述】:

我正在开发一个相当简单的 OO PHP 框架(我猜在这种情况下并不重要..),具有以下基本结构:

application/
  classes/
  controllers/
  includes/
  models/
  views/
classes/
includes/

我知道使用接口而不是硬编码类是 OOP 的良好做法,但我不确定在接口目录和文件的实际位置/结构方面的最佳做法是什么。

接口是否应该在一个目录下分成多个文件:

interfaces/
  iDatabase.php
  iRouter.php

或者应该把它们都放在一个文件中,因为它们不是那么大:

includes/
  interfaces.php (with all Interfaces inside)

使用第一个选项,我可以使用自动加载器来加载接口,而不必加载每个文件,而并非所有文件都可以使用,而使用第二个选项,它们最初都会被加载,但这样我就不必加载多个文件每次。

你的想法是什么?我是不是完全看错了(我倾向于解决我的大部分问题,直到有人引导我朝着正确的方向前进!哈哈)

谢谢大家!

瑞恩

2011-02-07 编辑:

在阅读了迄今为止给我的答案后,我尝试了一些方法。

假设下面的类是从磁盘上的确切位置自动加载的(Database_Database 将在 'classes/Database/Database.php' 中加载),这样设置是否有效?

class Database_Mysql_Database extends Database_DatabaseAbstract implements Database_Database {}

Database_Mysql_Database是一个普通类,Database_DatabaseAbstract是一个抽象类,具有不同类型数据库通用的基本方法,Database_Database是用户将输入提示以确保与他们的类兼容的接口。

我走对了吗?

【问题讨论】:

    标签: php oop interface directory-structure


    【解决方案1】:

    标准方法是像对待类一样对待它们 - 将它们存储在单独的文件中,使用自动加载器加载。另外:

    • 使用标准命名约定,您将调用接口 Database 和 Router,而不是 iDatabase 和 iRouter。

    • 在分析之前不要考虑性能:您如何确定每个请求都解析所有接口的影响不会抵消加载许多文件的影响?无论如何,这些文件都会在缓冲区中。

    编辑:(在问题被编辑后)。

    你想要:

    • Database (classes/Database.php),用于类型提示/代码完成的接口(如果您坚持首先要有一个接口,但我们不讨论这个);
    • Database_Abstract (classes/Database/Abstract.php),一个实现Database并具有通用、抽象功能的类;用户永远不会意识到它的存在;
    • Database_Mysql (classes/Database/Mysql.php),一个扩展 Database_Abstract 的类。它不必实现 Database(Database_Abstract 已经实现了);

    您还可以在所有路径(和名称)前面加上您的框架名称;这样,如果您使用 ZF 的某些类,例如生成 PDF,您可以保留相同的自动加载器和相同的类目录,而不会将两者混淆。

    如果您真的热衷于 OOP,您可以添加:

    • Database_Factory (classes/Database/Factory.php) - 有一个名为 createDatabase 的方法的类;该方法的文档注释为 /** @returns Database */ 并接受数据库配置(作为字符串或数组,或者如果您是硬核,则可能是 Database_Config 对象);这对于真正将实现与接口分离是必要的。

    至于给你一些不同的命名文件想法的答案,请考虑它们,但要考虑到: - 一致性本身就很有价值; - 如果你坚持 pear 约定,你可以使用相同的自动加载器来加载 pear 类和许多 PHP 库; - 如果你坚持 pear 约定,你就会习惯现代 PHP。

    【讨论】:

    • 一个很好的答案。为此非常感谢!感谢您的编辑和一些额外的信息——工厂课程有一天可能会派上用场。不幸的是,如果有更好的东西,我是那些不喜欢遵守约定的人之一——即使这意味着以后的其他事情可能会变得有点棘手。 PEAR 约定被如此广泛地使用真是太好了,但在我的情况下可能有更聪明的方法来做到这一点。即使这意味着我以后需要一个更复杂的自动加载器来加载不同的库。我会继续试验,看看什么最适合我。
    【解决方案2】:

    如果您使用 PEAR 或 Zend 命名约定,该接口将位于 components 文件夹中。

    即 Myprefix_Router_Interface 类转换为路径 /Myprefix/Router/Interface.php

    这是划分组件的好方法,并且在仅查看类名时使组织合乎逻辑(您确切地知道特定文件的位置)。

    【讨论】:

      【解决方案3】:

      一般来说,努力做到:

      • 使用自动加载器。
      • 让一个文件包含一个类。
      • 让接口靠近其具体实现。

      这是一篇关于这个主题的好文章:

      http://weierophinney.net/matthew/archives/254-Why-PHP-Namespaces-Matter.html

      【讨论】:

      • +1 我添加了“如果包含的数量是性能猪,则使用操作码缓存。”
      • 搞砸条件——只要让它“使用操作码缓存”。 :)
      【解决方案4】:

      就个人而言,我建议您将接口和异常放在语义上合适的地方。没有理由将它们全部集中到一个远离班级的文件夹中。但与此同时,不要仅仅为了它而将它们放在具体实现旁边。我举个例子吧。

      假设我们正在处理一个数据库抽象层。您将拥有一个iDatabase 接口和一个iDatabaseDriver 接口。假设你的文件夹(和类)结构是这样的:

      /classes/database/idatabase.php
      /classes/database/database.php
      /classes/database/drivers/mysql/databasedrivermysql.php
      /classes/database/drivers/postgres/databasedriverpostgres.php
      

      现在,有 2 个逻辑位置可以放置 iDatabaseDriver。您可以将其放在数据库或驱动程序下。就个人而言,我会将它放在数据库下,因为它靠近需要它的位置(因为Database 更可能需要iDatabaseDriver,因此存在依赖关系)。

      因此,您可以看到,有时将接口放在具体实现旁边在语义上是合适的。但其他时候,将接口放在依赖项旁边比具体实现更合适。

      现在这个例子过于简单化了,但我认为它应该明白这一点。

      • 对接口的命名和存储有规则

        想出一个组织代码的系统。这样,自动加载就更容易预测和更容易了。另外,当您可以根据规则判断某物应该在哪里时,维护起来会变得容易得多

      • 遵守这些规则!

        这比制定规则更重要。如果您不遵守规则,那比根本没有规则更糟糕,因为您期望某些事情不会发生。

      • 语义关系优于代码级关系

        接口与其具体实现之间的语义关系比接口是接口的关系更重要。所以把语义相关的代码放在相同(或相似)的地方。

      编辑:关于命名和您的编辑:

      就个人而言,我讨厌Database_Database 这样的东西。尽管考虑到应用程序的结构,它可能是有意义的,但它没有任何语义意义。相反,我喜欢在我的自动加载器中做的是测试文件,如果它不存在但目录存在,则测试该目录中的相同文件。所以,Database 将导致签入/database.php,如果失败,/database/database.php。它消除了双重命名的需要。 Database_DatabaseAbstract 将变为 Database_Abstract。所以你的Database_Mysql_Database 可以变成Database_Mysql 存储在/database/mysql/mysql.php 中(这对我来说似乎更干净)。

      就抽象类等的命名约定而言,我个人更喜欢通过名称来识别接口。它使一目了然更容易理解(您知道public function foo(iDatabase $database) 正在寻找接口的实例而不是抽象类或具体类)。现在,有两种真正的方法可以做到这一点。

      1. 将Interface 附加到名称后,因此Database_Database 将变为Database_Interface。我个人觉得这对于我的需求来说有点过于冗长,但是这里的好处是你所有的特殊类类型(异常、接口、迭代器等)都可以像这样简单地映射。类名准确地告诉你你所拥有的,没有任何歧义。

      2. 在整个序列前加上i。所以Database_Database 会变成iDatabase,然后在自动加载器中被翻译成/database/interface.php。然后,如果你有更深的接口,iDatabase_Mysql_Query 也可以工作(这将映射到/database/mysql/query/interface.php。

      就抽象类而言,我不会那样做。一个类是抽象的这一事实不应该与它的语义含义有任何关系。抽象性质是一种编码结构,而不是语义结构(抽象类仅用于继承,因为您使用接口进行类型检查)。因此,我不建议在类名中包含 Abstract。只需调用它Database 就可以了。它在语义上更好地阅读(恕我直言)并传达相同的含义。

      我希望这会有所帮助并且有意义...

      【讨论】:

      • 非常感谢这个答案,这可能是迄今为止最有帮助的!阅读此内容后,我编辑了我的问题;你对我的编辑有什么想法吗?
      猜你喜欢
      • 1970-01-01
      • 2013-01-06
      • 1970-01-01
      • 1970-01-01
      • 2012-08-18
      • 2010-09-09
      • 1970-01-01
      • 2015-01-10
      • 1970-01-01
      相关资源
      最近更新 更多