• [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式


     系列文章

    [Head First设计模式]山西面馆中的设计模式——装饰者模式

    [Head First设计模式]山西面馆中的设计模式——观察者模式

    [Head First设计模式]山西面馆中的设计模式——建造者模式

    引言

    今天是冬至,去饺子馆吃饺子,看他们店里面的水饺种类挺多,在等待中,在想是不是可以用设计模式模拟一下,生产饺子的过程,正好最近也在看工厂模式,也就现学现卖了。当然,实现的方式很多,只是一个例子而已。祝大家冬至,多多吃水饺.....

    对象创建的问题?

    我们应该面向接口编程而不是面向实现编程,因为面向实现编程会使得我们的设计更脆弱,缺乏灵活性。但是我们每次使用new时,是不是正在违背这一设计原则呢?

    当我们拥有一组相关的具体类时,是不是常常被迫写出类似下面的代码?

     1 Duck duck;
     2 
     3 if(picnic){
     4 
     5 duck=new MallardDuck();
     6 
     7 }else if(hunting){
     8 
     9 duck=new DecogDuck();
    10 
    11 }else if(inBathTub){
    12 
    13 duck=new RubberDuck();
    14 
    15 }

    (以上为伪代码,只为说明问题)

    向上面的实例化过程,知道运行时我们才知道需要实例化哪个类。

    这样做的后果是如果应用要做变化或扩展,往往要修改这段代码。这使得维护困难,并容易引入错误。

    问题在哪儿?

    出现上面那种问题,是不是new的问题呢?

    从技术上来说,new并没有任何问题。new只是面向对象语言的最基本部分,真正的问题在于“变化”。

    如果对接口编程,我们可以实现与许多“变化”的隔离,因为通过多态机制,我们的代码对于实现接口的新类依然适用。但是使用具体类麻烦就来了,因为增加新的具体类时相应的代码可能就必须修改?

    怎么办?

    面向对象的设计原则:识别变化的部分,并将与不变化的部分相分离。

    书中Pizza店案例分析

    PizzaStore类中的一段代码-订做pizza

     

    修改后的代码

    由于市场竞争,其他pizza店推出了新产品,我们也得增加!例如VeggiePizza。 GreekPizza最近不受欢迎,把它从菜单中取消。

    于是。。。

     

    分析:变与不变的部分

     

    分离

    我们将专管制作pizza的对象叫做Pizza工厂

    Pizza工厂---SimplePizzaFactory

    思考一下?

          这看来好像我们只是把问题从一个对象推给了另一个对象!这样做有什么好处?

          SimplePizzaFactory可以有许多个客户,这样,当实现改变时我们只需要修改SimplePizzaFactory,而不需修改众多的客户。 提高了聚合度,PizzaStore的职责是使用pizza对象, SimplePizzaFactory的职责是决定创建什么样的pizza对象。

    用工厂重写PizzaStore类

     1 public class PizzaStore {
     2    SimplePizzaFactory factory;
     3    public PizzaStore(SimplePizzaFactory factory) {
     4        this.factory = factory;
     5    }
     6    public Pizza orderPizza(String type) {
     7         Pizza pizza;
     8         pizza=factory.createPizza(type);
     9         pizza.prepare();
    10         pizza.bake();
    11         pizza.cut();
    12         pizza.box();
    13         return pizza;
    14    }
    15    //other methods here
    16 }

    简单工厂模式

    饺子馆中的简单工厂实现

     水饺要实现的接口

     1   /// <summary>
     2     /// 水饺要实现的接口
     3     /// </summary>
     4     public interface IDumpling
     5     {
     6         string DumplingName { get; }
     7         /// <summary>
     8         /// 水饺的准备阶段方法
     9         /// </summary>
    10         void Prepare();
    11         /// <summary>
    12         ///13         /// </summary>
    14         void Boild();
    15         /// <summary>
    16         /// 展示订单
    17         /// </summary>
    18         void Show();
    19     }

    具体的水饺类

     1  /// <summary>
     2     /// 猪肉大葱水饺类
     3     /// </summary>
     4     public class PorkAndScallionDumpling:IDumpling
     5     {
     6 
     7         public string DumplingName
     8         {
     9             get { return "猪肉大葱水饺"; }
    10         }
    11 
    12         public void Prepare()
    13         {
    14             Console.WriteLine("准备猪肉茴香水饺25个");
    15         }
    16 
    17         public void Boild()
    18         {
    19             Console.WriteLine("正在煮......请稍等.....");
    20         }
    21 
    22         public void Show()
    23         {
    24             Console.WriteLine("您的{0},准备好了。", this.DumplingName);
    25         }
    26     }
     1 /// <summary>
     2     /// 猪肉茴香水饺类
     3     /// </summary>
     4     public class PorkAndFennelDumpling : IDumpling
     5     {
     6         public string DumplingName
     7         {
     8             get { return "猪肉茴香水饺"; }
     9         }
    10 
    11         public void Prepare()
    12         {
    13             Console.WriteLine("准备猪肉茴香水饺25个");
    14         }
    15 
    16         public void Boild()
    17         {
    18             Console.WriteLine("正在煮......请稍等.....");
    19         }
    20 
    21         public void Show()
    22         {
    23             Console.WriteLine("您的{0},准备好了。", this.DumplingName);
    24         }
    25     }

    工厂类

     1   /// <summary>
     2     /// 水饺的生产工厂
     3     /// </summary>
     4     public static class SimpleDumplingFactory
     5     {
     6         public static IDumpling CreateDumpling(string dumplingName)
     7         {
     8             IDumpling dumpling = null;
     9             switch (dumplingName)
    10             {
    11                 case "猪肉大葱":
    12                     dumpling = new PorkAndScallionDumpling();
    13                     break;
    14                 case "猪肉茴香":
    15                     dumpling = new PorkAndFennelDumpling();
    16                     break;
    17             }
    18             return dumpling;
    19         }
    20     }

    控制台代码

     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             IDumpling dumpling = SimpleDumplingFactory.CreateDumpling("猪肉茴香");
     6             dumpling.Prepare();
     7             dumpling.Boild();
     8             dumpling.Show();
     9             Console.Read();
    10         }
    11     }

    结果

    授权pizza店

    我们的pizza店非常成功,许多人都想开设我们的授权加盟店。为保证质量,我们希望他们使用我们经过时间考验的代码。
    但是,不同地区的加盟pizza店可能希望供应不同口味的pizza。怎么解决这个问题呢?

    解决方法之一:建立不同的工厂

     1 //建立不同的工厂:如NYPizzaFactory、 ChicagoPizzaFactory、 CaliforniaPizzaFactory,在PizzaStore中包含相应工厂的实例。其代码类似于:
     2 //该pizza店提供纽约风味的pizza
     3 NYPizzaFactory nyFactory=new NYPizzaFactory();//建立一个生产纽约风味pizza的工厂
     4 PizzaStore nyStore=new PizzaStore(nyFactory);//建立一个pizza店,引用纽约风味pizza的工厂
     5 nyStore.orderPizza(“Veggie”);//生产的是纽约风味的pizza
     6 
     7 //该pizza店提供芝加哥风味的pizza
     8 ChicagoPizzaFactory chicagoFactory=new ChicagoPizzaFactory();
     9 PizzaStore chicagoStore=new PizzaStore(chicagoFactory);
    10 chicagoStore.orderPizza(“Veggie”);

    抽象工厂模式

    这么多工厂,可以再增加抽象层
    另一种解决方法-工厂方法模式

    思路:改写的PizzaStore,将createPizza()方法放回到PizzaStore,但是声明为抽象方法,然后,为每一种地方风味创建一个PizzaStore的子类。

    改造后的PizzaStore的代码

     1 public abstract class PizzaStore {
     2    
     3    public Pizza orderPizza(String type) {
     4       Pizza pizza = createPizza(type);//在PizzaStore内调用自身的一个方法来制造pizza,而不是使用一个factory对象
     5 
     6       pizza.prepare();
     7       pizza.bake();
     8       pizza.cut();
     9       pizza.box();
    10       return pizza;
    11      }
    12     abstract Pizza createPizza(String type);//factory对象成了这里的一个抽象方法
    13 
    14 }

    下面我们需要PizzaStore的各种子类(对应不同的地区风味)

    让子类做决定

     

     声明工厂方法

    abstract Pizza createPizza(String type);
    abstract Product factoryMethod(String type);

    工厂方法是抽象的,在一个超类中定义。必须由子类来实现。
    工厂方法返回一个产品,该产品通常在其所在类的方法中定义。(如orderPizza())
    工厂方法通常提供参数,用以选择一个产品的不同品种。
    工厂方法将客户(超类中的方法,如PizzaStore中的orderPizza())与具体的产品相隔离。

     工厂方法怎么工作?

    假定张三喜欢纽约风味的pizza,李四喜欢芝加哥风味的pizza。
    需要相应Pizza店的实例
    调用orderPizza()订购想要的pizza品种
    createPizza()被调用,并返回pizza到orderPizza()方法。
    尽管不知道是什么pizza,但orderPizza()仍知道对它进行后续处理。

    工厂方法模式中的类

    创建者类 The Creator classes

    产品类 The Product classes

    工厂方法模式的定义

    定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式让一个类的实例化延迟到其子类。

    工厂方法模式的结构

    总结:Factory Method模式

    意图
    定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式让一个类的实例化延迟到其子类。
    别名
    虚拟构造器

    Factory Method—参与者

    Product(document)定义工厂方法所创建对象的接口。
    ConcreteProduct(mydocument)实现product接口。
    Creator(application)声明工厂方法,可以调用工厂方法以创建一个product对象
    ConcreteCreator (MyApplication)重新定义工厂方法,以返回一个ConcreteProduct实例

    (篇幅有点长,关于工厂方法模式的实例就不再列举了,感兴趣的可以自己实现一下)

     参考书:

    《First Head 设计模式》

     

  • 相关阅读:
    k8s 权限控制初探
    golang gRPC 入门
    gpushare-scheduler-extender 升级依赖引发关于 golang dep 工具的思考
    admission webhook 初探(编译篇)
    以 gpushare-device-plugin 为例,探究 Resource yaml 配置
    编译 gpushare-device-plugin
    浅谈 docker 挂载 GPU 原理
    CCF计算机职业资格认证考试题解
    优达学城机器学习工程师纳米学位项目介绍
    IEEEXtreme 极限编程大赛题解
  • 原文地址:https://www.cnblogs.com/wolf-sun/p/3485805.html
Copyright © 2020-2023  润新知