资讯专栏INFORMATION COLUMN

Java 设计模式之工厂模式

Jochen / 2358人阅读

摘要:与以往的使用的方式不同,工厂模式使用工厂实例化对象。抽象工厂模式亮相抽象工厂模式抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

写在前面

这篇博文介绍设计模式的形式将与其他篇博文不太一样,这里我们将从一个问题入手,逐步了解到简单工厂、工厂方法与抽象工厂模式。

PS:这篇博文涉及的内容较多,所以篇幅有点长,请耐心阅读。

为什么要使用工厂模式?

在 OO 设计中,有一个重要的设计原则:针对接口编程而不针对实现编程。每当我们使用 new 去实例化一个对象时,用到的就是实现编程,而不是接口。这样以来代码绑定着具体类,会导致代码更脆弱,缺乏弹性。

在技术上,使用 new 没有错,毕竟这是 Java 的基础部分。真正错的是“改变”,以及它会影响 new 的使用。针对接口编程,可以隔离掉以后系统可能发生的一堆改变。原因是,如果代码针对接口编程,那么通过多态,它可以与任何新类实现该接口。

与以往的使用 new 的方式不同,工厂模式使用“工厂”实例化对象。这样一来代码的扩展性变得更强,又可以降低代码之间的耦合。

一、从问题中引出工厂模式

1.1 问题描述

比萨店:假设你有一家比萨店,为了吸引更多的顾客,所以你们的比萨店提供了很多种类型的比萨,比如奶酪比萨,希腊比萨等。针对这个问题设计比萨从生产到售卖相关的类。

设计图

1.2 代码实现

比萨类 Pizza

package com.jas.simplefactory;

public abstract class Pizza {
    public String name;
    public String sauce;
    
    public void prepare(){
        System.out.println("准备 ..." + name);
        System.out.println("添加配料 ..." + sauce);
    }
    
    public void bake(){
        System.out.println("烘烤 25 分钟。");
    }
    
    public void cut(){
        System.out.println("把比萨饼切成对角片。");
    }
    
    public void box(){
        System.out.println("将比萨放到比萨商店的盒子中。");
    }
}

奶酪比萨类 CheesePizza

package com.jas.simplefactory;

public class CheesePizza extends Pizza {
    public CheesePizza(){
        name = "奶酪比萨";
        sauce = "大蒜番茄酱";
    }
}

希腊比萨类 GreekPizza

package com.jas.simplefactory;

public class GreekPizza extends Pizza{
    public GreekPizza(){
        name = "希腊比萨";
        sauce = "大蒜番茄酱";
    }
}

比萨商店类 PizzaStore

package com.jas.simplefactory;

public class PizzaStore {
    public Pizza orderPizza(String type){
        Pizza pizza = null;
        
        //比萨商店负责生产比萨
        if("cheese".equals(type)){
            pizza = new CheesePizza();
        }else if("greek".equals(type)){
            pizza = new GreekPizza();
        }
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        
        return pizza;
    }
}

测试类 PizzaTestDrive

package com.jas.simplefactory;

public class PizzaTestDrive {
    public static void main(String[] args) {
        
        PizzaStore pizzaStore = new PizzaStore();
        // 点一份奶酪比萨
        pizzaStore.orderPizza("cheese");
    }
}

    /**
     * 输出
     * 
     * 准备 ...奶酪比萨
     * 添加配料 ...大蒜番茄酱
     * 烘烤 25 分钟。
     * 把比萨饼切成对角片。
     * 将比萨放到比萨商店的盒子中。
     * 
     */

1.3 设计引发的问题

上面代码中我们只列出了两种类型的比萨,在实际生产的过程中肯定不止这两种比萨。为了解决这个问题我们可以在 PizzaStore 类中的 orderPizza() 方法中添加类型判断,从而生产不同类型的比萨。所以,随着比萨类型的增多,这个方法会不停的进行判断。

假如某个比萨的销量不好,我们不想再生产这种类型的比萨,那么我们必须要找到 PizzaStore 类中的 orderPizza() 方法,将该种类型的比萨删除。

所以我们总结出:比萨的类型是多变的。这时候我们就要重新考虑设计的方式,简单工厂模式就可以帮我们解决这个问题。它将一些容易改变的代码抽取出来,多带带封装。

正好符合我们的一个设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

二、工厂模式登场

2.1 简单工厂模式亮相

(1)简单工厂模式

在《Head First 设计模式》一书中这样描述简单工厂:简单工厂其实并不是一个设计模式,反而比较像一种编程习惯。但是由于经常被使用,所以我们给它一个“Head First Pattern 荣誉奖”。

(2)简单工厂组成结构

工厂类角色:含有一定的商业逻辑和判断逻辑,用来创建产品。

抽象产品角色:它一般是具体产品继承的父类或者实现的接口。

具体产品角色:工厂类所创建的对象就是此角色的实例。

(3)简单工厂设计图

(4)简单工厂代码实现

工厂类 SimplePizzaFactory

package com.jas.simplefactory;

public class SimplePizzaFactory {
    private SimplePizzaFactory(){};
    
    public static Pizza createPizzs(String type){
        Pizza pizza = null;
        
        if("cheese".equals(type)){
            pizza = new CheesePizza();
        }else if("greek".equals(type)){
            pizza = new GreekPizza();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

改写比萨商店类 PizzaStore

package com.jas.simplefactory;

public class PizzaStore {
    public Pizza orderPizza(String type){
        Pizza pizza = null;
        
        pizza = SimplePizzaFactory.createPizzs(type);
        
        return pizza;
    }
}

测试类 PizzaTestDrive

package com.jas.simplefactory;

public class PizzaTestDrive {
    public static void main(String[] args) {
        
        PizzaStore pizzaStore = new PizzaStore();
        //点一份希腊比萨
        pizzaStore.orderPizza("greek");
        
    }
}

    /**
     * 输出
     * 
     * 准备 ...希腊比萨
     * 添加配料 ...大蒜番茄酱
     * 烘烤 25 分钟。
     * 把比萨饼切成对角片。
     * 将比萨放到比萨商店的盒子中。
     * 
     */

(5) 简单工厂总结

使用简单工厂模式,我们将 PizzaStroe 中易改变的代码抽取出来单,独用一个工厂类封装起来。当我们想要增加或减少比萨种类时,我们不必在去修改 PizzaStroe 类,直接修改 SimplePizzaFactory 类中的代码即可。

这样以来代码变得更灵活了,代码的复用性变得更强。当其他类中也需要 Pizza 实例时,也可以直接从 SimplePizzaFactory 类中获得。

2.2 工厂方法模式亮相

(1)工厂方法模式

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

(2)工厂方法模式组成结构

抽象工厂角色:是具体工厂角色必须实现的接口或者必须继承的父类。在 java 中它由抽象类或者接口来实现。声明了抽象的工厂方法。

具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。实现了父类或接口中定义的抽象工厂方法。

抽象产品角色:它是具体产品继承的父类或者是实现的接口。在 java 中一般由抽象类或者接口来实现。

具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在 java 中由具体的类来实现。

(3)工厂方法模式的 UML 图解

(4) 比萨店连锁啦

由于比萨店的销量比较好,你挣了一大笔钱,因此你想开几家分店。最终你选择在北京和上海各开一家分店。但是由于地区的差异,北京和上海两地的口味也不一样。

北京人希望比萨的调料会多一些,而上海人则希望比萨的调料少一些。所以根据口味的不同,你要制造不同口味的比萨来满足顾客。

我们已经有了一个简单的比萨生产工厂 SimplePizzaFactory,但是这个工厂加工的比萨并不对北京和上海两地人的胃口。于是就想着再建两个比萨生产工厂 (BJPizzaFactorySHPizzaFactory),分别生产不同口味的比萨。

但是这样做有一个不好的地方,虽然不同的比萨生产工厂能够生产出对应的比萨,但是其他的方法比如:烘焙,切片和包装却使用着自己的生产流程 (生产一致则存在大量重复代码)。但是你希望这些生产流程在每个工厂都应该一样 (复用烘焙,切片和包装的代码,或者覆盖它们),使用简单工厂的方式就不再能够行的通了。

于是工厂方法就站了出来。

(5)工厂方法模式设计图

(6)工厂方法模式代码实现

代码用了上面的 Pizza 类。

抽象工厂角色 PizzaStore 抽象类

package com.jas.factorymethod;

import com.jas.simplefactory.Pizza;

public abstract class PizzaStore {
    public Pizza orderPizza(String type){
        Pizza pizza = null;
        
        pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        
        return pizza;
    }

    /**
     * 工厂方法,将生产比萨的方法定义成抽象方法,让子类去实现
     * @param type 比萨的类型
     * @return 对应的比萨实例
     */
    protected abstract Pizza createPizza(String type);
}

具体工厂角色 BJPizzaStore 类 (这里为了防止篇幅过长,省略了 SHPizzaStore 类和对应的比萨类)

package com.jas.factorymethod;

import com.jas.simplefactory.Pizza;

public class BJPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        Pizza pizza = null;
        
        if("cheese".equals(type)){
            pizza =  new BJCheesePizza();
        }else if("greek".equals(type)){
            pizza = new BJGreekPizza();
        }
        
        return pizza;
    }
}

具体产品角色 BJCheesePizza 类 (这里只定义一种比萨的种类)

package com.jas.factorymethod;

import com.jas.simplefactory.Pizza;

public class BJCheesePizza extends Pizza {
    public BJCheesePizza(){
        name = "北京人喜欢吃的奶酪比萨";
        sauce = "浓浓的大蒜番茄酱";
    }

    /**
     * 覆盖 curt() 方法将比萨切成块状
     */
    @Override
    public void cut(){
        System.out.println("把比萨饼切成方块状。");
    }
}

测试类

package com.jas.factorymethod;

public class PizzaTestDrive {
    public static void main(String[] args) {
        
        PizzaStore pizzaStore = new BJPizzaStore();
        //根据北京人的口味要一个奶酪比萨
        pizzaStore.orderPizza("greek");
        
    }
}

    /**
     * 输出
     * 
     * 准备 ...北京人喜欢吃的希腊比萨
     * 添加配料 ...浓浓的大蒜番茄酱
     * 烘烤 25 分钟。
     * 把比萨饼切成方块状。
     * 将比萨放到比萨商店的盒子中。
     * 
     */

(7)工厂方法模式总结

简单工厂模式中将生产比萨的代码多带带抽取了出来,用一个工厂进行封装,由该工厂生产比萨实例。但是由于开了比萨连锁店,建立多个比萨工厂并不是好的解决办法。

所以我们使用了工厂方法模式,在比萨 PizzaStore 抽象类中定义了一个抽象方法 createPizza() 方法,由其子类去实现该方法,这样以来不同的连锁店就能生产自己的比萨了。

如果增加产品或者改变产品的实现,PizzaStore 并不需要做任何的改变。以很好的方式实现了 PizzaStore 与具体比萨之间的解耦。

2.3 抽象工厂模式亮相

(1) 抽象工厂模式

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

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

(2) 抽象工厂模式的 UML 图解

(3)比萨店连锁店出了一些状况

比萨连锁店成功的关键是在于新鲜、高质量的原料。但是有的连锁店开始使用低价的原料来增加利润,为了防止比萨店的声誉遭到破坏,你必须采取相对应的措施。为了解决这个问题,你打算新建一家生产原料的工厂,并将原料运往各家加盟店,这样以一来,所有的加盟店都使用你生产的原料来制作比萨。

但是这样做却有一个问题:因为加盟店坐落于不同的地域,比如北京和上海,它们需要的酱料是不一样的。所以为了满足需求,你为它们准备了两组不同的原料。
于是你有了一个想法,你打算创建一个原料接口,由不同地域的工厂自己去实现。

(4)抽象工厂模式设计图

(5) 抽象工厂模式代码实现

抽象原料工厂 PizzaInfgredientFactory 接口

package com.jas.abstractfactory;

public interface PizzaInfgredientFactory {
    Dough createDough();
    Cheese createCheese();
}

具体原料工厂 SHPizzaInfgredientFactory

package com.jas.abstractfactory;

/**
 * 这里是上海原料加工厂,生产的原料是薄面团与 Reggiano 干酪
 */
public class SHPizzaInfgredientFactory implements PizzaInfgredientFactory {
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }
}

抽象 Pizza

package com.jas.abstractfactory;

public abstract class Pizza {
    public String name;
    //为了篇幅过长,不再贴出原料相关代码
    public Dough dough;
    public Cheese cheese;

    public abstract void prepare();

    public void bake(){
        System.out.println("烘烤 25 分钟。");
    }

    public void cut(){
        System.out.println("把比萨饼切成对角片。");
    }

    public void box(){
        System.out.println("将比萨放到比萨商店的盒子中。");
    }
}

具体 CheesePizza 类 (这里只新建了一个比萨具体实现类)

package com.jas.abstractfactory;

public class CheesePizza extends Pizza {
    PizzaInfgredientFactory infgredientFactory = null;
    
    public CheesePizza(PizzaInfgredientFactory infgredientFactory){
        this.infgredientFactory = infgredientFactory;
    }
    
    @Override
    public void prepare() {
        System.out.println("准备" + name);
        dough = infgredientFactory.createDough();
        cheese = infgredientFactory.createCheese();
    }
}

具体 SHPizzaStore

package com.jas.abstractfactory;

public class SHPizzaStore {
    PizzaInfgredientFactory infgredientFactory = new SHPizzaInfgredientFactory();
    
    protected Pizza createPizza(String type){
        Pizza pizza = null;
        
        if("cheese".equals(type)){
            //生产比萨的原料来自上海的原料加工工厂
            pizza = new CheesePizza(infgredientFactory);
            pizza.name = "原料来自上海工厂加工的奶酪比萨";
        }
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

测试类 PizzaTestDrive

package com.jas.abstractfactory;

public class PizzaTestDrive {
    public static void main(String[] args) {

        SHPizzaStore pizzaStore = new SHPizzaStore();
        pizzaStore.createPizza("cheese");
    }
}

     /**
     * 准备原料来自上海工厂加工的奶酪比萨
     * 烘烤 25 分钟。
     * 把比萨饼切成对角片。
     * 将比萨放到比萨商店的盒子中。
     */

(6)抽象工厂模式总结

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

三、工厂方法模式与抽象工厂模式总结

工厂方法与抽象工厂的工作都是负责创建对象,但是工厂方法使用的方法是继承,而抽象工厂使用的是组合。

这意味着,利用工厂方法创建对象,需要扩展一个类,并实现其中的工厂方法。由这个方法创建对象,只不过这个方法通过子类创建对象,用这种做法,客户只需要知道他们所使用的抽象类型就可以了,而由子类负责决定具体类型。所以,工厂方法只负责将客户从具体类型中解耦。

抽象工厂提供一个类来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。要使用这个工厂,必须先要实例化它,然后将它传入一些针对抽象类型所写的代码中。

所以,和工厂方法一样,抽象工厂也可以实现客户从所使用地实际具体产品中解耦。但是它还有另一个优点:可以把一群相关的产品集合起来创建。但是这也有一个缺点:如果新加入创建的产品,就必须要改变接口...这样做的后果是很严重的。

其实抽象工厂中的具体工厂经常使用工厂方法来创建产品。总之,它们两个都可以将对象的创建封装起来,使应用程序解耦,因此降低程序之间的依赖。

当你需要创建产品家族和想让制造的相关产品集合起来时,你可以使用抽象工厂。当你需要创建一种类型的对象时,你可以选择使用工厂方法。

参考资料

《Head First 设计模式》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77332.html

相关文章

  • 设计模式抽象工厂模式

    摘要:所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。 0x01.定义与类型 定义:抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口 无需指定它们具体的类 类型:创建型 UML showImg(https://segmentfault.com/img/bVbtBp1?w=800&h=862...

    Acceml 评论0 收藏0
  • 设计模式工厂方法模式

    摘要:通过工厂方法模式的类图可以看到,工厂方法模式有四个要素工厂接口工厂接口是工厂方法模式的核心,与调用者直接交互用来提供产品。使用场景创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。 0x01.定义与类型 定义:定义一个创建对象的接口,但让实现这个接口的类来决定实例化那个类,工厂方法让类的实例化推迟到子类中进行 类型:创建型 uml类图 showImg(https:/...

    geekidentity 评论0 收藏0
  • 设计模式简单工厂模式

    摘要:类型创建型,但不属于中设计模式。简介通过一个专门的工厂类来创建其他类,这些被创建的类通常有一个共同的父类或接口。相关代码简单工厂模式推荐阅读慕课网设计模式精讲简单工厂模式 0x01.定义与类型 定义:由一个工厂对象决定创建出哪一种产品类的实例。 类型:创建型,但不属于GOF23中设计模式。 简介:通过一个专门的工厂类来创建其他类,这些被创建的类通常有一个共同的父类或接口。 uml类图...

    mo0n1andin 评论0 收藏0
  • Java设计模式(二)——工厂模式

    摘要:需要说明的是在设计模式一书中将工厂模式分为两类工厂方法模式与抽象工厂模式,将简单工厂模式看为工厂方法模式的一种特例,两者归为一类。工厂模式的作用工厂模式的作用封装变化创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。1、什么是工厂模式Define an interface for creating an object,but let subclasses decide whi...

    Doyle 评论0 收藏0
  • Java - 收藏集 - 掘金

    摘要:强大的表单验证前端掘金支持非常强大的内置表单验证,以及。面向对象和面向过程的区别的种设计模式全解析后端掘金一设计模式的分类总体来说设计模式分为三大类创建型模式,共五种工厂方法模式抽象工厂模式单例模式建造者模式原型模式。 强大的 Angular 表单验证 - 前端 - 掘金Angular 支持非常强大的内置表单验证,maxlength、minlength、required 以及 patt...

    XiNGRZ 评论0 收藏0
  • Java设计模式工厂模式详解

    摘要:工厂模式,又称为工厂方法模式。工厂模式,也就是工厂方法模式是解决了简单工厂要修改代码的问题,他把对象的创建操作延迟到子类工厂中,这样新增产品就不需要修改代码。 简单工厂其实并不是设计模式,只是一种编程习惯。 首先我们创建父类Cup,所有杯子类的父类。再创建它的子类BigCup和SmallCup类。 public abstract class Cup { public abst...

    Rocture 评论0 收藏0

发表评论

0条评论

Jochen

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<