15.1 就不能不换DB吗?
Access数据库和SQL Server数据库是完全不一样的数据库,在这两个数据库之间进行切换的时候,菜鸟只会用时间来解决这个问题。而真正精明的做法就是使用工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。使用工厂模式以后就可以达到所谓的业务逻辑与数据访问的解耦。
15.2 最基本的数据访问程序
这里追加了新增用户和得到用户两个方法。用户类:只有ID和Name两个字段,SqlserverUser类:用于操作User表,假设只有"新增用户"和"得到用户"方法。
之所以不能换数据库,原因就在于SqlserverUser su = new SqlserverUser()使得su这个对象被框死在SQL Server上了。
User.h
#pragma once #include <string> class User { public: void SetId(int id) { m_id = id; }; int GetId() { return m_id; }; void SetName(std::string name) { m_name = name; }; std::string GetName() { return m_name; }; private: int m_id; std::string m_name; };
SQLServerUser.h
#pragma once #include <iostream> #include "User.h" class SqlServerUser { public: void Insert(User* user) { std::cout << "在sql中给一条记录" << std::endl; }; User* GetUser(int id) { std::cout << "在sql中根据ID得到user表一条记录" << std::endl; return NULL; }; };
Main函数:
#include "stdafx.h" #include "SQLServerUser.h" int _tmain(int argc, _TCHAR* argv[]) { User* user = new User(); SqlServerUser* su = new SqlServerUser(); su->Insert(user); su->GetUser(1);//得到ID為一的用戶 return 0; }
15.3 用了工厂方法模式的数据访问程序
工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。
首先是User类:
#pragma once #include <string> class User { public: void SetId(int id) { m_id = id; }; int GetId() { return m_id; }; void SetName(std::string name) { m_name = name; }; std::string GetName() { return m_name; }; private: int m_id; std::string m_name; };
IFactory接口,定义一个创建访问User表对象的抽象的工厂接口。
SqlserverUser类,实现IFactory接口,实例化SqlserverUser类。
AccessFactory类,实现IFactory接口,实例化AccessUser类。
#pragma once #include "IUser.h" class IFactory { public: virtual IUser* CreateUser(void) = 0; }; class SqlServerFactory : public IFactory { public: IUser* CreateUser(void) { return new SqlServerUser(); }; }; class AccessFactory : public IFactory { public: IUser* CreateUser(void) { return new AccessUser(); }; };
IUser接口,用于客户端访问,解除与具体数据库访问的耦合。
SqlserverUser类,用于访问SQL Server的User。
AccessUser类,用于访问Access的User。
#pragma once #include <iostream> #include "User.h" class IUser { public: virtual void Insert(User* user) = 0; virtual IUser* GetUser(int id) = 0; }; class SqlServerUser : public IUser { public: void Insert(User* user) { std::cout << "在sqlServer中给一条记录" << std::endl; }; IUser* GetUser(int id) { std::cout << "在sqlServer中根据ID得到user表一条记录" << std::endl; return NULL; }; }; class AccessUser : public IUser { public: void Insert(User* user) { std::cout << "在AccessUser中给一条记录" << std::endl; }; IUser* GetUser(int id) { std::cout << "在AccessUser中根据ID得到user表一条记录" << std::endl; return NULL; }; };
Main函数
#include "stdafx.h" #include "IFactory.h" int _tmain(int argc, _TCHAR* argv[]) { User* oldUser = new User(); IFactory* fac = new SqlServerFactory(); IUser* user = fac->CreateUser(); user->Insert(oldUser); user->GetUser(1); delete oldUser; delete fac; delete user; return 0; }
15.4 用了抽象工厂模式的数据访问程序
IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合。
SqlserverDepartment类,用于访问SQL Server的Department。
AccessDepartment类,用于访问Access的Department。
#pragma once #include <iostream> #include "Department.h" class IDepartment { public: virtual void Insert(Department* dep) = 0; virtual Department* GetDepartment(int id) = 0; }; class SqlSeverDepartment : public IDepartment { public: void Insert(Department* dep) { std::cout << "在sqlServer中给Department一条记录" << std::endl; }; Department* GetDepartment(int id) { std::cout << "在sqlServer中根据ID得到Department表一条记录" << std::endl; return NULL; }; }; class AccessDepartment : public IDepartment { public: void Insert(Department* dep) { std::cout << "在Access中给Department一条记录" << std::endl; }; Department* GetDepartment(int id) { std::cout << "在Access中根据ID得到Department表一条记录" << std::endl; }; };
在这里,通过不断的需求演化,重构出了一个非常重要的设计模式。抽象工厂模式。
15.5 抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
为创建不同的产品对象,客户端应使用不同的具体工厂。
15.6 抽象工厂模式的优点和缺点。
抽象工厂模式的最大的好处便是易于交换产品系列,由于具体工厂类在一个一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
它让具体的创建实例过程与客户端分离,客户端是通过他们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
15.7 用简单工厂来改进抽象工厂
去除IFactory,SqlServerFactory和AccessFactory三个工厂类,取而代之的是DataAccess类。
#pragma once #include "IUser.h" #include "IDepartment.h" typedef enum _DataBaseType { eSqlServer, eAccess }DataBaseType; class DataAccess { public: static IUser* CreateUser(void) { IUser* result = NULL; switch (db) { case eSqlServer: result = new SqlServerUser(); break; case eAccess: result = new AccessUser(); break; default: result = new SqlServerUser(); break; } return result; }; static IDepartment* CreateDepartment(void) { IDepartment* result = NULL; switch (db) { case eSqlServer: result = new SqlSeverDepartment(); break; case eAccess: result = new AccessDepartment(); break; default: result = new SqlSeverDepartment(); break; } return result; }; private: //事先设置的数据库 static DataBaseType db; DataAccess(void){}; ~DataAccess(void){}; }; DataBaseType DataAccess::db = eAccess;
15.8 用反射+抽象工厂的数据访问程序
根据字符串db的值去某个地方找应该要实例化的类是哪一个。这样我们的switch就可以对它说再见了。这个就叫做依赖注入。
反射技术的运用:Assembly.Load("程序集名称").CreateInstance("命名空间.类名称")
Reflector.h
#pragma once #include <list> #include <memory> class Reflector { public: virtual ~Reflector() { for (std::list<Reflector*>::iterator it = Reflector::m_lstRef.begin(); it != Reflector::m_lstRef.end(); ++it) { delete (*it); } m_lstRef.clear(); }; static void FindOut(std::string className) { for (std::list<Reflector*>::iterator it = Reflector::m_lstRef.begin(); it != Reflector::m_lstRef.end(); ++it) { if (className == (*it)->getClassName()) { std::cout << (*it)->getClassName() << std::endl; (*it)->DoEvent(); } } }; virtual const char* getClassName() = 0; virtual void DoEvent(void) = 0; static std::list<Reflector*> m_lstRef; }; std::list<Reflector*> Reflector::m_lstRef; #define RUNTIME_CLASS_DECLARE(T) const char* getClassName() \ { \ return #T; \ }; #define RUNTIME_CLASS_REGEDIT(T) Reflector::m_lstRef.push_back(new T);
#include "stdafx.h" #include "DataAccess.h" void init() { RUNTIME_CLASS_REGEDIT(SqlServerUser); RUNTIME_CLASS_REGEDIT(AccessUser); }; int _tmain(int argc, _TCHAR* argv[]) { init(); Reflector::FindOut("AccessUser"); return 0; }
15.9 用反射+配置文件实现数据访问程序
所有在用简单工厂的地方,都可以考虑用反射技术来去除switch和if,来解除分支判断带来的耦合。
15.10 无痴迷,不成功
一个程序员如果从来没有熬夜写程序的经历,不能算是一个好程序员,因为他没有痴迷过,所以他不会有大成功。