C#设计模式之2:工厂方法模式
工厂模式包含三种,简单工厂模式,工厂方法模式,抽象工厂模式。这三种都是解决了一个问题,那就是对象的创建问题。他们的职责就是将对象的创建和对象的使用分离开来。
当我们创建对象的时候,总是会new一个对象,有错么?技术上,new没有错,毕竟是C#的基础部分,真正的犯人是我们的老朋友“改变”。以及他是如何影响new使用的。
针对接口编程,可以隔离掉以后系统可能发生的一大堆改变,为什么呢?如果代码是针对接口而写,那么通过多态,他可以与任何新类实现该接口,但是,当代码使用大量的具体类时,一旦加入新的具体类,就必须改变代码。违反了开闭原则了。
简单工厂
我们先来看一下要订购一个披萨的方法:
public static Pizza OrderPizza(string pizzaType)
{
Pizza pizza;
switch (pizzaType)
{
case "cheese":
pizza= new CheesePizza();
break;
case "ApplePie":
pizza= new ApplePiePizza();
break;
default:
pizza=new SomeOtherPizza();
break;
}
pizza.Prepare();
pizza.Beak();
pizza.Cut();
pizza.Box();
return pizza;
}
如果让我来实现一个订购披萨的系统,我肯定会这么写代码,因为这种的代码是能下意识的就写出来,几乎不用耗费我们的脑筋。但是,仔细看一下这段代码:这个方法的目的是要订购披萨,但是,订购披萨的代码中还要有关于生产pizza的知识,这个方法必须要知道所有的pizza,更加糟糕的是,如果将来要扩充pizza的种类或者删减销量不好的pizza,那么就会来修改这个订购披萨的代码了。看起来很糟糕,应该修改一下。我们第一步要做的一定是要将生产pizza的代码和订购披萨的代码分开来,因为你不应该让订购pizza的代码知道如何生产披萨的逻辑,应该将生产pizza的逻辑单独交给一个专门的类型来负责。这样,就可以应对将来的变化——如果要新增或者删除pizza,我们可以直接修改pizza的生产者而不影响到订购pizza这个逻辑。
而专门生产pizza的这个对象,我们可以叫他为工厂。看一下实现的代码:
public class SimpleFactory
{
public Pizza CreatePizza(string pizzaType)
{
switch (pizzaType)
{
case "cheese":
return new CheesePizza();
case "ApplePie":
return new ApplePiePizza();
default:
return new SomeOtherPizza();
}
}
}
public class PizzaStore
{
private SimpleFactory _factory;
public PizzaStore(SimpleFactory factory)
{
this._factory = factory;
}
public Pizza OrderPizza(string pizzaType)
{
var pizza = _factory.CreatePizza(pizzaType);
pizza.Prepare();
pizza.Beak();
pizza.Cut();
pizza.Box();
return pizza;
}
}
把创建对象的逻辑放到一个单独的类中的原因是它可以被多个客户端使用。并且把对象的创建和使用分离开来。但是这样的设计也存在很多缺陷,首先,SimpleFactory是一个具体的类,那么我们就必须针对实现编程,使得系统失去了这方面的弹性,二是如果增加新的产品我们还得修改simplefactory中的代码,这违反了开闭原则。上面这个模式也叫做简单工厂。他没有在GOF的模式中出现,它更像是一个编程的习惯。
可以看到PizzaStore依赖一个SImpleFactory,SimpleFactory依赖一个抽象的pizza。正常情况下,要做解耦,就要做控制反转,控制反转的核心思想是上层结构和底层结构都不依赖实现,而是依赖抽象。
工厂方法模式
现在来做一些改良,直接上代码:
public abstract class FactoryPizzaStore
{
public Pizza OrderPizza(string pizzaType)
{
Pizza pizza = CreatePizza(pizzaType);
pizza .Prepare();
pizza.Beak();
pizza.Cut();
pizza.Box();
return pizza;
}
public abstract Pizza CreatePizza(string pizzaType);
}
解释一下,在OrderPizza方法中,我们做了很多事情,包括调用CreatePizza方法产生一个想要的pizza,然后对pizza进行一系列的操作,关键在于,Pizza被定义为一个抽象类,也就是说在OrderPizza方法中我们并不关心这个Pizza在运行时到底是一个什么类型,换句话说,这就是解耦。解除了PizzaStore和Pizza之间的耦合。
那么现在,原本是通过一个对象负责所有具体类的实例化,现在通过对PizzaStore做的一些改变,变成由一群子类来对具体类的实例化:FactoryPizzaStore的子类负责实现CreatePizza;
更进一步的:工厂方法用来处理对象的创建,并把这种创建对象的实现延迟到子类中去实现,这样,客户代码中关于基类的代码就和子类对象创建代码解耦了。
由于Pizza还是一个抽象类,当我们创建一个FactoryPizzaStore的子类的时候,也应该实现一个具体的继承自Pizza的子类,如果只创建一个FactoryPizzaStore的子类,而没有相对应的Pizza的子类,那么我们的披萨店卖什么呢?
public abstract class Pizza
{
public string Name { get; set; }
public string Dough { get; set; }
public string Sauce { get; set; }
public List Toopings { get; set; }
public void Prepare()
{
Console.WriteLine($"Preparing {Name}");
Console.WriteLine("Tossing dough");
Console.WriteLine("Adding sauce");
Console.WriteLine("Adding Toppings..");
foreach (string item in Toopings)
{
Console.WriteLine($" {item}");
}
}
public void Cut()
{
Console.WriteLine("Cutting the pizza..");
}
public void Bake()
{
Console.WriteLine("Baking the pizza");
}
public void Box()
{
Console.WriteLine("Boxing the pizza");
}
}
在创建一些Pizza的子类后,就可以开始测试了:
static void Main(string[] args)
{
FactoryPizzaStore store=new NyPizzaStore();
var pizza = store.OrderPizza("Cheese");
Console.WriteLine(pizza.Name);
Console.ReadKey();
}
认识工厂方法模式:所有的工厂方法模式都是用来创建对象的。工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建过程进行封装的目的。工厂方法模式有如下几个角色:
1、创建者:就是上面定义的FactoryPizzaStore。它是一个抽象类,它定义了创建对象的方法,这个方法是一个抽象方法,具体的创建对象的过程由其子类来实现。这个抽象类通常会依赖一个抽象的产品(Product,工厂方法模式中的另一个角色)类。而这些抽象产品由子类制造,创建者并不关心制造的是哪种产品。
2、产品类:就是上面定义的Pizza,它也是一个抽象类,具体的产品由其子类来实现。
我们已经看到,将一个OrderPizza方法和一个工厂方法联合起来(就是FactoryPizzaStore里面的那两个方法),就形成了一个框架。除此之外,工厂方法吧生产知识封装到各个创建者子类,这也可以形成一个框架。
下面给出工厂方法的定义:定义了一个创建对象的接口,但由子类决定要具体创建哪一个。工厂方法让这种创建对象的过程推迟到子类。
简单工厂和工厂方法之间的差异:简单工厂和工厂方法模式看起来很相似,尤其是简单工厂和工厂方法中的具体工厂。简单工厂吧全部的事情,在一个地方都做完了。然而工厂方法创建的是一个框架,让子类决定如何实现。比方说,在工厂方法中,OrderPizza方法创建了一般的框架,以便创建披萨,OrderPizza方法依赖工厂方法创建具体的类,并制造出实际的披萨。可通过继承工厂,决定制造的实际产品到底是什么。简单工厂不具备这方面的弹性。
封装变化
将创建对象的代码封装起来,实例化具体的工厂类,达到了封装实例化目的。将创建对象的代码封装到一个地方,也可以达到复用的目的,并且更方便以后的维护。这也意味着客户在实例化对象时,只会依赖接口,不会依赖具体的实现。帮助我们更好的达到针对接口编程而不是实现,让代码更有弹性,以应对未来的扩展。
转载自: