笑说设计模式-小白逃课被点名

happlyfox
• 阅读 1344

关于我

文章首发 | 我的博客 | 欢迎关注

简介

工厂模式(Factory Pattern)是最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象。

分类

工厂模式可以分为三种,其中简单工厂一般不被认为是一种设计模式,可以将其看成是工厂方法的一种特殊。

  • 简单工厂
  • 工厂方法
  • 抽象工厂

场景分析

平凡枯燥的文字总是让人看得想睡觉,接下来我们用几个情景案例来进行分析

简单工厂

直接通过一个Factory【工厂类】类创建多个实体类的构造方式。

时间:2021年2月19日 地点:教室 人物:学生小白、老师、大佬黑皮


小白是一名大二的计算机系学生,懒惰不爱学习。今天早晨第一节课就因为睡懒觉迟到被老师逮个正着,这节课还正好是小白最头疼的上机课"C#设计模式”。这不,课堂上到一半老师就开始提问,小白“光荣”的成为了老师的点名对象。

老师笑着说道:“小白,请你解答一下屏幕上的问题。”

题目:请使用c#、java、python、php或其他任一面向对象编程语言实现输入俩个合法数字和一个合法符号,输出对应结果的功能。

小白一看,这算什么题目,这么简单,看我不手到擒来,伴随着双手噼里啪啦一顿敲击声音,屏幕上出现一串编码。

*Calculator操作类 *

    public class Calculator
    {
        public double GetResult(double A, double B, string operate)
        {
            double result = 0d;
            switch (operate)
            {
                case "+": 
                    result = A + B;
                    break;
                case "-":
                    result = A - B;
                    break;
                case "*":
                    result = A * B;
                    break;
                case "/":
                    result = A / B;
                    break;
                default: break;
            }
            return result;
        }
    }

客户端代码

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请输入数字A");
            string a = Console.ReadLine();
            Console.WriteLine("请输入数字B");
            string b = Console.ReadLine();
            Console.WriteLine("请选择操作符号(+、-、*、/)");
            string operate = Console.ReadLine();

            Calculator box = new Calculator();
            double result = box.GetResult(Convert.ToDouble(a), Convert.ToDouble(b), operate);
            Console.WriteLine(result);

            Console.ReadKey();
        }
    }

小白:”老师,我写好了“

老师:"不错,写的很好。使用到了面向对象三大特性中的封装,将计算方法封装成了一个计算类,多个客户端可以复用这个类。但是这其中也有不少的问题,哪位同学来回答一下。"

黑皮:”小白同学写的代码有俩处问题。

1、如果输入A=10,B=0,程序就会报错,没有做输入的有效性验证

2、如果操作符号不按照规定的输入 ,也会导致报错“

老师:”黑皮同学回答的非常好,但是这都只是针对代码业务逻辑错误的描述。有没有谁可以看出更加深层次的内容。“

黑皮:”老师,你给点提示吧。“

老师:”这个可以会有点隐蔽,老师就给出一点提示。如果我们增加一个新的运算怎么办?“

小白立即回答到:”在switch中增加一个新的分支就可以了“。

老师:”这样当然是没有错误的,但是问题在于,我只是增加了一个新的运算符号,却需要让加减乘除所有的运算都参加编译,如果在修改的过程中不小心修改了其他的代码,例如把+号改成了-号,那不是很糟糕,这就违背了开闭原则【对扩展开放,对修改关闭】“

小白挠一挠头问道:”开闭原则,这是什么?“

老师:”这就是你不认真听课落下的内容,回去好好补习。黑皮同学,不知道你Get到了老师的点没有?“

黑皮:”我知道应该如何修改了。小白实现了面向对象三大特性之一的封装,其实就是将其他俩个特性一起使用就可以完成老师要的功能“

小白:”多态和继承?“

黑皮:”是的,等我改完你再看程序就应该有感觉了“

    public class Operate
    {
        public double NumberA { get; set; }
        public double NumberB { get; set; }

        public virtual double GetResult()
        {
            return 0;
        }
    }

    public class OperateAdd : Operate
    {
        public override double GetResult()
        {
            return this.NumberA +this.NumberB;
        }
    }

    public class OperateSub : Operate
    {
        public override double GetResult()
        {
            return this.NumberA - this.NumberB;
        }
    }

简单工厂

    public class OperateFactory
    {
        public static Operate GetOperateFactory(string operate)
        {
            Operate fac = null;
            switch (operate)
            {
                case "+":
                    fac = new OperateAdd();
                    break;
                case "-":
                    fac = new OperateSub();
                    break;
                case "*":
                    fac = new OperateMul();
                    break;
                case "/":
                    fac = new OperateDiv();
                    break;
                default:
                    break;
            }
            return fac;
        }
    }

黑皮:”首先是一个运算类,里面有俩个Number属性和一个虚方法GetResult()。加减乘除四个方法作为运算类的子类继承,继承后重写GetResult()方法,调用基类的A和B公有属性进行不同的数学运算。“

黑皮:”然后定义一个简单工厂,静态方法传入操作符参数得到实际的业务处理类,客户端得到处理类后对参数赋值。最后一步你应该知道怎么做了吧“

小白:”我懂了,那客户端这么调用就可以了“。

  static void Main(string[] args)
        {
            Console.WriteLine("请选择操作符号(+、-、*、/)");
            string operateStr = Console.ReadLine();
            Operate operate = OperateFactory.GetOperateFactory(operateStr);
            operate.NumberA = 10;
            operate.NumberB = 4;
            double result = operate.GetResult();
            Console.WriteLine(result);
            Console.ReadKey();
        }

老师:”看来俩位同学都已经掌握了简单工厂的使用,接下来我提问几个问题,便于大家更快的掌握这种设计模式“

老师:”如果我们要修改除方法的逻辑,增加被除数为0的逻辑应该怎么做“

小白:”直接修改OperateDiv类,这不会对其他造成影响“

老师:”如果我们要新增一个开根运算应该怎么做“

小白:”添加一个新的类,Operate开根类,里面描述开根的逻辑。在工厂方法中将新的操作符添加进去即可。新增的操作单独一个类,也不会对其他方法体造成影响“。

小白:”那客户端需要做什么改变吗?“

老师:”客户端要做什么改变,客户端只要处理好自己的事情就可以了!“

是的,客户端不关心工厂创建了什么,工厂是一个黑盒子。客户端只要传入参数,工厂负责将内容生产后【实例化类的过程】给客户端即可。

优/缺点

简单工厂模式的工厂类一般是使用静态方法,通过接收的参数不同来返回不同的对象实例。不修改代码的话,是无法扩展的 优点:客户端可以免除直接创建产品对象的责任,而仅仅是“消费”产品。简单工厂模式通过这种做法实现了对责任的分割 缺点:由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了

工厂方法

时间:2021年2月19日下午 地点:教室 人物:学生小白、老师、大佬黑皮

老师:”我们紧接着上午的设计模式继续,上午我们讲的是简单工厂,下午我们讲下一个内容工厂方法。工厂方法和简单工厂其实大同小异,唯一的区别就在于每一个实现抽象类的实例(也叫做产品,即上午定义的加减乘除四个子类)都有一个对应的工厂去创建。同学们了解一下老师说话的内容,然后寻找一个场景编码实现一下。最快完成的有课堂奖励”

....几分钟过去了.....

小白:“老师,我完成了。”

老师:“好的,那我们请小白同学说明一下场景和实现的方式。”

我设计的是以水果作为场景的模式。

1、定义一个抽象类Fruit.cs,这个类定义俩个抽象方法printfColor()printfName()

2、实现俩种不同的水果分别继承此抽象类并复写抽象方法。

3、定义一个工厂接口,定义接口方法createFruit()

4、实现俩个不同的工厂分别实现水果实例的创建。

水果抽象类

    public abstract class Fruit
    {
        public abstract void PrintfColor();
        public abstract void PrintfName();
    }

  public class Apple : Fruit
    {
        public override void PrintfColor()
        {
            Console.WriteLine("红色");
        }

        public override void PrintfName()
        {
            Console.WriteLine("苹果");
        }
    }

工厂接口

   public interface IFruitFactory
    {
        Fruit CreateFruit();
    }

    public class AppleFactory : IFruitFactory
    {
        public Fruit CreateFruit()
        {
            return new Apple();
        }
    }

客户端实现

           //苹果工厂
            IFruitFactory appleFac = new AppleFactory();
            Fruit apple = appleFac.CreateFruit();
            apple.PrintfColor();
            apple.PrintfName

            //橘子工厂
            IFruitFactory orangeFac = new OrangeFactory();
            Fruit orage = orangeFac.CreateFruit();
            orage.PrintfColor();
            orage.PrintfName();

老师:“看来小白同学已经对上午的内容有了一个充分的了解,果然好好上课才能够学习到新的知识。逃课是没有益处的”

老师:“只是这样的案例太过简单,可能其他同学不是很能理解为什么要这样 ,我来举一个实际场景的案例方便大家理解。在实际的工作过程中我们总会用到日志组件,例如Nlog,Log4net这种第三方组件,这种组件都支持可配置化的多源输出。当我们在配置文件(json/xml)中增加一个“输出到控制台的参数”,程序 就会将内容输出到控制台,当配置一个输入到文件的参数,程序就会将内容输出到指定的文件。这种场景的实现其实就是一种典型的工厂方法。下面我来分析一下过程”

1、读取配置文件(json/xml)

2、获取所有的配置方式,循环遍历

3、判断配置类型,如果是输入到文件的配置。new一个文件日志工厂,将配置信息作为参数传递,便于后期方法调用;如果是输入到控制台的配置。new一个日志工厂也是做同样的操作

4、每一个工厂只管理自己的事情,但是应该都拥有日志输出这个接口。

5、当上层调用打印方法时候,循环遍历所有的工厂,调用接口的日志输出输出方法

优/缺点

工厂方法是针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例。在同一等级结构中,支持增加任意产品 优点:允许系统在不修改具体工厂角色的情况下引进新产品 缺点:由于每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量

抽象工厂

抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。

时间:2021年2月20日上午 地点:教室 人物:学生小白、老师、黑皮

新的一天又开始了,“设计模式”课程在小白的眼中好像没有那么复杂了,今天小白早早地就到了教室,准备迎接老师新的鞭策。

老师:”同学们早上好,今天我们继续昨日的课程。昨天讲的是工厂方法,今天我们在此基础上做一点改进,看看又有什么新的变化。小白同学学习热情很高嘛,现在都知道坐在第一排了。不错不错,值得鼓励”

小白:”嘻嘻“

老师:“好,那开始今天的课程。今天要讲的模式是抽象工厂模式。通过和工厂模式做比较,同学们可以比较清晰的发现这俩都之间的区别。我们用昨天小白同学的例子继续开拓。”

此时有苹果和橘子俩个产品,分别对应苹果工厂和橘子工厂。这是工厂方法的体现。可是如果有3个不同的工厂,他们分别都生产苹果和橘子呢。

小白:“恩...那就多建立几个工厂。每个产品分别对应不同的工厂,应该是这样的一个结构,每一个工厂分别对应生产产品的类”

A

  • A_苹果工厂.cs
  • A_橘子工厂.cs

B

  • B_苹果工厂.cs

  • B_橘子工厂.cs

    C

  • C_苹果工厂.cs

  • C_橘子工厂.cs

老师:“这样的方式当然是可以的,可以如果我有10个工厂呢,难道我们要建立10*2=20个类吗,这样程序的复杂度就是直线上升,不利于维护。”

小白:“那怎么办呢,用老师你说的那种抽象工厂吗?如果用,又应该怎么做呢”

老师:“是的,在这样的场景下,抽象工厂是最能匹配的设计模式。其实做法非常简单,对昨天的代码进行一些修改即可”

水果抽象类

新增一个Name属性,方便后期打印不同的工厂。

    public abstract class Fruit
    {
        public string Name { get; set; }
        public abstract void PrintfColor();
        public abstract void PrintfName();
    }

    public class Apple : Fruit
    {
        public Apple(string name)
        {
            this.Name = name;
        }

        public override void PrintfColor()
        {
            Console.WriteLine(this.Name + "红色");
        }

        public override void PrintfName()
        {
            Console.WriteLine(this.Name + "苹果");
        }
    }

工厂接口

老师:“这一处的改动就比较明显。原来的接口中方法输出唯一的产品——因为之前每一个工厂只生产一件产品。现在输出俩个产品,即继承工厂接口的类必须实现生产苹果和橘子的方法。这样的好处在于,每一个工厂负责管理自己产品的实现,避免了每一个产品都需要创建一个工厂的操作。“

解释:

工厂生产苹果和橘子。当有多个工厂的时候,每一个工厂都实现生产苹果和橘子。而不是生产A厂苹果需要一个工厂实现类,生产B厂苹果又需要一个。如下所示

旧模式

A

  • A_苹果工厂.
  • A_橘子工厂

B

  • B_苹果工厂

  • B_橘子工厂

    C

  • C_苹果工厂

新模式

A 工厂

  • 苹果/橘子

    B 工厂

  • 苹果/橘子

    C 工厂

  • 苹果/橘子

老师:“这样复杂度由原来的6变成了3。”

小白:"我明白了,又学习到了新的东西。"

    public interface IFruitFactory
    {
        Fruit CreateApple(string name);
        Fruit CreateOrange(string name);
    }

    public class AFactory : IFruitFactory
    {
        public Fruit CreateApple(string name)
        {
            return new Apple(name);
        }

        public Fruit CreateOrange(string name)
        {
            return new Orange(name);
        }
    }

客户端实现

            IFruitFactory fac = new AFactory();
            Fruit a_Apple = fac.CreateApple("a工厂");
            Fruit a_Orange = fac.CreateOrange("a工厂");
            a_Apple.PrintfName();
            a_Orange.PrintfName();

            IFruitFactory b_fac = new BFactory();
            Fruit b_Apple = b_fac.CreateApple("b工厂");
            Fruit b_Orange = b_fac.CreateOrange("b工厂");
            b_Apple.PrintfName();
            b_Orange.PrintfName();

小白:“可是在什么样的场景下用这种模式呢,我好像一下子想不到”

老师:“抽象工厂的使用相对来说比较少,但也不是没有。我举一个例子,在后端开始中我们有各种的组件【按钮,抽屉,导航栏】等等,这些组件有对应的皮肤,对皮肤的开发就是抽象工厂的实现。工厂接口是对每个组件的定义,每个皮肤就是一个工厂的实现。如果要切换皮肤,只需要实例化不同的工厂即可。”

小白:“哦。就和游戏中的皮肤切换一样吗?”

老师:“你也可以这样理解,设计模式只是一种通用解决方案,可以应用在不同的场景下,大家可以挑最适应自己,最好理解的场景下手。”

下课铃声又响起了

老师:“好了,这节课就到这里。下节课我们讲其他的设计模式,希望大家准时听讲。”

优/缺点

抽象工厂是应对产品族概念的。应对产品族概念而生,增加新的产品线很容易,但是无法增加新的产品。比如,每个汽车公司可能要同时生产轿车、货车、客车,那么每一个工厂都要有创建轿车、货车和客车的方法 优点:向客户端提供一个接口,使得客户端在不必指定产品具体类型的情况下,创建多个产品族中的产品对象 缺点:增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性

推荐阅读

Redis工具收费后新的开源已出现

GitHub上Star最高的工程师技能图谱

中国程序员最容易发错的单词

推荐!!! Markdown图标索引网站

END

欢迎关注公众号 程序员工具集 👍👍 致力于分享优秀的开源项目、学习资源 、常用工具

回复关键词“关注礼包”,送你一份最全的程序员技能图谱。

回复关键词"wx"添加个人微信,勾搭作者,欢迎来聊^-^。

点赞
收藏
评论区
推荐文章
zdd小小菜鸟 zdd小小菜鸟
1年前
创建型-工厂模式 ( Factory Pattern )
创建型工厂模式(FactoryPattern)tex工厂模式(FactoryPattern)提供了一种创建对象的最佳方式工厂模式在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象工厂
Wesley13 Wesley13
2年前
Java设计模式之三种工厂模式
工厂模式实现了创建者和调用者的分离,实现了更好的解耦。详细分类:1)简单工厂模式(静态工厂模式);2)工厂方法模式;3)抽象工厂模式面向对象设计的基本原则:1)      OCP(开闭原则,OpenClosedPrinciple):一个软件的实体应当对扩展开放,对修改关闭。2)      
Wesley13 Wesley13
2年前
Unity C# 设计模式(二)简单工厂模式
定义:简单工厂模式是属于创建型模式,又叫做静态工厂方法(StaticFactoryMethod)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该
Wesley13 Wesley13
2年前
Java描述设计模式(04):抽象工厂模式
一、抽象工厂模式1、生活场景汽车生产根据用户选择的汽车类型,指定不同的工厂进行生产,选择红旗轿车,就要使用中国工厂,选择奥迪轿车,就要使用德国工厂。2、抽象工厂模式1.抽象工厂模式:定义了一个interface用于创建相关对象或相互依赖的对象,而无需指明具体的类;2.抽象工厂模式可以
Wesley13 Wesley13
2年前
Java进阶篇设计模式之三
前言在上一篇(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.cnblogs.com%2Fxuwujing%2Fp%2F9363142.html)中我们学习了工厂模式,介绍了简单工厂模式、工厂方法和抽象工厂模式。本篇则介绍设计模式中属于创建型模式的建造者模式和原型模式。
Stella981 Stella981
2年前
Python之设计模式
一、设计模式分类a、创建型模式简单工厂模式一、内容不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。二、角色工厂角色(Creator)抽象产品角色(Product)具体产品角色(ConcreteProduct)
Wesley13 Wesley13
2年前
C#设计模式 —— 工厂模式
。  工厂模式同样是项目中最常用的设计模式,工厂模式中又分为简单工厂,工厂方法,抽象工厂。下面我们由简单的开始逐一介绍。1.简单工厂模式  简单工厂又被称为静态工厂,在设计模式中属于创建型模式。主要解决的问题是封装了实例化的过程,通过传入参数来获不同实例。下面我们举一个项目中可能会用到的例子。  假设我们程序的数据保存在几个不同
Wesley13 Wesley13
2年前
23种设计模式(面向对象语言)
一、设计模式的分类总体来说设计模式分为三大类:创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。创建型模式是用来创建对象的模式,抽象了实例化的过程,帮助一个系统独立于其他关联对象的创建、组合和表示方式。所有的创建型模式都有两个主要功能:  1.将系统所使用的具体类的信息封装起来  2.隐藏
Wesley13 Wesley13
2年前
JS创建对象模式7种方法详解
创建对象的几种模式虽然Object构造函数或者字面量,都可以用来创建对象,但这些方式有明显的缺点:使用同一个接口创建很多对象,会产生大量的代码,于是,工厂模式诞生了1工厂模式工厂模式是广为人知的设计模式,抽象了创建具体对象的过程。在ES6的Class创建类之前,是无法创建类的,开发人员就发明了一种函数,用函数来封
Wesley13 Wesley13
2年前
C++ 常用设计模式(学习笔记)
设计模式1、工厂模式在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。工厂模式作为一种创建模式,一般在创建复杂对象时,考虑使用;在创建简单对象时,建议直接new完成一个实例对象的创建。1.1、简单工厂模式主要特点是需要在工厂类中做判断,从而创造相应的产品,当
happlyfox
happlyfox
Lv1
关注公众号【程序员工具集】领取海量视频学习资源
文章
19
粉丝
3
获赞
5