2023-03-27  阅读(3)
原文作者:ClarenceZero 原文地址:https://blog.csdn.net/ClarenceZero/article/details/106996361

工厂模式

所谓工厂,就是将零件组装成产品的地方。

建一个对象常常需要复杂的过程,所以不适合包含在一个复合对象中。创建对象可能会导致大量的重复代码,可能会需要复合对象访问不到的信息,也可能提供不了足够级别的抽象,还可能并不是复合对象概念的一部分。

在面向对象程序设计中,工厂通常是一个用来创建其他对象的对象。工厂是构造方法的抽象,用来实现不同的分配方案。

一般情况下,工厂模式分为三种更加细分的类型: 简单工厂、工厂方法和抽象工厂 。不过,在GoF的《设计模式》一书中,它将简单工厂看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法抽象工厂两类。实际上,前一种分类方法更加常见。

实际上,简单工厂工厂方法原理比较简单,实际的项目也比较常用。而抽象工厂的原理稍微复杂,在实际项目相对也不常用。对于抽象工厂方法了解即可。

几个不同的设计模式都应用了工厂的概念,并可以使用在很多语言中。例如,在《设计模式》一书中,像工厂方法模式、抽象工厂模式生成器模式,甚至是单例模式都应用了工厂的概念。

简单工厂(Simple Factory)

实现

1. 工厂类

    /**
     * Pizza 简单工厂类
     */
    public class SimplePizzaFactory {
        /**
         * 根据不同pizza类型创建对应Pizza
         * @param type
         * @return
         */
        public Pizza createPizza(String type) {
            if (type.equals("cheese")) {
                return new CheesePizza();
            } else if (type.equals("veggie")) {
                return new Veggie("veggiePizza");
            } else {
                return null;
            }
        }
    }

2. 具体对象

    public class Veggie extends Pizza {
        public Veggie(String name) {
            setName(name);
        }
    }

3. Main方法

    public class T {
        public static void main(String[] args) {
            // 1. 创建Pizza简单工厂
            SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
            // 2.根据type创建Pizza
            Pizza cheese = simplePizzaFactory.createPizza("veggie");
            // 3.得到Pizza
            cheese.toString();
        }
    }

通过简单的if/else判断,创建对应的pizza。如果要添加新的pizza,势必要改动源码,那这违反开闭原则呢? 实际上,对于不需要频繁添加新的pizza,也是完全可以接受的。

工厂方法

定义

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

工厂方法模式帮助我们将产品的实现使用中解耦。如果增加产品或者改变产品的实现。Creator类并不会受影响。

类图

202303272240350811.png

  1. 两个类层级平等: 因为它们都有抽象类,而抽象类都有许多具体的子类,每个子类都有自己的实现。
  2. 所有的工厂都是用来封装对象的创建。
  3. 工厂方法使用继承,把对象的创建委托给子类,子类实现工厂方法的创建对象。

实现

202303272240358362.png

1. 定义工厂

    /**
     * PizzaStore 作为超类,可以被其他
     * 各个加盟店之间的区别在于风味不同,而其它流程我们认为是相同的
     */
    public abstract class PizzaStore {
        public Pizza orderPizza(String type) {
            Pizza pizza;
            pizza = createPizza(type);
            System.out.println(pizza.getName());
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    
        /**
         * ① 工厂方法是抽象的。所以依赖子类来处理对象的创建
         * ② 必须返回一个产品
         * ③ 工厂方法有可能需要参数,也有可能不需要参数来指定所需要的产品
         * @param type
         * @return
         */
        protected abstract Pizza createPizza(String type);
    }

2. 实现工厂

    /**
     * 芝加哥 Pizza风味店
     */
    public class ChicagoStylePizzaStore extends PizzaStore{
        @Override
        protected Pizza createPizza(String type) {
            if (type.equals("cheese")) {
                return new ChicagoStyleCheesePizza();
            } else {
                return null;
            }
        }
    }

3. Main方法

    public class M {
        public static void main(String[] args) {
            System.out.println("The first customer order in Chicago pizza store");
            PizzaStore chicagoStylePizzaStore = new ChicagoStylePizzaStore();
            Pizza pizza = chicagoStylePizzaStore.orderPizza("cheese");
    
            System.out.println("The second customer order in NY pizza store");
            NYStylePizzaStore nyStylePizzaStore = new NYStylePizzaStore();
            nyStylePizzaStore.orderPizza("cheese");
        }
    }

总结

优点:

  1. 调用者只需要知道名称即可,不需要了解具体实现。屏蔽具体实现细节。
  2. 扩展性高。如果想增加一个产品,只要扩展一个工厂类即可。

缺点:

  1. 每增加一个产品,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,一定程序上增加了系统的复杂度。
  2. 增加了系统具体类的依赖。
  3. 不符合开闭-原则。即对扩展开发、对修改关闭。因为每增加一个产品类,需要在工厂代码里面增加if-else判断。
  4. 简单工厂模式其实不是一个设计模式,反而比较像是一种编程习惯。

什么时候该用工厂方法模式,而非简单工厂模式?

之所以将某个代码块剥离独立为函数或类,原因是这个代码块的逻辑过于复杂,剥离之后能让代码更加清晰,更加可读、可维护。但是,如果代码块本身并不复杂,就几行代码而已,我们完全没必要将它拆分成单独的函数或者类。

基于这个设计思想,当对象的创建逻辑比较复杂,不只是简单的new一下,而是要组合其他类对象,做各种初始化操作的时候,推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得复杂。

除此之外,在某些场景下,如果对象不可复用,那工厂类每次都要返回不同对象。如果我们使用简单工厂模式实现,就只能选择第一种包含if分支逻辑的实现方式。如果我们还想避免烦人的if-else分支逻辑,推荐使用工厂方法模式

抽象工厂模式

定义

抽象工厂模式 (英语: Abstract factory pattern )是一种软件开发设计模式。抽象工厂模式提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。

工厂是创建产品(对象)的地方,其目的是将产品的创建与产品的使用分离。抽象工厂模式的目的,是将若干抽象产品的接口与不同主题产品的具体实现分离开。这样就能在增加新的具体工厂的时候,不用修改引用抽象工厂的客户端代码。

使用抽象工厂模式,能够在具体工厂变化的时候,不用修改使用工厂的客户端代码,甚至是在运行时。抽象工厂模式的实质是“提供接口,创建一系列相关或独立的对象,而不指定这些对象的具体类。

(引用维基百科)

抽象工厂为产品家族提供接口。通过抽象工厂所提供的接口,可以创建产品的家族,复用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。

抽象工厂的方法经常以工厂方法的方式实现。

(引用Head First 设计模式)

抽象工厂的工作就是将抽象零件组装为抽象产品抽象是指不考虑具体怎么实现,而是仅仅只确定了方法的名字和签名

类图

202303272240366733.png

  • 抽象工厂

    确定工厂的业务范围。

  • 具体工厂

    每个具体工厂对应一个产品族。具体工厂决定生产哪个具体的产品对象。

  • 抽象产品

    同一产品等级结构的抽象类。

  • 具体产品

    可供生产的具体产品。

实现

202303272240374234.png

1. 定义原料工厂接口

    /**
     * Pizza 原料工厂接口
     * 定义如何产生一个相关产品的家族。这个家族包含了所有制作Pizza的原料
     */
    public interface PizzaIngredientFactory {
    
        Dough createDough();
    
        Sauce createSauce();
    
        Cheese createCheese();
    }

2. 定义原料工厂实现类

    /**
     * 该工厂根据自身类型生产原料
     */
    public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
        @Override
        public Dough createDough() {
            return new ThinCrustDough();
        }
    
        @Override
        public Sauce createSauce() {
            return new MarinaraSauce();
        }
    
        @Override
        public Cheese createCheese() {
            return new ReggianoCheese();
        }
    }

3. 定义原料抽象类

    /**
     * Cheese原料定义
     */
    public abstract class Cheese {
    }
    /**
     * 酱料
     */
    public abstract class Sauce {
    }

4. 定义原料实现类

    public class MarinaraSauce extends Sauce {
    
    }
    public class ReggianoCheese extends Cheese {
    
    }

5. 定义抽象工厂

    /**
     * Pizza店 抽象类
     */
    public abstract class PizzaStore {
        public Pizza orderPizza(String type) {
            Pizza pizza = createPizza(type);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    
        /**
         * ① 工厂方法是抽象的。所以依赖子类来处理对象的创建
         * ② 必须返回一个产品
         * ③ 工厂方法有可能需要参数,也有可能不需要参数来指定所需要的产品
         * @param type
         * @return
         */
        protected abstract Pizza createPizza(String type);
    }

6. 定义抽象工厂实现类

    /**
     * Pizza店具体实例
     * 是抽象工厂的客户
     */
    public class NYPizzaStore extends PizzaStore{
        /**
         * 根据类型创建Pizza
         * @param type
         * @return
         */
        @Override
        protected Pizza createPizza(String type) {
            Pizza pizza = null;
            // 通过
            PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
            if (type.equals("cheese")) {
                pizza = new CheesePizza(ingredientFactory);
                pizza.setName("New York Style Cheese Pizza");
                return pizza;
            }
            return null;
        }
    }

7. Main

    public class T {
        public static void main(String[] args) {
            // 创建纽约Pizza店
            PizzaStore pizzaStore = new NYPizzaStore();
            // 接受订单
            pizzaStore.orderPizza("cheese");
        }
    }

依赖到置原则

要依赖抽象,不要依赖具体类

指导方针

  1. 变量不可以持有具体类的引用

    如果使用new关键字,就会持有具体类的引用。你可以改用工厂来避开这样的做法。

  2. 不要让类派生自具体类

    如果派生自具体类,你就会依赖这个具体类。请派生自一个抽象(接口或抽象类)。

  3. 不要覆盖基类中已实现的方法。

    如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。

当然,完全遵守这些指导方针似乎不太可能,应该尽量达到这个原则,而不是随时都要遵循这个原则。

总结

202303272240380445.png

  1. 工厂方法使用继承实现对象的创建。而抽象方法使用组合。工厂方法只负责将客户从具体类型中解耦。而抽象工厂提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。
  2. 高层模块 只需要知道自己是哪个产品族的 ,藉此确定具体工厂。再通过具体工厂获取对象的时候 不需要知道这个对象是属于哪个具体类的 ,具体工厂决定使用哪个具体类。

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文