使用特征和接口

利用接口作为建立一组类的分类,并保证某些方法的存在,被认为是一种最好的做法。Traits和Interface经常一起工作,是实现的一个重要方面。只要你有一个经常使用的Interface,定义了一个代码不会改变的方法(比如setter或getter),那么也定义一个包含实际代码实现的Trait是很有用的。

如何做…

1.在这个例子中,我们将使用ConnectionAwareInterface,首先在第4章,使用PHP面向对象编程中介绍。这个接口定义了一个setConnection()方法来设置$connection属性。Application\Generic命名空间中的两个类,CountryListCustomerList,包含了多余的代码,它们与接口中定义的方法相匹配。

  1. 以下是更改前的CountryList的样子。
  1. class CountryList
  2. {
  3. protected $connection;
  4. protected $key = 'iso3';
  5. protected $value = 'name';
  6. protected $table = 'iso_country_codes';
  7. public function setConnection(Connection $connection)
  8. {
  9. $this->connection = $connection;
  10. }
  11. public function list()
  12. {
  13. $list = [];
  14. $sql = sprintf('SELECT %s,%s FROM %s', $this->key,
  15. $this->value, $this->table);
  16. $stmt = $this->connection->pdo->query($sql);
  17. while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
  18. $list[$item[$this->key]] = $item[$this->value];
  19. }
  20. return $list;
  21. }
  22. }
  1. 现在我们将list()移到一个名为ListTrait的特质中去。
  1. trait ListTrait
  2. {
  3. public function list()
  4. {
  5. $list = [];
  6. $sql = sprintf('SELECT %s,%s FROM %s',
  7. $this->key, $this->value, $this->table);
  8. $stmt = $this->connection->pdo->query($sql);
  9. while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
  10. $list[$item[$this->key]] = $item[$this->value];
  11. }
  12. return $list;
  13. }
  14. }
  1. 然后我们可以将ListTrait中的代码插入到一个新的类—CountryListUsingTrait中,如下图所示。
  1. class CountryListUsingTrait
  2. {
  3. use ListTrait;
  4. protected $connection;
  5. protected $key = 'iso3';
  6. protected $value = 'name';
  7. protected $table = 'iso_country_codes';
  8. public function setConnection(Connection $connection)
  9. {
  10. $this->connection = $connection;
  11. }
  12. }
  1. 接下来,我们观察到很多类需要设置一个连接实例。同样,这需要一个trait。然而这次,我们将trait放在Application\Database命名空间中。这就是新的trait。
  1. namespace Application\Database;
  2. trait ConnectionTrait
  3. {
  4. protected $connection;
  5. public function setConnection(Connection $connection)
  6. {
  7. $this->connection = $connection;
  8. }
  9. }
  1. 特质经常被用来避免代码的重复。通常情况下,你还需要确定使用该特征的类。一个好的方法是开发一个与trait匹配的接口。在这个例子中,我们将定义Application\Database\ConnectionAwareInterface
  1. namespace Application\Database;
  2. use Application\Database\Connection;
  3. interface ConnectionAwareInterface
  4. {
  5. public function setConnection(Connection $connection);
  6. }
  1. 而这里是修改后的CountryListUsingTrait类。请注意,由于新的trait受其在命名空间中的位置影响,我们需要在类的顶部添加一个使用声明。你还会注意到,我们实现了ConnectionAwareInterface来标识这个类需要trait中定义的方法的事实。请注意,我们正在利用新的 PHP 7 组使用语法。
  1. namespace Application\Generic;
  2. use PDO;
  3. use Application\Database\ {
  4. Connection, ConnectionTrait, ConnectionAwareInterface
  5. };
  6. class CountryListUsingTrait implements ConnectionAwareInterface
  7. {
  8. use ListTrait;
  9. use ConnectionTrait;
  10. protected $key = 'iso3';
  11. protected $value = 'name';
  12. protected $table = 'iso_country_codes';
  13. }

如何运行…

首先,确保在第4章 “使用PHP面向对象编程 “中开发的类已经被创建。这些类包括Application\Generic\CountryListApplication\Generic\CustomerList类,这些类在第4章 “使用接口 “中讨论过。将每个类以CountryListUsingTrait.phpCustomerListUsingTrait.php的形式保存在Application\Generic文件夹下的新文件中。一定要把类名改成与新文件名一致的名字!

如第3步所述,从CountryListUsingTrait.phpCustomerListUsingTrait.php中删除list()方法。添加use ListTrait;来代替删除的方法。将删除的代码放到一个单独的文件中,在同一个文件夹中,名为ListTrait.php

你还会注意到两个list类之间的代码进一步重复,在这种情况下,setConnection()方法。这将调用另一个特质!

CountryListUsingTrait.phpCustomerListUsingTrait.php列表类中的setConnection()方法剪掉,并将删除的代码放到一个单独的文件中,名为ConnectionTrait.php。由于这个trait在逻辑上与ConnectionAwareInterfaceConnection有关,所以将该文件放在Application\Database文件夹下是合理的,并相应地指定其命名空间。

最后,定义Application\Database\ConnectionAwareInterface,如步骤6中讨论的那样。下面是所有改动后的最终Application\Generic\CustomerListUsingTrait类。

  1. <?php
  2. namespace Application\Generic;
  3. use PDO;
  4. use Application\Database\Connection;
  5. use Application\Database\ConnectionTrait;
  6. use Application\Database\ConnectionAwareInterface;
  7. class CustomerListUsingTrait implements ConnectionAwareInterface
  8. {
  9. use ListTrait;
  10. use ConnectionTrait;
  11. protected $key = 'id';
  12. protected $value = 'name';
  13. protected $table = 'customer';
  14. }

现在你可以将第4章,PHP面向对象编程中提到的chap_04_oop_simple_interfaces_example.php文件复制到一个名为chap_13_trait_and_interface.php的新文件中。将CountryList的引用改为CountryListUsingTrait。同样地,将CustomerList的引用改为CustomerListUsingTrait。否则,代码可以保持不变。

  1. <?php
  2. define('DB_CONFIG_FILE', '/../config/db.config.php');
  3. require __DIR__ . '/../Application/Autoload/Loader.php';
  4. Application\Autoload\Loader::init(__DIR__ . '/..');
  5. $params = include __DIR__ . DB_CONFIG_FILE;
  6. try {
  7. $list = Application\Generic\ListFactory::factory(
  8. new Application\Generic\CountryListUsingTrait(), $params);
  9. echo 'Country List' . PHP_EOL;
  10. foreach ($list->list() as $item) echo $item . ' ';
  11. $list = Application\Generic\ListFactory::factory(
  12. new Application\Generic\CustomerListUsingTrait(),
  13. $params);
  14. echo 'Customer List' . PHP_EOL;
  15. foreach ($list->list() as $item) echo $item . ' ';
  16. } catch (Throwable $e) {
  17. echo $e->getMessage();
  18. }

输出将与第4章 “使用面向对象的编程” 中的使用接口示例中的描述一模一样。你可以在下面的截图中看到输出的国家列表部分。

使用特征和接口 - 图1

下一张图片显示输出的客户列表部分。

使用特征和接口 - 图2