C#设计模式之6:抽象工厂模式

时间:2023-09-22 10:44:38

前面分析了简单工厂模式和工厂方法模式,接着来看一下抽象工厂模式,他与工厂方法模式有一些相似的地方,也有不同的地方。

先来看一个不用工厂方法模式实现的订购披萨的代码:

C#设计模式之6:抽象工厂模式

对象依赖的问题:当你直接实例化一个对象时,就是在依赖他的具体类。接着上面的例子,如果在一个PizzaStore里面直接创建很多对象时,他们的依赖关系是这样的:

C#设计模式之6:抽象工厂模式

这里引出一个概念:依赖倒置。很清楚的代码里减少具体类的依赖是一件好事。依赖倒置的定义是:要依赖抽象,不要依赖实现。这个原则说说明了:不能让高层组建依赖底层组件,而且,不管是高层组件还是底层组件,他们都要依赖抽象。所谓的高层组件,是由其它底层组件定义其行为的类。例如。PizzaStore是个高层组件,因为他的行为是由比萨定义的:PizzaStore创建所有不同的比萨对象,准备、烘烤、切片、装盒;而披萨本身属于低层组件。

原则的应用

上图展示的问题在于,它依赖每个比萨类型,因为它是在自己的orderPizza方法中,实例化这些具体类型的。虽然我们已经创建了一个抽象,就是Pizza,但是我们仍然在代码中,实际的创建了具体的pizza,所以,这个抽象没有什么影响力。如何在OrderPizza方法中,将这些实例化对象的代码独立出来,我们都知道,工厂方法刚好能排上用场。所以,应用工厂方法之后。类图看起来就像这:

C#设计模式之6:抽象工厂模式

在应用了工厂方法模式之后(参考工厂方法模式),你将注意到,高层组件和底层组件都依赖了抽象(Pizza),要遵循依赖倒置原则,工厂方法并非唯一的技巧,但是,确实最有威力的技巧之一。

如何更好的遵循依赖倒置原则

变量不可以持有具体类的引用。如果使用new,就会持有具体类的引用,就会对具体的类产生依赖。可以改用工厂来避免这种做法。

不要让类派生自具体的类。如果派生自具体类,那你就会依赖具体类。请派生自一个抽象。

不要覆盖基类中已经实现的方法。基类中已实现的方法,应该由所有的子类共享。

上面三点只是说尽量去做到,并不是一定要做到。

抽象工厂模式

这个模式涉及的类有点儿多。。。所以决定线上UML类图,然后根据类图来一步一步的说明:

C#设计模式之6:抽象工厂模式

实际上单就拿抽象工厂来说,不是太难说明这个模式的含义,但是抽象工厂一般是和工厂方法模式配合使用的:

C#设计模式之6:抽象工厂模式

看图:首先,NYPizzaStore还是继承自PizzaStore

public abstract   class PizzaStore
{
public Pizza OrderPizza(string pizzaType)
{
Pizza pizza = CreatePizza(pizzaType);
pizza.Prepare();
pizza.Bake();
pizza.Cut();
pizza.Box();
return pizza;
}
public abstract Pizza CreatePizza(string pizzaType);
}

PizzaStroe依赖一个抽象的Pizza,而NYPizzaStore继承了PizzaStore,覆写了CreatePIzza:

 public class NyPizzaStore:PizzaStore
{
public override Pizza CreatePizza(string pizzaType)
{
Pizza pizza=null;
var indigredientFactory=new NyPizzaIngredientFactory();
if (pizzaType=="Cheese")
{
pizza = new CheesePizza(indigredientFactory);
}
else if (pizzaType=="Clam")
{
pizza=new ClamPizza(indigredientFactory);
}
return pizza;
}
}

实现的这个NYPizzaStore的耦合比较高,因为他在俩面new了三个对象:一个抽象工厂NyPizzaIngredientFactory,两个具体的披萨CheesePizza和ClamPizza。但是因为这个部分应该是不易变的部分,以后也不会进行修改了(如果不是这样的话那这个类还得继续抽象不是么?)此外,NYPizzaStore还针对一个Pizza抽象进行了编程,Pizza的代码如下:

public abstract class Pizza
{
public string Name { get; set; }
public Dough Dough { get; set; }
public Sauce Sauce { get; set; }
public Veggie[] Veggies { get; set; }
public Repperoni Repperoni { get; set; }
public Clams Clams { get; set; }
public Cheese Cheese { get; set; }
public abstract void Prepare(); public void Bake()
{
Console.WriteLine("bake...");
} public void Cut()
{
Console.WriteLine("Cut...");
} public void Box()
{
Console.WriteLine("Box...");
} public override string ToString()
{
return this.Name;
}
}

Pizza里面依赖了很多的抽象,也就是说Pizza是针对抽象在编程,具体是一个什么披萨,应该交给Pizza的子类来进行详细的描述,此外,看到Pizza里面还有一个抽象的Prepare方法,由此判断出来Pizza类也实现了工厂方法模式。看一下Pizza的具体实现类(其中一个):

 public class CheesePizza : Pizza
{
private IPizzaIngredientFactory _factory; public CheesePizza(IPizzaIngredientFactory factory)
{
_factory = factory;
}
public override void Prepare()
{
Console.WriteLine($"Preparing {Name}");
Dough = _factory.CreateDough();
Sauce = _factory.CreateSauce();
Cheese = _factory.CreateCheese();
}
}

CheesePizaa针对一个抽象的IPizzaIngredientFactory进行编程,这种利用组合的方式避免了继承带来的静态的编译所造成的不便,使得代码可以在运行时体现出更灵活和可扩展的特性。IPizzaIngredientFactory是一个抽象工厂的接口,抽象工厂又叫做原料工厂,就是说生产产品所需要的各种原料都可以从抽象工厂来获取:

 public class NyPizzaIngredientFactory : IPizzaIngredientFactory
{
public Dough CreateDough()
{
return new ThinCruseDough();
} public Sauce CreateSauce()
{
return new MarinaraSauce();
} public Cheese CreateCheese()
{
return new ReggianoChesse();
} public Veggie[] CreateVeggies()
{
return new Veggie[]
{
new Garlic(), new Mushroom(), new Onion(), new RedPepper(),
};
} public Repperoni CreateRepperoni()
{
return new SlicedRepperoni();
} public Clams CreateClams()
{
return new FreshClams();
}
}

下面给出抽象工厂的定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。在这个示例中,这个接口指的就是IPizzaInfredientFactory。

最重要的结论是,抽象工厂并不是单独使用的,而是在工厂方法模式中进行扩展使用的。有的时候,我们要创建一些相关联的或依赖的一族对象,这个时候可以把创建这些对象用抽象工厂来实现,而不是用工厂方法模式来实现,如果用工厂方法模式来实现,一个抽象产品对应一个抽象工厂,那样的情况叫做“类型爆炸”。

最最重要的是:工厂方法模式遵循控制反转的设计原则,并在此基础上定义了一个框架,在框架中,如果又必要的话,将抽象工厂的逻辑放到这个框架中的适当位置上。所以,工厂方法模式和抽象工厂模式一般是配合使用的。抽象工厂的作用就是防止"类型爆炸”。

要点:

所有工厂都是用来封装对象的创建。

简单工厂提供给我们更多的是一种想法,让我们萌生了将系统做一些分隔的想法,比如将对象的使用和实现进行解耦,等等。

工厂方法使用的是继承和多态,吧对象的创建委托给子类。

抽象工厂使用的是对象的组合。对象的创建被实现在工厂接口所暴露的方法中来,比如Pizza类就实现工厂方法模式,里面的Prepare就是一个工厂方法。

所有工厂模式都是通过减少应用程序和类之间的依赖促进耦合。

依赖倒置原则,指导我们要尽量避免依赖具体,而要依赖抽象。

工厂是很有威力的技巧,帮我们尽量针对接口编程,而不是针对具体。