Fork me on GitHub

设计模式系列一创建型之(抽象工厂模式)

1、抽象工厂简介

在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时由于需求的变化,往往存在着更多系列对象的创建工作。

  • 如何应对这种变化?
  • 如何绕过常规的对象的创建方法(new)?
  • 如何来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?

然而抽象工厂便可以很好地解决这个问题!

2、意图

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

3、适用性

  • 一个系统要独立与它的产品创建、组合和表示时
  • 一个系统要由多个产品系列中的一个来配置时
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时
  • 当你提供一个产品类库,而只想显示它们的接口而不是实现时

4、结构图

 

5、应用实例

注:此案例,借鉴网络资料;比较易于学习,所以自己练习了下,在这里也给大家分享了!

中国企业需要一项简单的财务计算:每月月底,财务人员要计算员工的工资。

员工工资 = 基本工资 + 奖金 - 个人所得税 ; 为了简化系统,这里定义员工基本工资:4000

中国企业奖金和个人所得税的计算规则是:

奖金 = 基本工资 * 10%

个人所得税 =(基本工资 + 奖金) * 40%

为此我们要构造一个系统,满足中国企业的需要:(名称Softo)


/// <summary>
    /// 公用变量基本工资
    /// </summary>
    public class Constant
    {
        public static double base_salary = 4000;
    }
    /// <summary>
    /// 计算中国个人奖金
    /// </summary>
    public class ChinaBonus
    {
        public double Calculate()
        {
            return Constant.base_salary * 0.1;
        }
    }
    /// <summary>
    /// 计算个人所得税
    /// </summary>
    public class ChinaTax
    {
        public double Calculate()
        {
            return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4;
        }
    }
View Code

客户端调用:

        static void Main(string[] args)
        {
            double bonusValue_China = new ChinaBonus().Calculate();
            double taxValue_China = new ChinaTax().Calculate();
            double salary_China = 4000 + bonusValue_China - taxValue_China;
            Console.WriteLine("China Salary is:" + salary_China);
        }   
View Code

运行结果:


以上满足了中国企业需求,但为了拓展国际市场,需求发生变更;我们要把该系统移植给美国公司使用,员工工资计算规则不变;但是奖金和个人所得税不同于中国,其规则如下:

奖金 = 基本工资 * 15%

个人所得税 = 基本工资 * 5% + 奖金 * 25%

根据现有系统,我们只需要做如下更改:

    /// <summary>
    /// 公用变量基本工资
    /// </summary>
    public class Constant
    {
        public static double base_salary = 4000;
    }
    /// <summary>
    /// 计算美国个人奖金
    /// </summary>
    public class AmericanBonus
    {
        public double Calculate()
        {
            return Constant.base_salary * 0.1;
        }
    }
    /// <summary>
    /// 计算美国个人所得税
    /// </summary>
    public class AmericanTax
    {
        public double Calculate()
        {
            return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4;
        }
    }
View Code

客户端调用:

       static void Main(string[] args)
        {         

            double bonusValue_American = new AmericanBonus().Calculate();
            double taxValue_American = new AmericanTax().Calculate();
            double salary_American = 4000 + bonusValue_American - taxValue_American;
            Console.WriteLine("American Salary is:" + salary_American);
            Console.ReadLine();

        }
View Code

运行结果:

 


为了以后业务拓展,我们打算把Softo整合为通用系统: 

比较以上两个系统,业务规则类发生了变化,客户端调用发生了变化,如果要做通用的就必须保留所有的业务规则模型,在中国与美国之间切换时,只需要修改客户端调用即可。

但是,一个维护性良好的系统应该遵循“开闭原则”。即:封闭对原来代码的修改,开放对原来代码的扩展 (如类的继承,接口的实现)。我们发现不论是中国企业还是美国企业,他们的业务运规则都采用同样的计算接口,于是修改如下:

   class Program
    {
        static void Main(string[] args)
        {
            IBonus bonus = new ChinaBonus();
            double bonusValue_China = bonus.Calculate();
            ITax tax = new ChinaTax();
            double taxValue_China = tax.Calculate();
            double salary_China = 4000 + bonusValue_China - taxValue_China;
            Console.WriteLine("China Salary is:" + salary_China);
            Console.ReadLine();
        }
    }
    /// <summary>
    /// 公用变量基本工资
    /// </summary>
    public class Constant
    {
        public static double base_salary = 4000;
    }

    public interface IBonus
    {
        double Calculate();
    }
    public interface ITax
    {
        double Calculate();
    }

    /// <summary>
    /// 计算中国个人奖金
    /// </summary>
    public class ChinaBonus : IBonus
    {
        public double Calculate()
        {
            return Constant.base_salary * 0.1;
        }
    }
    /// <summary>
    /// 计算个人所得税
    /// </summary>
    public class ChinaTax : ITax
    {
        public double Calculate()
        {
            return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4;
        }
    }
    /// <summary>
    /// 计算美国个人奖金
    /// </summary>
    public class AmericanBonus : IBonus
    {
        public double Calculate()
        {
            return Constant.base_salary * 0.1;
        }
    }
    /// <summary>
    /// 计算美国个人所得税
    /// </summary>
    public class AmericanTax : ITax
    {
        public double Calculate()
        {
            return (Constant.base_salary + Constant.base_salary * 0.1) * 0.4;
        }
    }
View Code

运行结果:


然而,上面增加的接口几乎没有解决任何问题,因为当系统的客户在美国和中国企业间切换时,客户端仍然需要修改;

为此增加一个工具类Factory:

    public class Factory
    {
        public IBonus CreateBonus()
        {
            return new ChinaBonus();
        }
        public ITax CreateTax()
        {
            return new ChinaTax();
        }
    }
View Code

客户端调用:

     static void Main(string[] args)
        {
            IBonus bonus = new Factory().CreateBonus();
            double bonusValue = bonus.Calculate();
            ITax tax = new Factory().CreateTax();
            double taxValue = tax.Calculate();
            double salary = 4000 + bonusValue - taxValue;
            Console.WriteLine(" Salary is:" + salary);
            Console.ReadLine();
        }
View Code

运行结果:

此时,如果我们把该系统移植到美国,只需修改Factory工具类,把ChinaBonus替换为AmericanBonus;ChinaTax替换为AmericanTax即可;其实这也有一个副作用,新建了一个Factory类,并且把修改点转移到该类中,并没有解决根本问题;而且这个工具类可能是专属于美国企业或者中国企业的,名称叫:AmericanFactory,ChineseFactory更合适。那我们该解决这个问题?

此时才引入重点,添加抽象工厂方法:

 

增加一个静态方法,该方法根据一个配置文件动态地判断应该实例化哪个工厂类;

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="factoryName" value="ChinaFactory"></add>
    </appSettings>
</configuration>

 

    /// <summary>
    /// AbstractFactory
    /// </summary>
    public abstract class AbstractFactory
    {
        public static AbstractFactory GetInstance()
        {
            string factoryName = ConfigurationManager.AppSettings["factoryName"];
            AbstractFactory instance;
            switch (factoryName)
            {
                case "ChinaFactory":
                    instance = new ChinaFactory(); break;
                case "AmericanFactory":
                    instance = new AmericanFactory(); break;
                default:
                    instance = null; break;
            }
            return instance;
        }
        public abstract IBonus CreateBonus();
        public abstract ITax CreateTax();
    }
    public class ChinaFactory : AbstractFactory
    {
        public override IBonus CreateBonus()
        {
            return new ChinaBonus();
        }
        public override ITax CreateTax()
        {
            return new ChinaTax();
        }
    }
    public class AmericanFactory : AbstractFactory
    {
        public override IBonus CreateBonus()
        {
            return new AmericanBonus();
        }
        public override ITax CreateTax()
        {
            return new AmericanTax();
        }
    }
View Code

客户端调用:

       static void Main(string[] args)
        {
            AbstractFactory instanceFac = AbstractFactory.GetInstance();

            double bonusValue = instanceFac.CreateBonus().Calculate();

            double taxValue = instanceFac.CreateTax().Calculate();

            double salary = 4000 + bonusValue - taxValue;
            Console.WriteLine(" Salary is:" + salary);
            Console.ReadLine();
        }
View Code

运行结果:

此时当系统在美国企业和中国企业之间切换时,我们只需要修改配置改为AmericanFactory即可。

相信你看到这里也会赶脚到抽象工厂的强大吧!其实以上解决方案并不是最完美的,不知是否满过你的锐眼?

抽象工厂类中采用了分支判断,一旦业务更加广泛,不只是美国,有拓展至德国,此时我们不但要增加新的业务规则类:德国Tax、德国Bonus分别实现ITax和IBonus接口,新增德国Factory继承自AbstractFactory,而且还要添加AbstractFactory中的case分支,这依然不能满足OCP!至于该如何解决该问题,且看下节分析!

 


 

6、总结

  • 最后使用抽象工厂模式后,我们会发现客户端完全依赖于抽象类,它不必去理解中国和美国企业具体的业务规则如何实现;面对的只是业务规则接口IBonus和ITax;从而把业务规则与客户端调用完全分离,从而降低耦合度;
  • 完完全全地理解抽象工厂模式的意义非常重大,可以说对它的理解是你对OOP理解上升到一个新的里程碑的重要标志。学会了用抽象工厂模式编写框架类,你将理解OOP的精华:面向接口编程。

 注:虽然案例是搜集的资料,但通过自己的整理与测试!希望博友能够尊重我的劳动成果,大家互相学习、共同进步!

 

posted @ 2014-09-18 16:35  迁梦余光  阅读(322)  评论(0编辑  收藏  举报