C#面向对象设计模式2:工厂方法(Factory Method)

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

一、情景

假设开了小餐馆,自主开发一套点餐系统什么的,提供了“手撕鸡”、“黑椒牛柳”、“酸菜鱼”这些菜,那么您最初的版本,可能就只有这三道菜的代码。

小店经营一段时间后,客户越来越多,对菜品的丰富性也开始提出了要求,时常听到有人想吃“麻婆豆腐”,于是你为了多赚点钱,开始新增这道菜。

如果您的代码与现有的其它类存在耦合关系,特别是当这种关系还是使用面向过程来编写的情况,那么您想要添加新类实现新的需求可能并没有你想象中的那么容易。

好的,能够多提供菜品,才能更好的留住客人。但是代码要怎么写?现有的系统大部分就与那三道菜有关,可能全部代码都是针对这最初的三道菜来开发的,在程序中添加新菜“麻婆豆腐”,可能让您面临着修改全部代码。

仅仅是这样那也就算了,劳动辛苦下就是了。但万一将来客户喜欢吃更多的菜品,难道你又来修改一遍?每次都来重新干一遍吗?显然这不应该。

二、解决方案

1.定义产品

工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用 (即使用 new运算符)。 不用担心, 对象仍将通过 new运算符创建, 只是该运算符改在工厂方法中调用罢了。 工厂方法返回的对象通常被称作 “产品”。而对应我们的案例,产品就是我们的各道菜。如“手撕鸡”、“黑椒牛柳”、“酸菜鱼”这些菜,这些产品可能都有共同的接口名称,就叫“菜品”。

2.定义工厂

工厂用于处理一类的产品,并返回该产品的对象类型。在我们的案例中,工厂就是我们的"厨房",工厂方法就是我们厨房的“加工”。

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

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

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

3.处理逻辑

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

举例来说:

“手撕鸡”、“黑椒牛柳”、“酸菜鱼”这些菜,都必须实现“菜品”接口,该接口定义了一个“制作”方法。每个产品类都将以不同的方式实现该方法: 酸菜鱼有酸菜鱼的制作方法, 手撕鸡当然也有手撕鸡自己的制作方法。 ​ 

厨房呢,是我们的加工厂,“工厂方法”模式中的方法在我们的示例中,就是“厨房”类中的“加工”方法,该方法可以被子类继承,并实现各自的加工方法,然后得到菜品加工对象,也就是产品。

4.针对业务变化

您只需要新增对应的产品类,以及工厂方法,即可无限扩展业务。

举例来说:

本店要增加”麻婆豆腐“这道菜,你需要实现“菜品”接口,以及厨房的子类进而重写“加工”方法即可。

 

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

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

三、工厂方法模式的类图结构

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

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

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

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

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

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

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

四、代码模拟生成

以下是使用C#的模拟编写的例子:

using System;

namespace SimpleFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            //如果客人点的是酸菜鱼
            厨房 kitchen = new 加工酸菜鱼();
            kitchen.客人点菜();
            kitchen.加工();
        }
    }

    public interface 菜品
    {
        void 制作();
    }

    public class 酸菜鱼 : 菜品
    {
        public virtual void 制作()
        {
            Console.WriteLine("做出了酸菜鱼");
        }
    }

    public class 麻婆豆腐 : 菜品
    {
        public virtual void 制作()
        {
            Console.WriteLine("做出了麻婆豆腐");
        }
    }

    public abstract class 厨房
    {
        public abstract void 客人点菜();
        public abstract void 加工();
    }

    public class 加工酸菜鱼 : 厨房
    {
        private 菜品 _dishes;
        public override void 客人点菜()
        {
            Console.WriteLine("客人点了酸菜鱼");
            _dishes = new 酸菜鱼();
        }
        public override void 加工()
        {
            this._dishes.制作();
        }
    }

    public class 加工麻婆豆腐 : 厨房
    {
        private 菜品 _dishes;
        public override void 客人点菜()
        {
            Console.WriteLine("客人点了麻婆豆腐");
            _dishes = new 麻婆豆腐();
        }
        public override void 加工()
        {
            this._dishes.制作();
        }
    }
}

程序运行结果如下:

五、工厂方法模式适合应用场景

1.当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。

工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。

例如,如果需要向应用中添加一种新产品,你只需要开发新的创建者子类,然后重写其工厂方法即可。

2.如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。

继承可能是扩展软件库或框架默认行为的最简单方法。 但是当你使用子类替代标准组件时, 框架如何辨识出该子类?解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。

3.如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。

在处理大型资源密集型对象 (比如数据库连接、 文件系统和网络资源) 时, 你会经常碰到这种资源需求。而且它们必须位于同一处, 这样才能确保重复代码不会污染程序。

可能最显而易见, 也是最方便的方式, 就是将这些代码放置在我们试图重用的对象类的构造函数中。 但是从定义上来讲, 构造函数始终返回的是新对象, 其无法返回现有实例。

因此, 你需要有一个既能够创建新对象, 又可以重用现有对象的普通方法。 这就是你此刻使用工厂方法的理由。

六、代码编写步骤

  1. 让所有产品都遵循同一接口。 该接口必须声明对所有产品都有意义的方法。本例指“菜品”接口。

  2. 在创建类中添加一个空的工厂方法。 该方法的返回类型必须遵循通用的产品接口。本例指“厨房”类中的“加工”方法。

  3. 在创建者代码中找到对于产品构造函数的所有引用。 将它们依次替换为对于工厂方法的调用, 同时将创建产品的代码移入工厂方法。 你可能需要在工厂方法中添加临时参数来控制返回的产品类型。本例指编写“加工”方法的方法体内容。

    工厂方法的代码看上去可能非常糟糕。 其中可能会有复杂的 switch分支运算符或者if/else条件运算符, 用于选择各种需要实例化的产品类。但是不要担心,在接下去的第4点中,我们很快就会修复这个问题。

  4. 现在,为工厂方法中的每种产品编写一个创建者子类, 然后在子类中重写工厂方法, 并将基本方法中的相关创建代码移动到工厂方法中。本例指“加工酸菜鱼”类和“加工麻婆豆腐”类。

  5. 如果应用中的产品类型太多, 那么为每个产品创建子类并无太大必要, 这时你也可以在子类中复用基类中的控制参数。针对菜品特别多的时候,比如满汉全席,高达100多甚至几百道菜,难道每个都创建一次子类?

    这时候,你可以一是通过创建多个多子类来实现更多的工厂方法,二是可以通过传递参数来处理同一类的加工方法,这样会缩减子类数量。

  6. 如果代码经过上述移动后, 基础工厂方法中已经没有任何代码, 你可以将其转变为抽象类。 如果基础工厂方法中还有其他语句, 你可以将其设置为该方法的默认行为。

七、工厂方法模式优缺点

优点:

  •  你可以避免创建者和具体产品之间的紧密耦合。
  •  单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
  •  开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。

缺点:

  •  应用工厂方法模式需要引入许多新的子类,这样会产生很多的C#类文件, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。

八、与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式原型模式生成器模式 (更灵活但更加复杂)。

  • 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

  • 你可以同时使用工厂方法迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。

  • 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。

  • 工厂方法模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。

 

面向对象的武功招式是一个艰难的学习和训练过程,它将耗费你3年以上的实践时间,请给自己多点耐心。即使您认为自己是神级人物,您可能也要不少于1年以上的实践时间。

 

源码下载:https://download.csdn.net/download/mazhiyuan1981/13992361

 

祝您用餐愉快。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值