zoukankan      html  css  js  c++  java
  • 【设计模式】工厂模式

    工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

    简单工厂也称为静态工厂,一般可以用静态方法(类方法)来获取对象。

    基本介绍

    • 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

    • 主要解决:主要解决接口选择的问题。

    • 何时使用:我们明确地计划不同条件下创建不同实例时。

    • 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

    • 关键代码:创建过程在其子类执行。

    • 应用实例:
      • 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
      • Hibernate 换数据库只需换方言和驱动就可以。
    • 优点:
      • 一个调用者想创建一个对象,只要知道其名称就可以了。
      • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
      • 屏蔽产品的具体实现,调用者只关心产品的接口。
    • 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

    • 使用场景:
      • 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
      • 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
      • 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

    注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

    概括

    工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

    1. 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
    2. 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
    3. 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式。

    一句话概括工厂模式

    • 简单工厂:一个工厂类,一个产品抽象类。
    • 工厂方法:多个工厂类,一个产品抽象类。
    • 抽象工厂:多个工厂类,多个产品抽象类。

    生活中的工厂模式

    • 简单工厂类:一个麦当劳店,可以生产多种汉堡。
    • 工厂方法类:一个麦当劳店,可以生产多种汉堡。一个肯德基店,也可以生产多种汉堡。
    • 抽象工厂类:百胜餐饮集团下有肯德基和百事公司,肯德基生产汉堡,百事公司生成百事可乐。

    其实工厂模式就如它的名字一般,是个密不透风的工厂。我们只需要把我们需要的清单交给它,他就能做出相应的物品给我们,而不需要我们亲力亲为,亲手去制造物品。如 我们需要汽车 car,我们不需要自己new Car()创建一个汽车对象,而是只需要将 "car"(枚举字符串)这种清单交给工厂就可以了。


    类图:

    角色分析:

    1. Product:是产品接口
    2. Product1、2、3:是具体产品
    3. Factory:是工厂类,里面有静态方法 createProduct(),负责创建不同的产品。

    我的理解

    其实工厂模式就如它的名字一般,是个密不透风的工厂。我们只需要把我们需要的清单交给它,他就能做出相应的物品给我们,而不需要我们亲力亲为,亲手去制造物品。如 我们需要汽车 car,我们不需要自己new Car()创建一个汽车对象,而是只需要将 "car"(枚举字符串)这种清单交给工厂就可以了。

    比如说有个饭馆,会做上百种菜,饭馆里面的老板,还有老顾客,他们当然都知道这个饭馆到底卖哪些菜。但是来了一个新来的顾客 他一时间当然不知道这个饭馆到底卖哪些菜,这样的话,我们就需要一个菜单来将这些菜的创建给聚合起来,方便管理与查找。不然的话,上百种菜零零散散 很难管理。
    不然的话,新顾客需要点菜就必须得依赖每一种菜他才能点,所以我们需要简单工厂来帮我们管理、解耦。

    比如说我们这个系统需要一个外卖系统和一个现场购买系统,这两个系统都需要创建披萨,如果重复创建会带来很多冗余,所以需要工厂。

    工厂也可以充当一个缓冲层来用,将来如果有什么变化的话,工厂可以起到缓冲的作用,避免因为变化而使系统出现错误,
    因为缓冲,底层代码的修改可以不影响到顶层代码的功能。

    这是一个树形的创建型设计模式,和包的作用一样,希望用到什么功能就去哪个功能的工厂去找。

    个人感觉,这些工厂,抽象工厂,都是为了让类的声明、对象的构造更有层次感,就好像树形图一样,以抽象工厂为根,以不同工厂为枝叶,以工厂的产品为叶子。不然一下这个类,一下那个类,会很凌乱。

    应用实例

    看一个具体的需求
    看一个披萨的项目:要便于披萨种类的扩展,要便于维护

    1. 披萨的种类很多(比如 GreekPizz、CheesePizz 等)
    2. 披萨的制作有 prepare,bake, cut, box
    3. 完成披萨店订购功能。

    使用传统方式

    1. 思路分析(类图)

    2. 编写 OrderPizza.java 去订购需要的各种 Pizza
    public class OrderPizza {
        public static void main(String[] args) {
    
            //我们需要什么披萨就创建什么披萨
            //创建芝士披萨
            Pizza pizza = new CheesePizza();
            //Pizza pizza = new PepperPizza();
            //Pizza pizza = new GreekPizza();
        }
    }

    传统的方式的优缺点:

    1. 优点:比较好理解,简单易操作。
    2. 缺点:如果我们有几十种不同的披萨,我们程序员就要硬生生记住这些不同的披萨类名,还不能出错。
      若服务器中披萨的类名一改,我们客户端使用的披萨类名也要改。

    改进的思路分析:

    • 分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意味着,也需要修改,而创建 Pizza
      的代码,往往有多处。
    • 思路:把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改该类就可,其它有创建到 Pizza
      对象的代码就不需要修改了。 => 简单工厂模式

    使用简单工厂模式

    简单工厂模式的设计方案: 定义一个可以实例化 Pizaa 对象的类,封装创建对象的代码。
    简单工厂模式 也叫 静态工厂模式,可以将方法静态化为类方法,直接用类调用。

    类图:

    代码示例:

    package com.atguigu.factory.simplefactory.pizzastore.order;
    
    import com.atguigu.factory.simplefactory.pizzastore.pizza.CheesePizza;
    import com.atguigu.factory.simplefactory.pizzastore.pizza.GreekPizza;
    import com.atguigu.factory.simplefactory.pizzastore.pizza.PepperPizza;
    import com.atguigu.factory.simplefactory.pizzastore.pizza.Pizza;
    
    
    //简单工厂类
    public class SimpleFactory {
    
        //更加 orderType 返回对应的 Pizza 对象
        public Pizza createPizza(String orderType) {
    
            Pizza pizza = null;
    
            System.out.println("使用简单工厂模式");
            if (orderType.equals("greek")) {
                pizza = new GreekPizza();
                pizza.setName(" 希腊披萨 ");
            } else if (orderType.equals("cheese")) {
                pizza = new CheesePizza();
                pizza.setName(" 奶酪披萨 ");
            } else if (orderType.equals("pepper")) {
                pizza = new PepperPizza();
                pizza.setName("胡椒披萨");
            }
    
            return pizza;
        }
    
        //简单工厂模式 也叫 静态工厂模式
        public static Pizza createPizza2(String orderType) {
    
            Pizza pizza = null;
    
            System.out.println("使用简单工厂模式 2");
            if (orderType.equals("greek")) {
                pizza = new GreekPizza();
                pizza.setName(" 希腊披萨 ");
            } else if (orderType.equals("cheese")) {
                pizza = new CheesePizza();
                pizza.setName(" 奶酪披萨 ");
            } else if (orderType.equals("pepper")) {
                pizza = new PepperPizza();
                pizza.setName("胡椒披萨");
            }
    
            return pizza;
        }
    }

    优化:
    我看到网上有很多用反射来使披萨不用去一个一个 if-else。

    其实使用反射是一种不错的办法,但反射也是从类名反射而不能从类反射!

    先看一下工厂模式是用来干什么的——属于创建模式,解决子类创建问题的。换句话来说,调用者并不知道运行时真正的类名,只知道从“Circle"可以创建出一个shape接口的类,至于类的名称是否叫'Circle",调用者并不知情。所以真正的对工厂进行扩展的方式(防止程序员调用出错)可以考虑使用一个枚举类(防止传入参数时,把circle拼写错误)。

    如果调用者参肯定类型是Circle的话,那么其工厂没有存在的意义了!

    比如 IShape shape = new Circle();这样不是更好?也就是说调用者有了Circle这个知识是可以直接调用的,根据DP(迪米特法则)其实调用者并不知道有一个Circle类的存在,他只需要知道这个IShape接口可以计算圆面积,而不需要知道;圆这个类到底是什么类名——他只知道给定一个”circle"字符串的参数,IShape接口可以自动计算圆的面积就可以了!

    其实在.net类库中存在这个模式的的一个典型的。但他引入的另一个概念“可插入编程协议”。

    那个就是WebRequest req = WebRequest.Create("http://ccc......");可以自动创建一个HttpWebRequest的对象,当然,如果你给定的是一个ftp地址,他会自动创建一个FtpWebRequest对象。工厂模式中着重介绍的是这种通过某个特定的参数,让你一个接口去干对应不同的事而已!而不是调用者知道了类!

    比如如果圆的那个类名叫"CircleShape“呢?不管是反射还是泛型都干扰了你们具体类的生成!其实这个要说明的问题就是这个,调用者(clinet)只知道IShape的存在,在创建时给IShape一个参数"Circle",它可以计算圆的面积之类的工作,但是为什么会执行这些工作,根据迪米特法则,client是不用知道的。

    我想问一下那些写笔记的哥们,如果你们知道了泛型,那么为什么不直接使用呢?干吗还需要经过工厂这个类呢?不觉得多余了吗?

    如果,我只是说如果,如果所有从IShape继承的类都是Internal类型的呢?而client肯定不会与IShape一个空间!这时,你会了现你根本无法拿到这个类名!

    Create时使用注册机制是一种简单的办法,比如使用一个枚举类,把功能总结到一处。而反射也是一种最简单的办法,调用者输入的名称恰是类名称或某种规则时使用,比如调用者输入的是Circle,而类恰是CircleShape,那么可以通过输入+”Shape"字符串形成新的类名,然后从字符串将运行类反射出来!

    工厂的创建行为,就这些作用,还被你们用反射或泛型转嫁给了调用者(clinet),那么,这种情况下,要工厂类何用?!

    使用枚举类优化

    优化一

    增加枚举类

    public enum PizzaType {
        GREEKPIZZA,
        CHEESEPIZZA,
        PEPPERPIZZA
    }

    修改工厂类的工厂方法,个人建议工厂方法应该是静态方法或者采用单例模式:

    public class SimpleFactory {
        public static Pizza createPizza(PizzaType type) {
            switch(type) {
                case GREEKPIZZA:
                    return new GreekPizza();
                case CHEESEPIZZA:
                    return new CheesePizza();
                case PEPPERPIZZA:
                    return new PepperPizza();
                default:
                    throw new UnknownTypeException();
            }
        }
    }

    最后是使用示例:

    public class FactoryPatternDemo {
        public static void main(String[] args) {
            //获取 GreekPizza 的对象
            Pizza pizza1 = SimpleFactory.createPizza(PizzaType.GREEKPIZZA);
        }
    }

    优化二

    public enum Factory {
        //使用构造方法,构造枚举类型对象
        GREEKPIZZA(new GreekPizza(),"GREEKPIZZA"),
        CHEESEPIZZA(new CheesePizza(),"CHEESEPIZZA"),
        PEPPERPIZZA(new PepperPizza(),"PEPPERPIZZA");
        
        // 成员变量,上面的构造方法就取决于这些成员变量
        private Pizza pizza;  
        private String name;  
        
        // 普通方法  
        public static Shape getPizza(String name) {  
            for (Factory c : Factory.values()) {  
                if (c.name == name) {  
                    return c.pizza;  
                }  
            }  
            return null;  
        } 
    
        // 构造方法  
        private Factory(Pizza pizza, String name) {  
            this.pizza = pizza;  
            this.name = name;  
        } 
        public String getName() {
            return name;
        }
        public Pizza getPizza() {
            return pizza;
        }
        public void setPizza(Pizza pizza) {
            this.pizza = pizza;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    public class FactoryPatternDemo {
        public static void main(String[] args) {
            //获取 GreekPizza 的对象
            /*使用枚举类*/
            Pizza pizza1 = Factory.getPizza("GREEKPIZZA");
            Pizza pizza2 = Factory.getPizza("CHEESEPIZZA");
            Pizza pizza3 = Factory.getPizza("PEPPERPIZZA");
        }
    }

    工厂模式小结

    1. 工厂模式的意义:
      将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
    2. 三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)
    3. 设计模式的依赖抽象原则
      • 创建对象实例时,不要直接 new 类, 而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
      • 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
      • 不要覆盖基类中已经实现的方法。
  • 相关阅读:
    VS Code中编写C
    Latex
    JAVA学习-----容器和数据结构
    Markdown2最最基本操作说明(未完待续)
    [lua] table.sort(_table, comp)使用要点
    [coco2d]pageView:addPage时,page无法对齐
    [cocos2d]修改富文本文本和高度
    [cocos2d]格式化获取当前layer的控件名
    [c++]牛客刷题记录2.18
    [c++]STL学习
  • 原文地址:https://www.cnblogs.com/blknemo/p/13257194.html
Copyright © 2011-2022 走看看