15.3用了工厂方法模式的数据访问程序

代码结构图 第十五章 就不能不换DB吗?——抽象工厂模式 - 图1IUser接口,用于客户端访问,解除与具体数据库访问的耦合。

  1. interface IUser
  2. {
  3. public function Insert($user);
  4. public function GetUser($id);
  5. }

SqlServerUser类,用于访问SQL Server的User。

  1. class SqlServerUser implements IUser
  2. {
  3. public function Insert($user)
  4. {
  5. echo '在SQL Server中给User表增加一条记录' . PHP_EOL;
  6. }
  7. public function GetUser($id)
  8. {
  9. echo '在SQL Server中根据ID得到User表一条记录' . PHP_EOL;
  10. }
  11. }

AccessUser类,用于访问Access的User。

  1. class AccessUser implements IUser
  2. {
  3. public function Insert($user)
  4. {
  5. echo '在Access中给User表增加一条记录' . PHP_EOL;
  6. }
  7. public function GetUser($id)
  8. {
  9. echo '在Access中根据ID得到User表一条记录' . PHP_EOL;
  10. }
  11. }

IFactory接口,定义一个创建访问User表对象的抽象的工厂接口。

  1. interface IFactory
  2. {
  3. public function CreateUser();
  4. }

SqlServerFactory类,实现IFactory接口,实例化SqlServerUser。

  1. class SqlServerFactory implements IFactory
  2. {
  3. public function CreateUser()
  4. {
  5. return new SqlServerUser();
  6. }
  7. }

AccessFactory类,实现IFactory接口,实例化AccessUser。

  1. class AccessFactory implements IFactory
  2. {
  3. public function CreateUser()
  4. {
  5. return new AccessUser();
  6. }
  7. }

客户端代码

  1. public function factoryMethodImp()
  2. {
  3. $user = new User();
  4. //若要改成Access数据库,只需要将本句
  5. //改成$factory = new AccessFactory();
  6. $factory = new SqlServerFactory();
  7. $iu = $factory->CreateUser();
  8. $iu->Insert($user);
  9. $iu->GetUser(1);
  10. }

此处实现了业务逻辑与数据访问的解耦。

15.4用了抽象工厂模式的数据访问程序

代码结构图 第十五章 就不能不换DB吗?——抽象工厂模式 - 图2IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合。

  1. interface IDepartment
  2. {
  3. public function Insert($department);
  4. public function GetDepartment($id);
  5. }

SqlServerDepartment类,用于访问SQL Server的Department。

  1. class SqlServerDepartment implements IDepartment
  2. {
  3. public function Insert($dept)
  4. {
  5. echo '在SQL Server中给Department表增加一条记录' . PHP_EOL;
  6. }
  7. public function GetDepartment($id)
  8. {
  9. echo '在SQL Server中根据ID得到Department表一条记录' . PHP_EOL;
  10. }
  11. }

AccessDepartment类,用于访问Access的Department。

  1. class AccessDepartment implements IDepartment
  2. {
  3. public function Insert($dept)
  4. {
  5. echo '在Access中给Department表增加一条记录' . PHP_EOL;
  6. }
  7. public function GetDepartment($id)
  8. {
  9. echo '在Access中根据ID得到Department表一条记录' . PHP_EOL;
  10. }
  11. }

IFactory接口,定义一个创建访问Department表对象的抽象的工厂接口。

  1. interface IFactory
  2. {
  3. public function CreateUser();
  4. //增加的接口方法
  5. public function CreateDepartment();
  6. }

SqlServerFactory类,实现IFactory接口,实例化SqlServerUser和SqlServerDepartment。

  1. class SqlServerFactory implements IFactory
  2. {
  3. public function CreateUser()
  4. {
  5. return new SqlServerUser();
  6. }
  7. //增加了SqlServerDepartment工厂
  8. public function CreateDepartment()
  9. {
  10. return new SqlServerDepartment();
  11. }
  12. }

AccessFactory类,实现IFactory接口,实例化AccessUser和AccessDepartment。

  1. class AccessFactory implements IFactory
  2. {
  3. public function CreateUser()
  4. {
  5. return new AccessUser();
  6. }
  7. //增加了AccessDepartment工厂
  8. public function CreateDepartment()
  9. {
  10. return new AccessDepartment();
  11. }
  12. }

客户端代码

  1. public function abstractFactoryImp()
  2. {
  3. $user = new User();
  4. $dept = new Department();
  5. //只需确定实例化哪一个数据库访问对象给factory
  6. //$factory = new SqlServerFactory();
  7. $factory = new AccessFactory();
  8. //则此时已与具体的数据库访问解除了依赖
  9. $iu = $factory->CreateUser();
  10. $iu->Insert($user);
  11. $iu->GetUser(1);
  12. //则此时已与具体的数据库访问解除了依赖
  13. $id = $factory->CreateDepartment();
  14. $id->Insert($dept);
  15. $id->GetDepartment(1);
  16. }

显示结果如下

  1. Access中给User表增加一条记录
  2. Access中根据ID得到User表一条记录
  3. Access中给Department表增加一条记录
  4. Access中根据ID得到Department表一条记录

15.5抽象工厂模式

抽象工厂模式(AbstractFactory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式(AbstractFactory)结构图 第十五章 就不能不换DB吗?——抽象工厂模式 - 图3AbstractProductA 和 AbstractProductB 是两个抽象产品,之所以为抽象,是因为它们都有可能有两种不同的实现,就刚才的例子来说就是 User 和 Department,而ProductA1、ProductA2 和 ProductB1、ProductB2就是对两个抽象产品的具体分类的实现,比如 ProductA1 可以理解为是 SqlServerUser,而 ProductB1 是 SqlServerDepartment。
IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法。而ConcreteFactory1和ConcreteFactory2就是具体工厂了。就像SqlServerFactory和AccessFactory一样。
通常是在运行时刻再创建一个ConcreteFactory类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。

15.6抽象工厂模式的优点与缺点

抽象工厂模式的优点:

  • 易于交换产品系列,由于具体工厂类,在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
  • 它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。

抽象工厂模式的缺点:
比如增加项目表Project,至少要增加三个类,IProject、SqlServerProject、AccessProject,还需要更改IFactory、SqlServerFactory和AccessFactory才可以完全实现。
编程是门艺术,这样大批量的改动,显然是非常丑陋的做法。

15.7用简单工厂来改进抽象工厂

代码结构图 第十五章 就不能不换DB吗?——抽象工厂模式 - 图4去除IFactory、SqlServerFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,代码如下

  1. class DataAccess
  2. {
  3. private static $db = 'SqlServer';
  4. // private static $db = 'Access';
  5. public static function CreateUser()
  6. {
  7. $result = null;
  8. switch (self::$db) {
  9. case 'SqlServer':
  10. $result = new SqlServerUser();
  11. break;
  12. case 'Access':
  13. $result = new AccessUser();
  14. break;
  15. }
  16. return $result;
  17. }
  18. public static function CreateDepartment()
  19. {
  20. $result = null;
  21. switch (self::$db) {
  22. case 'SqlServer':
  23. $result = new SqlServerDepartment();
  24. break;
  25. case 'Access':
  26. $result = new AccessDepartment();
  27. break;
  28. }
  29. return $result;
  30. }
  31. }

客户端代码

  1. public function ImproveBySimpleFactory()
  2. {
  3. $user = new User();
  4. $dept = new Department();
  5. $iu = DataAccess::CreateUser();
  6. $iu->Insert($user);
  7. $iu->GetUser(1);
  8. $id = DataAccess::CreateDepartment();
  9. $id->Insert($dept);
  10. $id->GetDepartment(1);
  11. }

客户端没有出现任何一个SQL Server 或 Acces 的字样,达到了解耦的目的。

15.8用反射+抽象工厂的数据访问程序

DataAccess类,用反射技术,取代IFactory、SqlServerFactory 和 AccessFactory。

  1. class DataAccess
  2. {
  3. private static $namespace = 'app\design\logic\AbstractFactory\ImproveByReflection\\';
  4. private static $db = 'SqlServer';
  5. // private static $db = 'Access';
  6. public static function CreateUser()
  7. {
  8. $className = self::$namespace . self::$db . 'User';
  9. $ref_class = new \ReflectionClass($className);
  10. return $ref_class->newInstance();
  11. }
  12. public static function CreateDepartment()
  13. {
  14. $className = self::$namespace . self::$db . 'Department';
  15. $ref_class = new \ReflectionClass($className);
  16. return $ref_class->newInstance();
  17. }
  18. }

15.9用反射+配置文件实现数据访问程序

通过增加配置项,来解决更改DataAccess的问题。
所有在用简单工厂的地方,都可以考虑用反射技术来去除 switch 或 if,解除分支判断带来的耦合。