大话设计模式之抽象工厂模式

本文探讨了如何在抽象工厂模式中运用反射技术,以简化产品创建过程,提高代码复用性和灵活性。通过实例展示了如何使用反射来动态创建不同数据库相关的产品对象,以及如何结合配置文件进一步优化代码结构,减少对硬编码字符串的依赖。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类

 

在文章一开始,作者提出针对相同的程序采用不同的数据库,从而采用工厂模式。

以下结构图为对User表和Department表采用的sqlserver和access的不同的具体实现,然后提取到各自的Factory类中去。

              

 

using System;
usingSystem.Collections.Generic;
using System.Text;
 
namespace 抽象工厂模式
{
    class Program
    {
        static void Main(string[] args)
        {
            User user = new User();
            Department dept = new Department();
 
            //AbstractFactoryfactory = 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();
        }
    }
 
    //某个产品
    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; }
        }
    }
    //另一个产品
    class Department
    {
        private int _id;
        public int ID
        {
            get { return _id; }
            set { _id = value; }
        }
 
        private string _deptName;
        public string DeptName
        {
            get { return _deptName; }
            set { _deptName = value; }
        }
    }
 
    interface IUser
    {
        void Insert(User user);
 
        User GetUser(int id);
    }
 
    //以下为该产品的不同两种实现
    class SqlserverUser : IUser
    {
        public void Insert(User user)
        {
            Console.WriteLine("在Sqlserver中给User表增加一条记录");
        }
 
        public User GetUser(int id)
        {
            Console.WriteLine("在Sqlserver中根据ID得到User表一条记录");
            return null;
        }
    }
 
    class AccessUser : IUser
    {
        public void Insert(User user)
        {
            Console.WriteLine("在Access中给User表增加一条记录");
        }
 
        public User GetUser(int id)
        {
            Console.WriteLine("在Access中根据ID得到User表一条记录");
            return null;
        }
    }
 
    interface IDepartment
    {
        void Insert(Department department);
 
        Department GetDepartment(int id);
    }
    //以下为该产品的不同两种实现
    class SqlserverDepartment : IDepartment
    {
        public void Insert(Department department)
        {
            Console.WriteLine("在Sqlserver中给Department表增加一条记录");
        }
 
        public Department GetDepartment(int id)
        {
            Console.WriteLine("在Sqlserver中根据ID得到Department表一条记录");
            return null;
        }
    }
 
    class AccessDepartment : IDepartment
    {
        public void Insert(Department department)
        {
            Console.WriteLine("在Access中给Department表增加一条记录");
        }
 
        public Department GetDepartment(int id)
        {
            Console.WriteLine("在Access中根据ID得到Department表一条记录");
            return null;
        }
    }
 
    //工厂方法
    interface IFactory
    {
        IUser CreateUser();
 
        IDepartment CreateDepartment();
    }
 
    //为每种实现提供一个具体工厂
    class SqlServerFactory : IFactory
    {
        public IUser CreateUser()
        {
            return new SqlserverUser();
        }
 
        public IDepartment CreateDepartment()
        {
            return new SqlserverDepartment();
        }
    }
 
    class AccessFactory : IFactory
    {
        public IUser CreateUser()
        {
            return new AccessUser();
        }
 
        public IDepartment CreateDepartment()
        {
            return new AccessDepartment();
        }
    }
}


 

 

以下为抽象工厂模式的具体结构图:

 

AbstractA和AbstractB是两个抽象的产品,之所以为抽象,因为他们都可能有两种不同的实现

ProductA1、ProductA2、ProductB1、ProductB2就是对两个抽象产品的具体分类的实现.

通常我们是在运行时创建一个ConcretFactory类的实例,这个具体的工厂再继续创建具有特定实现的产品对象,也就是说,为了创建不同的产品对象,客户端应该使用不同的具体工厂

 

 

抽象工厂模式的优点与缺点

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

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

    但是,缺点也很明显,如果我们需要增加一个产品,那么要增加的就多的多了,比如AbstractProductC,ProductC1, ProductC2, 还要在Factory类中增加生产产品的函数,这样一来,明显十分麻烦。

 

    既然如此麻烦,我们不如将工厂类去掉,转而使用简单工厂模式:

 

 

    

class DataAccess
    {
        private static readonly string db = "Sqlserver";
        //private staticreadonly 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;
        }
    }

 

 

    虽然不用再增加product的时候添加无数类了,但正如我们之前所学过的,我们却需要修改程序代码,违背了开闭原则。

    此时我们需要一个技术,如果是sqlserver就是去实例化Sql server数据库相关类,如果是Access就去实例化Access相关类,而且在增加product(比如增加Oracle数据库)时不需要修改原本代码内容本身那该多好!

 

这就是用到反射,格式:

Assembly.Load(“程序集名称”).CreateInstance(“命名空间.类名称”)

只需要在顶端写上 usingSystem.Reflection;就可以

 

举个例子:

常规情况下,我们写成这样:

IUser result = new SqlserverUser();

 

可我们用了反射,就是这样:

Using System.Reflection;

IUser result = (IUser)Assembly.Load(“抽象工厂模式”).CreateInstance(“抽象工厂模式.SqlserverUser”);

 

我们可以发现,上下两种方法看上去一样,但是反射方法将类名变成了字符串,可以根据需要来替换字符串变量,而不再使用switch或if,那样的话在增加product的时候虽然必要的功能类肯定是要增加的,但代码就不需要修改了

 

代码如下:

class DataAccess
    {
        private static readonly string AssemblyName = "抽象工厂模式";
        private static readonly string db = "Sqlserver";
        //private staticreadonly string db = "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 + "Department";
            return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
        }
 }


 

虽然简化了许多,每次只需要修改db字符串,但能不能不修改代码里的字符串呢?

下面我们就用到配置文件与反射相结合

添加如下的配置文件:

 

<?xml version=”1.0” encoding=”utf-8” ?>
<configuration>
    <appSettings>
        <add key=”DB” value=”Sqlserver”>
    </appSettings>
</configuration>


并添加引用 System.configuratio15.,在程序头添加

    Using System.Configuration;

然后更改DataAccess类的字段DB的赋值码

    string db = ConfigurationManager.AppSettings[“DB”];

这下就解决了修改程序中db字符串的问题了

 

class DataAccess
    {
        private static readonly string AssemblyName = "抽象工厂模式";
        private static readonly string db = ConfigurationManager.AppSettings["DB"];
       
        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);
        }
}


 

总的来说,所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值