首页 > 代码库 > 设计模式之-抽象工厂模式

设计模式之-抽象工厂模式

前言

我们先来看一段基本的数据访问代码,以‘新增用户’和得到用户为例,假设只有ID和Name两个字段,其余省略。

 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表

 public class SqlserverUser    {        public void Insert(User user)        {            Console.WriteLine("在Sql Server中给User表增加一条记录");        }        public User GetUser(int user)        {            Console.WriteLine("在Sql Server中根据ID得到User表一条记录");            return null;        }    }

客户端代码

 static void Main(string[] args)        {            User user = new User();            SqlserverUser su = new SqlserverUser();//此处与SQL Server耦合            su.Insert(user);//插入用户            su.GetUser(1);//得到ID为1的用户            Console.Read();        }

这里和Sql Server数据库耦合,不能做到灵活的更换数据库,如果下次要换成Mysql或者其他数据库,就非常麻烦了。这里我们可以改进下程序,使用工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。

技术分享

 

IUser接口,用于客户端访问,解除于具体数据库访问的耦合

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

SqlserverUser类,用于访问SQL Server的User

 public class SqlserverUser:IUser    {        public void Insert(User user)        {            Console.WriteLine("在Sql Server中给User表增加一条记录");        }        public User GetUser(int user)        {            Console.WriteLine("在Sql Server中根据ID得到User表一条记录");            return null;        }    }

AccessUser类,用于访问Access的User

 class AccessUser : IUser    {        public void Insert(User user)        {            Console.WriteLine("在Sql Server中给User表增加一条记录");        }        public User GetUser(int user)        {            Console.WriteLine("在Sql Server中根据ID得到User表一条记录");            return null;        }    }

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

 /// <summary>    /// 创建访问User表对象的抽象工厂接口    /// </summary>    interface IFactory    {        IUser CreateUser();    }

SqlServerFactory类,实现IFactory接口,实例化SqlserverUser

 /// <summary>    /// 实现IFactory接口,实例化SqlserverUser    /// </summary>    class SqlServerFactory : IFactory    {        public IUser CreateUser()        {            return new SqlserverUser();        }    }

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

    /// <summary>    /// 实现IFactory接口,实例化AccessUser    /// </summary>    /// <returns></returns>    class AccessFactory : IFactory    {        public IUser CreateUser()        {            return new AccessUser();        }    }

 客户端代码

       static void Main(string[] args)        {            User user = new User();            //若要改成Access数据库,只需要将本剧改成  IFactory factory = new AccessFactory();            IFactory factory = new SqlServerFactory();            IUser iu = factory.CreateUser();            iu.Insert(user);            iu.GetUser(1);            Console.Read();        }

程序到这里依然还存在问题,虽然我们把业务逻辑和数据访问解耦了,但是如果我们数据此时新增其他的表,比如部门表(Department),此时程序应该怎样才会更灵活呢?思考五秒。。。。。

代码结构图如下

技术分享

IDepartment接口,用于客户端访问,解除于具体数据库访问的耦合

interface IDepartment    {        void Insert(IDepartment department);        Department GetDepartment(int id);    }

SqlserverDepartment类,用于访问SQL server 的Department.

 class SqlserverDepartment:IDepartment    {        public void Insert(Department user)        {            Console.WriteLine("在Sql Server中给Department表增加一条记录");        }        public Department GetUser(int user)        {            Console.WriteLine("在Sql Server中根据ID得到Department表一条记录");            return null;        }    }

AccessDepartment类,用于访问Access的Department

class AccessDepartment : IDepartment    {        public void Insert(Department user)        {            Console.WriteLine("在Sql Server中给Department表增加一条记录");        }        public Department GetUser(int user)        {            Console.WriteLine("在Sql Server中根据ID得到Department表一条记录");            return null;        }    }

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

 /// <summary>    /// 创建访问表对象的抽象工厂接口    /// </summary>    interface IFactory    {        IUser CreateUser();        IDepartment CreateDepartment();//增加接口方法    }

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

/// <summary>    /// 实现IFactory接口,实例化SqlserverUser    /// </summary>    class SqlServerFactory : IFactory    {        public IUser CreateUser()        {            return new SqlserverUser();        }        /// <summary>        /// 新增SqlserverDepartment工厂        /// </summary>        /// <returns></returns>        public IDepartment CreateDepartment()        {            return new SqlserverDepartment();        }    }

AccessFactory类,实现了IFactory接口,实例化User和Department

 /// <summary>    /// 实现IFactory接口,实例化AccessUser    /// </summary>    /// <returns></returns>    class AccessFactory : IFactory    {        public IUser CreateUser()        {            return new AccessUser();        }        /// <summary>        /// 新增OleDBDepartment工厂        /// </summary>        /// <returns></returns>        public IDepartment CreateDepartment()        {            return new AccessDepartment();        }    }

客户端代码

 static void Main(string[] args)        {            User user = new User();            Department dept = new Department();            //只需确定实例化哪一个数据库访问对象给factory            //IFactory factory = new SqlServerFactory();            IFactory factory = new AccessFactory();            //则此时已于具体的数据库访问接触了依赖            IUser iu = factory.CreateUser();            iu.Insert(user);            iu.GetUser(1);            //则此时已于具体的数据库访问接触了依赖            IDepartment id = factory.CreateDepartment();            id.Insert(dept);            id.GetDepartment(1);            Console.Read();        }

技术分享

此时,我们会发现数据库中有很多个表,和SQL Server和Access又是两大不同的分类,所以解决这种涉及到多产品系列的问题,我们可以使用抽象工厂模式。

抽象工厂模式

 技术分享

技术分享

 

AbstractProductA和AbstractProductB是两个抽象产品,之所以为抽象,是因为他们都有可能有两种不同的实现。就上面的例子来说就是User和Department,而ProductA1,ProductA2和ProductB1,ProductB2就是对两个抽象产品的具体分类实现 ,比如ProductA1可以理解是SqlserverUser,而ProductB1是AccessUser。

IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法,而ConcreateFactory1和ConcreateFactory2就是具体的工厂了,就像 SqlserverFactory和AccessFactory一样。

这样做优点和缺点?

好处:易于交换产品系列,由于是具体工厂类,例如IFactory factory=new AccessFactory,再一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂边的非常容易,它只需要改变具体工厂即可使用不同的产品配置。

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

缺点:很明显,我们新增一个表改动的地方很多,接口、工厂类,具体实现,这太糟糕了

 

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

技术分享

去除IFactory/SqlserverFactory和AccessFactory三个类,取而代之的是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 CreateUser()        {            IDepartment result = null;            switch (db)            {                case "Sqlserver":                    result = new SqlserverDepartment();                    break;                case "Access":                    result = new AccessDepartment();                    break;            }            return result;        }    }

由于db的实现设置,所以swtich中可以根据选择实例化出相应的对象

 static void Main(string[] args)        {            User user = new User();            Department dept = new Department();            //直接得到实际的数据库访问实例,而不存在任何依赖            IUser iu = DataAccess.CreateUser();            iu.Insert(user);            iu.GetUser(1);            IDepartment id = DataAccess.CreateDepartment();            id.Insert(dept);            id.GetDepartment(1);            Console.Read();        }

这里用简单工厂来实现了,我们抛弃了IFactory、SqlserverFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,客户端没有出现任何一个Sqlserver 和Access的字样,达到了解耦的目的。

不过此时还不是最完美的,因为我们需要增加Oracle的话,现在需要在DataAccess类中每个方法的swicth中加case了。

反射+抽象工厂

上述问题的关键在于我们如何去解决switch的问题,可以使用依赖注入(Dependency Injection),本来依赖注入需要专门的IoC容器提供,比如:Spring.NET,显然这个程序不需要这么麻烦。

技术分享

程序引用:using System.Reflection 就可以使用反射帮我们克服抽象工厂模式的先天不足了。

技术分享

技术分享

DataAccess类,用反射技术,取代IFactory、SqlserverFactory和AccessFactory.

class DataAccess    {        private static readonly string AssemblyName = "程序集名称";        private static readonly string db = "Sqlserver";//数据库名称,可以替换成Access        public static IUser CreateUser()        {            string className = AssemblyName + "." + db + "User";            return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);        }        public static IDepartment CreateDepartment()        {            string className = AssemblyName + "." + db + "User";            return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);        }           }

技术分享

用反射+配置文件实现数据库访问

 技术分享

最后得到执行结果:

技术分享

 

总结:所有简单工厂的地方,都可以考虑用反射技术取出swtich或if,接触分支判断带来的耦合。

 

设计模式之-抽象工厂模式