• C#设计模式详解(2)——Factory Method(工厂方法)


    C#设计模式详解(2)——Factory Method(工厂方法

    工厂方法模式

    1.1 概念

    亦称: 虚拟构造函数、Virtual Constructor、Factory Method

    1.2 意图

    工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。

    1.3 问题

    假设你正在开发一款物流管理应用。 最初版本只能处理卡车运输, 因此大部分代码都在位于名为 卡车的类中。

    一段时间后, 这款应用变得极受欢迎。 你每天都能收到十几次来自海运公司的请求, 希望应用能够支持海上物流功能。

    在程序中新增一个运输类会遇到问题

    如果代码其余部分与现有类已经存在耦合关系, 那么向程序中添加新类其实并没有那么容易。

    这可是个好消息。 但是代码问题该如何处理呢? 目前, 大部分代码都与 卡车类相关。 在程序中添加 轮船类需要修改全部代码。 更糟糕的是, 如果你以后需要在程序中支持另外一种运输方式, 很可能需要再次对这些代码进行大幅修改。

    最后, 你将不得不编写繁复的代码, 根据不同的运输对象类, 在应用中进行不同的处理。

    1.4 解决方案

    工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用 (即使用 new运算符)。 不用担心, 对象仍将通过 new运算符创建, 只是该运算符改在工厂方法中调用罢了。 工厂方法返回的对象通常被称作 “产品”。

    创建者类结构

    子类可以修改工厂方法返回的对象类型。

    乍看之下, 这种更改可能毫无意义: 我们只是改变了程序中调用构造函数的位置而已。 但是, 仔细想一下, 现在你可以在子类中重写工厂方法, 从而改变其创建产品的类型。

    但有一点需要注意:仅当这些产品具有共同的基类或者接口时, 子类才能返回不同类型的产品, 同时基类中的工厂方法还应将其返回类型声明为这一共有接口。

    产品对象层次结构

    所有产品都必须使用同一接口。

    举例来说, 卡车Truck和 轮船Ship类都必须实现 运输Transport接口, 该接口声明了一个名为 deliver交付的方法。 每个类都将以不同的方式实现该方法: 卡车走陆路交付货物, 轮船走海路交付货物。 陆路运输Road­Logistics类中的工厂方法返回卡车对象, 而 海路运输Sea­Logistics类则返回轮船对象。

    使用工厂方法模式后的代码结构

    只要产品类实现一个共同的接口, 你就可以将其对象传递给客户代码, 而无需提供额外数据。

    调用工厂方法的代码 (通常被称为客户端代码) 无需了解不同子类返回实际对象之间的差别。 客户端将所有产品视为抽象的 运输 。 客户端知道所有运输对象都提供 交付方法, 但是并不关心其具体实现方式。

    1.5 工厂方法模式结构

    工厂方法模式结构

    1. 产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。

    2. 具体产品 (Concrete Products) 是产品接口的不同实现。

    3. 创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。

      你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。

      注意, 尽管它的名字是创建者, 但它最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。 但是, 这些公司的主要工作还是编写代码, 而非生产程序员。

    4. 具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。

      注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。

    1.6 案例代码

    (自己写的,有问题欢迎指出并且讨论)

    类图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xOrAH5dU-1669545517115)(C:\Users\JackiieWang\AppData\Roaming\Typora\typora-user-images\image-20221127183817280.png)]

    IProduct

    namespace _001_test_工厂模式
    {
        public interface IProduct
        {
            public string Operation();
            public void RuneLine();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ConcreteProduct1

    using System;
    
    namespace _001_test_工厂模式
    {
        class ConcreteProduct1 : IProduct
        {
            public string Operation()
            {
                string result = "具体产品1的相关操作";
                return result;
            }
    
            public void RuneLine()
            {
                ProductOneThings();
            }
    
            public void ProductOneThings()
            {
                Console.WriteLine("这是专属于具体产品1独有的特性");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    ConcreteProduct2

    using System;
    
    namespace _001_test_工厂模式
    {
        class ConcreteProduct2 : IProduct
        {
            public string Operation()
            {
                var result = "具体产品2的相关操作";
                return result;
            }
    
            public void RuneLine()
            {
                ProductTwoThings();
                OnSale();
            }
    
            public void ProductTwoThings()
            {
                Console.WriteLine("这是专属于具体产品2独有的特性");
            }
    
            public void OnSale()
            {
                Console.WriteLine("这是具体产品2的销售方法");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    ConCretor

    namespace _001_test_工厂模式
    {
        public abstract class ConCretor
        {
            public abstract IProduct FactoryMethod();
    
            public string SomeOperation()
            {
                var product = FactoryMethod();
                var result = "父类执行了ConCretorDoSomeThing()方法并调用了" + product.Operation();
                return result;
            }
    
            //...父类调用子类的操作流程
            public void RuneLine()
            {
               var product = FactoryMethod();
               product.RuneLine();
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    ConcreteCreator1

    namespace _001_test_工厂模式
    {
        class ConcreteCreator1 : ConCretor
        {
            public override IProduct FactoryMethod()
            {
                return new ConcreteProduct1();
    
                //...具体产品接口的变化可以在具体的产品内部去做操作
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ConcreteCreator2

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace _001_test_工厂模式
    {
        class ConcreteCreator2 : ConCretor
        {
            public override IProduct FactoryMethod()
            {
                return new ConcreteProduct2();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Client

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace _001_test_工厂模式
    {
        public class Client
        {
    
            public void Main()
            {
                Console.WriteLine("通过客户端中的ConCretor类调用子类接口中的方法,调用了具体产品1的创建者方法");
                ClientCode(new ConcreteCreator1());
    
                Console.WriteLine("");
    
                Console.WriteLine("通过客户端中的ConCretor类调用子类接口中的方法,调用了具体产品2的创建者方法");
                ClientCode(new ConcreteCreator2());
            }
    
            //客户端代码与具体创建者的实例一起工作,尽管是通过其基本接口。只要客户端通过基接口继续与创建者工作,您就可以将创建者的任何子类传递给它。
            public void ClientCode(ConCretor creator)
            {
                // ...
                //客户端:我不知道创建者的类是“+”但它仍然有效。
                Console.WriteLine("客户端:我不知道ConCretor类是一个什么样的类,但是我依旧工作\n" + creator.SomeOperation());
                //Console.WriteLine("调用子类的产品流水线");
                //creator.RuneLine();//统一调用所有子类的流水线方法
    
                //如果说需要针对不同类型的产品类型做处理,我们也一样可以通过switch case做处理
                if(typeof(ConcreteCreator1)== creator.GetType())
                {
                    Console.WriteLine("此时创建的类型为 具体产品类1");
                }
                else
                {
                    Console.WriteLine("此时创建的类型不是具体产品类1");
                    creator.RuneLine();
    
                }
    
                //还有一种思路,我们是不是可以尝试去对这个进行switch case的转换?来简化if的操作呢?但是我们也要注意,一个注重程序代码质量和美感的程序员
                //都在为了减少switch casse的使用,我们称之为代码整洁,所以我们要根据对应的情况去使用,如果说成千上万个case杂糅在一起是否是一种好的操作呢?
                //这点需要我们进行思考;
                // ...
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    Program

    using _001_test_工厂模式;
    using System;
    
    namespace _001_工厂模式_test
    {
        class Program
        {
            static void Main(string[] args)
            {
                new Client().Main();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行结果:

    ①产品内部执行的方法和流程都一致,调用所有产品类的方法:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dGUs3j81-1669545517115)(C:\Users\JackiieWang\AppData\Roaming\Typora\typora-user-images\image-20221127183154708.png)]

    ②当不同的产品内部发生变化时,我们需要根据不同的产品类型执行不同的操作时:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gMFucpDu-1669545517115)(C:\Users\JackiieWang\AppData\Roaming\Typora\typora-user-images\image-20221127183244743.png)]

    1.7 游戏开发中的应用

    作为一个游戏开发者,我相信大家对这个更是广为应用;而对于我而言,我的理解:

    像我们常见玩的游戏里面都有一种技能或者同类型不同操作的功能,我们是否可以尝试用工厂模式来使代码尽可能的减少与父类之间耦合,达到单一职责的目的;

    例如:技能常见的分为 主动技能,被动技能,增益技能,技能有待学习,技能已经学习,技能可以升级,技能已经满级,技能的等级状态等等;

    我们都可以去思考是否可以通过这种途径去做处理;

    以上便是我对这个模式的了解和学习总结,希望能够帮助大家去理解这个设计模式,当然如果发现我的讲解其实是有问题的,欢迎大家指出我的问题所在,也欢迎大家留言讨论,设计模式的学习一直在路上,我后续还是跟进设计模式的理解和学习,想要了解的朋友可以关注我哈~嘻嘻(●’◡’●)
    在这里插入图片描述
    以上呢,便是我今日学习并实践工厂模式的总结,希望能够对你有所帮助~ 也希望你能够点赞、评论吖~ 你们的点赞、评论就是我前进的动力!

    公众号:平平无奇代码猴
    也可以搜索:Jackiie_wang 公众号,欢迎大家关注!欢迎催更!留言!

    作者:ProMer_Wang

    链接:https://blog.csdn.net/qq_43801020/article/details/128067846

    本文为ProMer_Wang的原创文章,著作权归作者所有,转载请注明原文出处,欢迎转载!

  • 相关阅读:
    游戏设计模式专栏(五):三步学会原型模式
    开发日志:Kylin麒麟操作系统部署ASP.NET CORE
    用HTML+CSS+JS做一个漂亮简单的公司网站(JavaScript期末大作业)
    docker 部署问题集锦
    项目篇——java文档搜索引擎
    一起来学习flutter 的布局组件
    C 风格文件输入/输出---无格式输入/输出---(std::fputc,std::putc,std::fputs)
    CV&NLP基础10之卷积神经网络CNN进阶
    Jmeter之接口测试基础篇
    vue状态管理——Vuex
  • 原文地址:https://blog.csdn.net/qq_43801020/article/details/128067846