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


    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的精华:面向接口编程。

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

  • 相关阅读:
    django的用户认证模块(auth)
    算法
    图书管理系统
    mac系统中pycharm激活
    mac常见问题
    mysql安装
    restful规范及DRF基础
    MySQL存储引擎
    [python] with statement
    MySQL索引及执行计划
  • 原文地址:https://www.cnblogs.com/tianboblog/p/3979519.html
Copyright © 2020-2023  润新知