我们将探讨 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 类图表示如下。
到了这里,我们已经实现了工厂方法设计模式。如果你已经学会了如何使用它,那我们该在什么情况下使用该设计模式呢?
如果说,我们实现并不知道代码将使用的对象的确切类型和依赖项,那么很好的暗示我们一开始就应该引入工厂方法设计模式。
因为该工厂方法可以轻松扩建产品,构建代码独立于应用程序的其余部分。
更重要的是,我们将产品创建代码集中在程序中的一处时,我们可以遵循并应用开放-封闭原则和单一职责原则。
如何进一步升级?
好,在此基础之上我们引起思考:假如我们的疯狂汉堡餐厅蒸蒸日上,即将开分店招揽更多的生意,且汉堡产品口味相同,但是采用中式制作汉堡。
如果交付到应用程序的话,那我们该如何设计呢?