设计模式 | 工厂模式

我们将探讨 23 种设计模式中的工厂模式,属于创建型模式。主要目的是封装对象的创建过程。

比如说某某电子厂,通常会接到不同商家的订单,根据商家提供对应的图纸,生产并组装出成品,交付给客户。

商家无需知道这产品的制造和组装过程,只需根据商家提供的图纸,还原成品即可。

面向直接开发

例如您看到疯狂星期四非常火爆,也想开一家自己的疯狂汉堡餐厅,并且为这家疯狂汉堡餐厅创建了一个用于配送汉堡的应用程序。

现在,我们配送的每种口味的汉堡都由一个类来表示,根据我们已经研制出的汉堡种类,并支持汉堡的配送和生产代码。

java 复制代码
public class Restaurant
{
    public ??? OrderBurger(string request)
    {
        //牛肉汉堡
        if ("beef".Equals(request))
        {
            BeefBurger burger = new BeefBurger();
            burger.prepare();
            return burger;
        }
        else if ("veggie".Equals(request))//素汉堡
        {
            VeggieBurger burger = new VeggieBurger();
            burger.prepare();
            return burger;
        }
        return null;
    }
}

目前来说,我们根据客户需求来制作对应的汉堡,创建相对应的'汉堡'对象,并返回给客户。这里偏向于面向直接开发方式,的确可以最为直观和快速。

设计抽象类

我们很快就会发现问题,这样的写法有一些限制,目前只能创建 BeefBurger 和 VeggieBurge 对象代表不同类型的产品,且返回单一类型的单一对象。

因此,如果这些产品没有共同的基类和接口,我们将无法很好地实现并且返回指定的对象,且存在大量的冗余的属性。

csharp 复制代码
//汉堡抽象类
public abstract class Burger
{
    //产品id
    public int productId { get; set; }
    //添加配菜
    public string addOns { get; set; }
    public abstract Burger prepare();
}
//牛肉堡
public class BeefBurger : Burger
{
    //是否安格斯牛
    public bool angus { get; set; }
    public override Burger prepare()
    {
        return new BeefBurger();
    }
}
//素堡
public class VeggieBurger : Burger
{
    //是否安格斯牛
    public bool angus { get; set; }
    public override Burger prepare()
    {
        return new VeggieBurger();
    }
}

通常,我们会建立一个基类 Burger,将两个汉堡的类抽象为一个类,还可以将两个汉堡的共同属性提取到抽象类 Burger,因为 BeefBurger 和 VeggieBurger 继承至 Burger 之后,相应的属性和方法已经实现。

csharp 复制代码
public class RestaurantCover
{
    public Burger OrderBurger(string request)
    {
        Burger burger = null;
        //牛肉汉堡
        if ("beef".Equals(request))
        {
            burger = new BeefBurger();
        }
        else if ("veggie".Equals(request))//素汉堡
        {
            burger = new VeggieBurger();
        }
        return burger;
    }
}

我们的代码经过改进,实现了简易的扩展。但我们的餐厅现在还不够疯狂,随时面临顾客的不同口味需求,我们不得不扩展我们的菜单,添加更多种类的汉堡。

工厂设计模式

一旦发生扩展,我们不得不更改上面的代码。这样的改动不仅会影响之前稳定运转的程序,还违背了开放-封闭原则和单一职责原则。

每次扩展都对这些相同的位置进行更改且冗余的代码,这一定很糟糕不想这么干。

因此,我们不如把那些容易发生变化的进行封装起来,当我们的餐厅随着时间的推移而发展和变化时,我们可以直接对菜单进行新增,修改,删除。

我们把创建汉堡的流程进行封装起来,将其分离到一个 Factory 的类中。

为什么是工厂?因为这是一个唯一负责制作汉堡的类,交给汉堡生产部门进行生产汉堡。

csharp 复制代码
public class SimpleBurgerFactory
{
    public Burger CreateBurger(string request)
    {
        Burger burger = null;
        //牛肉汉堡
        if ("beef".Equals(request))
        {
            burger = new BeefBurger();
        }
        else if ("veggie".Equals(request))
        {
            burger = new VeggieBurger();
        }
        return burger;
    }
}

我们需要汉堡时统一调用这个方法,他们就会给到我们需要的汉堡。

ini 复制代码
public class Restaurant
{
    public Burger orderBurger(string request)
    {
        SimpleBurgerFactory factory = new SimpleBurgerFactory();
        Burger burger = factory.CreateBurger(request);
        burger.prepare();
        return burger;
    }
}

现在汉堡工厂有了,我们要做的就是联系汉堡工厂,将我们的汉堡需求订单给到它,然后我们需要的食物就已经获得了。

我们不再需要担心具体的汉堡制作细节问题,也不需要注意请求汉堡的类型,我们只负责接收汉堡订单需求。无论是何种类型的汉堡,最终它都是实现 Burger 接口,这就是我们疯狂汉堡餐厅所关心的。

我们将这种设计模式称为简单工厂模式

我们目前拥有了客户端餐厅作为接收顾客的汉堡需求订单,拥有了工厂,它是唯一的地方,所提供的汉堡种类是已知的,分别为牛肉汉堡和素汉堡。

目前来说,我们疯狂汉堡店的设计模式并不是成熟的官方模式,它更像是一种常用的习惯设计思路。

**

**

工厂方法设计模式

**

**

当我们理解了这个思路,我们就可以用上更为强大的设计模式:工厂方法设计模式。此模式也是一种创建型的设计模式。

它通过将产品的创建代码部分与使用该产品的代码部分进行分离,以此减少给定代码的耦合度。

我们将结合疯狂汉堡店的示例来说明,为什么说现在实现的设计模式不是一种成熟的设计模式。

当我们将汉堡的制作逻辑封装在简单工厂类中,我们隔离了该逻辑并创建了一个 SimpleBurgerFactory 类,目的是为了创建汉堡对象。这里我们遵循了单一职责原则,只负责创建汉堡对象。

但是仔细看这个类的代码,我们发现还是会有改动,比如我们增加了鸡肉汉堡,我们就需要在这里进行添加代码,更多的汉堡类型时将会出现更多的 if 逻辑代码。

为了解决这个缺陷,我们将引入工厂方法设计模式。要改造当前简单工厂,我们需要删除 SimpleBurgerFactory 简单工厂类,并将 CreateBurger() 创建汉堡的逻辑和 Restaurant 餐厅的 orderBurger订单 都放回到 Restaurant 餐厅中,从头开始升级。

接下来我们要做的第一件事,就是抽象餐厅类,为什么这么做?

之前存在于简单工厂类中的 CreateBurger() 方法,现在将成为 Restaurant 类中的抽象方法,该方法是工厂方法,将交给 Restuarant 子类去实现,并基于在汉堡上,我们提供的这些子类分别是 BeefBurgerRestaurant 和 VeggieBurgerRestaurant。

csharp 复制代码
//牛肉堡餐厅
public class BeefBurgerRestaurant : Restaurant
{
    public override Burger CreateBurger()
    {
        return new BeefBurger();
    }
}
//素堡餐厅
public class VeggieBurgerRestaurant : Restaurant
{
    public override Burger CreateBurger()
    {
        return new VeggieBurger();
    }
}

上面代码示例中,我们可以看到这实际上是给定代码中的精确定位工厂的使用方式, 因为工厂方法设计模式严重依赖于继承,它将对象的创建委托给实现的子类工厂方法。

我们的汉堡产品定义接口后,我们就可以让创建者子类决定要实现哪个类,这才是我们在这个实现过程中需要做的。

好,那现在剩下的就是用工厂方法替换业务逻辑中的创建代码。

csharp 复制代码
public abstract class Restaurant
{
    public IBurger orderBurger()
    {
        IBurger burger = CreateBurger();
        burger.Prepare();
        return burger;
    }
    public abstract IBurger CreateBurger();
}
csharp 复制代码
//牛肉堡餐厅
public class BeefBurgerRestaurant : Restaurant
{
    public override IBurger CreateBurger()
    {
        return new BeefBurger();
    }
}
//素堡餐厅
public class VeggieBurgerRestaurant : Restaurant
{
    public override IBurger CreateBurger()
    {
        return new VeggieBurger();
    }
}
csharp 复制代码
//Burger 接口
public interface IBurger
{
    void Prepare();
}
//牛肉堡实现类
public class BeefBurger : IBurger
{
    public void Prepare()
    {
        //todo
    }
}
//素堡实现类
public class VeggieBurger : IBurger
{
    public void Prepare()
    {
        //todo
    }
}

此时的 orderBurger() 方法不再依赖于请求对象,这是因为我们作为用户不需要它,而是由餐厅的员工直接实例化并调用。

ini 复制代码
public static void main(string[] args)
{
    Restaurant beefRes = new BeefBurgerRestaurant();
    IBurger beefBurger = beefRes.orderBurger();

    Restaurant veggieRes = new VeggieBurgerRestaurant();
    IBurger veggieBurger = veggieRes.orderBurger();
}

餐厅员工需要的具体餐厅类实现,并且正确选择汉堡将返回给餐厅员工。最终使用 UML 类图表示如下。

到了这里,我们已经实现了工厂方法设计模式。如果你已经学会了如何使用它,那我们该在什么情况下使用该设计模式呢?

如果说,我们实现并不知道代码将使用的对象的确切类型和依赖项,那么很好的暗示我们一开始就应该引入工厂方法设计模式。

因为该工厂方法可以轻松扩建产品,构建代码独立于应用程序的其余部分。

更重要的是,我们将产品创建代码集中在程序中的一处时,我们可以遵循并应用开放-封闭原则和单一职责原则。

如何进一步升级?

好,在此基础之上我们引起思考:假如我们的疯狂汉堡餐厅蒸蒸日上,即将开分店招揽更多的生意,且汉堡产品口味相同,但是采用中式制作汉堡。

如果交付到应用程序的话,那我们该如何设计呢?

相关推荐
许野平20 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
58沈剑1 小时前
80后聊架构:架构设计中两个重要指标,延时与吞吐量(Latency vs Throughput) | 架构师之路...
架构
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod2 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。3 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man3 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*3 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu3 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s3 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子3 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算