zoukankan      html  css  js  c++  java
  • 设计模式之-抽象工厂模式

    前言

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

  • 相关阅读:
    学WPF (1 of n)干啥都有第一次
    程序启动时显示Flash窗体(C#)
    对象序列化后直接获取byte[]的方法
    工程管理(1 of n): 建立用于管理代码开发的注释标记
    发现Visual Studio隐含的大礼包漂亮的Visual Studio图像库
    C# Hello World
    更人性化地控制用户输入(1 of n)
    快手导航 计算机软件网址导航 时空地图TimeGIS
    中国图书馆图书分类法(Chinese Library Classification CLC)的XML文档生成 时空地图TimeGIS
    快手软件 v2.5 发布 时空地图TimeGIS
  • 原文地址:https://www.cnblogs.com/zhangxiaoyong/p/7211568.html
Copyright © 2011-2022 走看看