在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
前面分析了简单工厂模式和工厂方法模式,接着来看一下抽象工厂模式,他与工厂方法模式有一些相似的地方,也有不同的地方。 先来看一个不用工厂方法模式实现的订购披萨的代码:
对象依赖的问题:当你直接实例化一个对象时,就是在依赖他的具体类。接着上面的例子,如果在一个PizzaStore里面直接创建很多对象时,他们的依赖关系是这样的: 这里引出一个概念:依赖倒置。很清楚的代码里减少具体类的依赖是一件好事。依赖倒置的定义是:要依赖抽象,不要依赖实现。这个原则说说明了:不能让高层组建依赖底层组件,而且,不管是高层组件还是底层组件,他们都要依赖抽象。所谓的高层组件,是由其它底层组件定义其行为的类。例如。PizzaStore是个高层组件,因为他的行为是由比萨定义的:PizzaStore创建所有不同的比萨对象,准备、烘烤、切片、装盒;而披萨本身属于低层组件。 原则的应用上图展示的问题在于,它依赖每个比萨类型,因为它是在自己的orderPizza方法中,实例化这些具体类型的。虽然我们已经创建了一个抽象,就是Pizza,但是我们仍然在代码中,实际的创建了具体的pizza,所以,这个抽象没有什么影响力。如何在OrderPizza方法中,将这些实例化对象的代码独立出来,我们都知道,工厂方法刚好能排上用场。所以,应用工厂方法之后。类图看起来就像这: 在应用了工厂方法模式之后(参考工厂方法模式),你将注意到,高层组件和底层组件都依赖了抽象(Pizza),要遵循依赖倒置原则,工厂方法并非唯一的技巧,但是,确实最有威力的技巧之一。 如何更好的遵循依赖倒置原则变量不可以持有具体类的引用。如果使用new,就会持有具体类的引用,就会对具体的类产生依赖。可以改用工厂来避免这种做法。 不要让类派生自具体的类。如果派生自具体类,那你就会依赖具体类。请派生自一个抽象。 不要覆盖基类中已经实现的方法。基类中已实现的方法,应该由所有的子类共享。 上面三点只是说尽量去做到,并不是一定要做到。 这个模式涉及的类有点儿多。。。所以决定线上UML类图,然后根据类图来一步一步的说明: 实际上单就拿抽象工厂来说,不是太难说明这个模式的含义,但是抽象工厂一般是和工厂方法模式配合使用的: 看图:首先,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就是一个工厂方法。 所有工厂模式都是通过减少应用程序和类之间的依赖促进耦合。 依赖倒置原则,指导我们要尽量避免依赖具体,而要依赖抽象。 工厂是很有威力的技巧,帮我们尽量针对接口编程,而不是针对具体。 |
请发表评论