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

Posted 潇十一郎

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之-抽象工厂模式相关的知识,希望对你有一定的参考价值。

前言

我们先来看一段基本的数据访问代码,以‘新增用户’和得到用户为例,假设只有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,接触分支判断带来的耦合。

 

以上是关于设计模式之-抽象工厂模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之抽象工厂模式

创建型模式之抽象工厂模式实例及代码操作

设计模式探秘之抽象工厂模式

设计模式之简单工厂工厂方法抽象工厂

设计模式之抽象工厂模式

设计模式之抽象工厂模式