• 设计模式-C#实现简单工厂模式


    前言

    上一篇文章写了如何使用RabbitMQ做个简单的发送邮件项目,然后评论也是比较多,也是准备去学习一下如何确保RabbitMQ的消息可靠性,但是由于时间原因,先来说说设计模式中的简单工厂模式吧!
    在了解简单工厂模式之前,我们要知道C#是一款面向对象的高级程序语言。它有3大特性,封装、继承、多态。

    简述

    工厂模式(Factory Pattern)是一种常用的设计模式,属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
    工厂模式的核心是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。这样客户端可以无需指定具体产品的类,只需通过工厂类即可得到所需的产品对象。

    工厂模式主要分为三种类型:简单工厂模式(Simple Factory Pattern)、工厂方法模式(Factory Method Pattern)和抽象工厂模式(Abstract Factory Pattern)。
    本文主要讲解简单工厂模式(Simple Factory Pattern)。

    案例带入

    下面使用C#控制台程序去写一个简易的计算器,实现加减乘除。如果我没学过设计模式,我会这么写:

    static void Main(string[] args)  
    {  
        Console.WriteLine("请输入数字A:");  
        string A = Console.ReadLine();  
        Console.WriteLine("请选择运算符号:(+、-、*、/):");  
        string op = Console.ReadLine();  
        Console.WriteLine("请输入数字B:");  
        string B = Console.ReadLine();  
        string result = "";  
        switch (op)  
        {        
    	    case "+":  
                result = Convert.ToString(Convert.ToDouble(A) + Convert.ToDouble(B));  
                break;  
            case "-":  
                result = Convert.ToString(Convert.ToDouble(A) - Convert.ToDouble(B));  
                break;  
            case "*":  
                result = Convert.ToString(Convert.ToDouble(A) * Convert.ToDouble(B));  
                break;  
            case "/":  
                result = Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(B));  
                break;  
            default:  
                Console.WriteLine("输入的运算符号有误!");  
                break;  
        }    
        Console.WriteLine("结果:" + result);  
    }
    

    上述代码乍一看没问题,实则隐藏了很多陷阱,比如:

    1. 变量命名不规范
    2. 除数为0怎么办
    3. 输入的不是数字怎么办
    4. ......

    优化

    我们用面向对象的思想进行优化,主要体现在:可维护、可复用、可扩展、灵活性几个方面。通过封装、继承、多态来降低程序的耦合度。

    封装

    我们可以将运算逻辑封装成一个方法去实现,让主方法减轻负担。封装后:
    Operation

    public class Operation
    {
        public static double GetResult(double num1, double num2, string op)
        {
            double result = 0d;
            switch (op)
            {
                case "+":
                    result = num1 + num2;
                    break;
                case "-":
                    result = num1 - num2;
                    break;
                case "*":
                    result = num1 * num2;
                    break;
                case "/":
                    result = num1 / num2;
                    break;
            }
            return result;
        }
    }
    

    Main方法

    static void Main(string[] args)
    {
    	try
    	{
    		Console.WriteLine("请输入数字A:");
    		string strNumA = Console.ReadLine();
    		Console.WriteLine("请选择运算符号:(+、-、*、/):");
    		string op = Console.ReadLine();
    		Console.WriteLine("请输入数字B:");
    		string strNumB = Console.ReadLine();
    		string result = "";
    		result = Convert.ToString(Operation.GetResult(Convert.ToDouble(strNumA), Convert.ToDouble(strNumB), op));
    		Console.WriteLine("结果:" + result);
    	}
    	catch (Exception e)
    	{
    		Console.WriteLine("发生异常:" + e.Message);
    	}
    }
    

    松耦合

    当我们完成封装后开始思考一个问题,如果后面有新的需求,需要增加一个开根运行,应该如何去修改?如果是我,我会在switch里面加一个分支,但是这样耦合度太高。我明明只需要去开根,但是却要让加减乘除参与进来,所以我们应该将加减乘除运算分离出来。
    优化耦合度:

    public class Operation
    {
        private double _num1;
        private double _num2;
        public double Num1 { get => _num1; set => _num1 = value; }
        public double Num2 { get => _num2; set => _num2 = value; }
        public virtual double GetResult()
        {
            return 0;
        }
    }
    
    //加法类
    public class AddOperation : Operation
    {
        public override double GetResult()
        {
            return Num1 + Num2;
        }
    }
    //减法类
    public class SubtractOperation : Operation
    {
        public override double GetResult()
        {
            return Num1 - Num2;
        }
    }
    //乘法类
    public class MultiplyOperation : Operation
    {
        public override double GetResult()
        {
            return Num1 * Num2;
        }
    }
    //除法类
    public class DivideOperation : Operation
    {
        public override double GetResult()
        {
            if (Num2 == 0)
                throw new DivideByZeroException("除数不能为0");
            return Num1 / Num2;
        }
    }  
    

    我创建了Operation基类,并定义了2个成员变量_num1_num2,同时定义了一个GetResult虚方法。同时分别创建了加减乘除子类去重写GetResult方法来降级耦合度。

    回归正题(简单工厂模式)

    我们需要通过简单工厂模式,来让程序知道该实例化谁。需要来创建一个工厂类:

    public class OperationFactory
    {
        public static Operation CreateOperation(string operation)
        {
            switch (operation)
            {
                case "+":
                    return new AddOperation();
                case "-":
                    return new SubtractOperation();
                case "*":
                    return new MultiplyOperation();
                case "/":
                    return new DivideOperation();
                default:
                    return null;
            }
        }
    }
    

    创建了工厂类有什么好处呢,好处就是,只需要输入运算符号,工厂就能自己实例化出合适的对象,通过多态,返回父类的方法实现了计算器的计算结果。
    Main方法
    通过简单工厂模式,让我们在计算加减乘除的时候只需要去增加对应的子类就行了,下面的代码进行加法运行时,通过传入+号让工厂去帮我们实例化子类。

    static void Main(string[] args)  
    {  
        try  
        {  
            // 简单工厂模式  
            var oper = OperationFactory.CreateOperation("+");  
            oper.Num1 = 10;  
            oper.Num2 = 5;  
            Console.WriteLine(oper.GetResult());  
        }    
        catch (Exception e)  
        {        
        Console.WriteLine("发生异常:" + e.Message);  
        }
    }
    

    类图

    讲完简单工厂模式后,简简单单复现一下类图:
    (之前线画错了,下图已更正)
    image

    小小知识点

    • 接口:强调“做什么”,即接口定义了对象应该做什么,而不关心它是如何做的。
    • 虚方法:强调“如何做”,即基类提供了一种实现方式,但允许派生类根据需要进行修改。

    参考资料

    • 熟读并反复背诵大话设计模式-程杰出版

    __EOF__

  • 本文作者: 妙妙屋(zy)
  • 本文链接: https://www.cnblogs.com/zyplj/p/18306505
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    【C语言实现内核链表】
    终于找到了!多种类型的电子期刊模板在这里!
    @RunWith注解找不到,怎么办?
    CentOS Linux 的安装
    经典算法冒泡排序之标志位优化版
    如何根据rfc5280中的crl流程写一个crl吊销查询代码
    IP地址详解、无分类编址和路由寻址(计算机网络二)
    ICCV 2023|小红书 4 篇入选论文亮点解读,「开集视频目标分割」获得 Oral
    分布式id解决方法--雪花算法
    aws-msk-托管kafka集群的简单使用(VPC内部访问:无验证和SASL认证)
  • 原文地址:https://www.cnblogs.com/ZYPLJ/p/18306505