抽象工厂模式-学习笔记

学习抽象工厂模式ing...

希望大家给出意见,有不对的地方大家多多指教!小女子在此谢过!

---------------------------------------------------------------------------

最基本的数据访问程序

User类:表示数据库中字段,假设只有ID和Name两个字段

User
      class User
      {
            private int _id;

            public int Id
            {
                  get { return _id; }
                  set { _id = value; }
            }

            private string _name;

            public string Name
            {
                  get { return _name; }
                  set { _name = value; }
            }
      }

SqlserverUser类:用于操作User表,假设只有“添加用户”和“得到用户”两个方法(这里为了简单,使用两句话代替O(∩_∩)O~)

SqlserverUser
      class SqlserverUser
      {
            public void Insert(User user)
            {
                  Console.WriteLine("通过SQL Server给User表添加一条记录");
            }
            public User GetUser(int id)
            {
                  Console.WriteLine("通过SQL Server根据ID得到User表中的一条记录");
                  return null;
            }

客户端代码

客户端代码
            static void Main(string[] args)
            {
                  User user = new User();
                  SqlserverUser su = new SqlserverUser();
                  su.Insert(user);
                  su.GetUser(1);
                  Console.ReadKey();
            }

  在这个最基本数据访问程序中,如果要更换数据库,那么如果代码量非常大的情况下,修改数据库代码是非常痛苦的,甚至与数据库无关的代码页需要改。这里之所以不能更换数据库,原因在于

SqlserverUser su = new SqlserverUser();

使得su这个对象被框死在SQL Server上了,如果这里是多态的,那么在执行

                  su.Insert(user);
                  su.GetUser(1);

时,就不用考虑是在用SQL Server还是在用Access或者Oracle了。


使用工厂方法模式的数据访问程序,解除客户端与具体数据库访问的耦合

这里我们添加一个IUser接口,用来解除客户端与具体数据库访问的耦合:

 

      interface IUser
      {
            void Insert(User user);
            User GetUser(int id);
      }

 

这个时候我们就需要修改原先简单的数据访问代码了,因为我们的目的是解除与具体数据访问的耦合,所以需要让数据访问类来继承这个接口(不管是SQL Server还是Access),然后通过工厂模式来创建访问数据库表User的对象。

首先修改SqlserverUser类,让之继承IUser接口;

 

class SqlserverUser:IUser

 

然后创建AccessUser类,用来访问Access数据库中的User类

 

AccessUser
      class AccessUser:IUser
      {
            public void Insert(User user)
            {
                  Console.WriteLine("通过Access给User表添加一条记录");
            }

            public User GetUser(int id)
            {
                  Console.WriteLine("通过Access根据ID得到User表中的一条记录");
                  return null;
            }
      }

 

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

 

      interface IFactory
      {
            IUser CreateUser();
      }

 

SqlserverFactory类,实现IFactory接口,用来实例化SqlserverUser

      class SqlServerFactory:IFactory
      {
            public IUser CreateUser()
            {
                  return new SqlserverUser();
            }
      }

AccessFactory类,同样实现IFactory接口,用来实例化AccessUser

 

      class AccessFactory:IFactory
      {
            public IUser CreateUser()
            {
                  return new AccessUser();
            }
      }

 

修改后的客户端代码:

            static void Main(string[] args)
            {
                  User user = new User();
                  //IFactory factory = new SqlServerFactory();
                  IFactory factory = new AccessFactory();        //如果需要修改数据库,只需要变更这句代码即可
                  IUser su = factory.CreateUser();
                  su.Insert(user);
                  su.GetUser(1);
                  Console.ReadKey();
            }

此时由于多态的关系,使得声明IUser接口的对象su事先不知道是在访问哪个数据库,却可以再运行时很好地完成工作,这就解决了所谓的业务逻辑与数据访问的解耦

 

这个时候问题还没有完全解决,数据库中不可能只有一张User表,如果增加一张Department表,那么依照上面的方法,需要增加一个IDepartment接口;

然后由AccessDepartment和SqlServerDepartment两个类来实现这个接口,完成对数据库的操作。

完成对数据库的操作之后,就需要在对应的数据库工厂中创建相应的对象,因为所有的数据库工厂都实现了IFactory接口,所以我们只需要在对应的数据库工厂中添加相应的方法。

 

SqlServerFactory
      class SqlServerFactory:IFactory
      {
            public IUser CreateUser()
            {
                  return new SqlserverUser();
            }
            public IDepartment CreateDepartment()
            {
                  return new SqlServerDepartment();
            }
      }

 

AccessFactory
      class AccessFactory:IFactory
      {
            public IUser CreateUser()
            {
                  return new AccessUser();
            }
            public IDepartment CreateDepartment()
            {
                  return new AccessDepartment();
            }
      }

这样一来虽然解决了业务逻辑和数据访问的紧耦合问题,但是如果数据库中的数据量非常庞大的时候,我们也要一张表一个接口的这样做吗?当然不,当只有一个User表的时候,只需要一个User类和User操作类,这时使用工厂方法模式来解决问题;但是现在一般情况下显然数据库中有很多的表,而且SQL Server和Access又是两大不同的分类,所以解决涉及到多个产品系列的问题,就有一种专门的工厂模式:抽象工厂模式。

 

抽象工厂模式

Abstract Factory,提供一个创建一系列相关或相互依赖的接口,而无需指定它们具体的类。

优点:

  1、便于交换产品系列,由于具体工厂类,比如IFactory factory = new SqlServerFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变的非常容易,它只需要改变具体工厂即可使用不同的产品配置;

  2、它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。

缺点:

  1、如果有增加功能的需求,比如增加表Project,那么至少需要增加三个类:IProject、SqlServerProject、AccessProject,同时还需要更改IFactory、SqlServerFactory、AccessFactory才能完全实现。所以增加功能时是非常麻烦的。

  2、客户端程序多的情况下(在这里是Main里面的代码),在每一个类的开始都要声明IFactory factory = new SqlServerFactory(),如果有100个调用数据库访问的类,改为iAccess时就需要修改至少100处代码。

 

为了解决上面缺点2的问题,我们试着用简单工厂来代替抽象工厂;

不再使用IFactory、SqlServerFactory、AccessFactory,替换为DataAccess,用一个简单工厂来实现:

DataAccess
      class DataAccess
      {
            //private static readonly string db = "SqlServer";
            private static readonly string db = "Access";

            public static IUser CreateUser()
            {
                  IUser result = null;
                  switch (db)
                  { 
                        case "SqlServer":
                              result = new SqlserverUser();
                              break;
                        case "Access":
                              result = new AccessUser();
                              break;
                  }
                  return result;
            }

            public static IDepartment CreateDepartment()
            {
                  IDepartment result = null;
                  switch (db)
                  {
                        case "SqlServer":
                              result = new SqlServerDepartment();
                              break;
                        case "Access":
                              result = new AccessDepartment();
                              break;
                  }
                  return result;
            }
      }

客户端代码:

客户端代码
            static void Main(string[] args)
            {
                  User user = new User();
                  Department dept = new Department();

                  //IFactory factory = new SqlServerFactory();
                  //IFactory factory = new AccessFactory();

                  //IUser su = factory.CreateUser();
                  IUser su = DataAccess.CreateUser();
                  su.Insert(user);
                  su.GetUser(1);

                  //IDepartment id = factory.CreateDepartment();
                  IDepartment id = DataAccess.CreateDepartment();
                  id.Insert(dept);
                  id.GetDepartment(1);

                  Console.ReadKey();
            }

通过上面的方法在客户端没有出现一个SQL Server或者Access的字样,达到了解耦的目的。

但是如果需要使用Oracle数据库访问,就需要在每个switch中增加case了。

 

用反射+配置文件+抽象工厂实现数据访问程序

在增加数据库时,相关类的增加是不可避免的,这点目前为止还不能解决,不过开放-封闭原则告诉我们:对于扩展,我们开放;对于修改,我们应该尽量关闭。

因此我们可以通过反射,来获得当前运行的程序集名称,再通过拼接字符串得到所需要创建的实例的类名,代码如下:

            private static readonly string db = ConfigurationManager.AppSettings["DB"];
            private static readonly string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

            public static IUser CreateUser()
            {
                  string className = assemblyName + "." + db + "User";
                  return (IUser)Assembly.Load(assemblyName).CreateInstance(className);
            }

            public static IDepartment CreateDepartment()
            {
                  string className = assemblyName + "." + db + "Department";
                  return (IDepartment)Assembly.Load(assemblyName).CreateInstance(className);
            }

其中我把相关的数据库字符串放在了配置文件中:

  <appSettings>
    <add key="DB" value="Access"/>
  </appSettings>

这样就解决了关于抽象工厂数据访问的可维护可扩展的问题。

 

--------------------------------------------------------------------

各位多多指导...Thank you!

posted @ 2012-05-04 17:04  王小萌  阅读(1709)  评论(9编辑  收藏  举报