Skip to content
On this page

工厂模式

概述

需求:设计一个生产手机模式系统

设计一个手机类(Phone),并定义其两个子类(小米[mi]和荣耀[Honor]),再设计一个手机店面,手机店面销售手机,其UML图如下:

image-20220917221304702

在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重。加入需要更换对象,所有new对象的地方都需要修改一遍,这显示违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只需要和工厂打交道就可以了,彻底和对象进行解耦,如果需要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的,所以说工厂模式最大的有点就是:解耦

分类

  • 简单工厂模式(不属于GOF的23种经典设计模式)
  • 工厂方法模式
  • 抽象工厂模式

简单工厂模式

结构

简单工厂包含如下角色

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品:实现或者继承抽象产品的子类
  • 具体工厂:提供创建产品的方法,调用者通过该方法来获取产品

实现

现在使用简单工厂对上面的案例进行改进,类图UML如下:

image-20220917222515996

工厂类

java
package com.huangjiliang.design.heima.create.factory.simple;

/**
 * 简单工厂
 */
public class SimplePhoneFactory {
    
    public Phone createPhone(String type) {
        Phone phone = null;
        switch (type) {
            case "mi":
                phone = new MiPhone();
                break;
            case "honor":
                phone = new HonorPhone();
            default:
                // 找不到该品牌手机
        }
        phone.playMusic();
        phone.playVideo();
        return phone;
    }
}

手机店

java
package com.huangjiliang.design.heima.create.factory.simple;

/**
 * 手机商店
 */
public class PhoneStore {

    private SimplePhoneFactory phoneFactory;

    public void setPhoneFactory(SimplePhoneFactory phoneFactory) {
        this.phoneFactory = phoneFactory;
    }

    public Phone sellPhone(String type) {
        return phoneFactory.createPhone(type);
    }
}

工厂factory处理创建对象的细节,一旦有了SimplePhoneFactory,PhoneStore类中的sellPhone()方法就变成了此对象的客户,后期如果需要Phone对象就直接从工厂中获取即可。这样解决了PhoneStore和Phone实现类的耦合;同时又产生了新的耦合,PhoneStore对象和SimplePhoneFactory工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再添加新品种的手机品牌,我们就需要修改SimplePhoneFactory的代码,违反了开闭原则。

工厂类的客户端可能有很多,这样只需要修改工厂类的代码,省去了其他的修改操作

优缺点

优点

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层进行分开,这样以后就避免了修改客户端代码。如果要实现新产品就直接修改工厂类,而不需要再原代码中修改,这样就降低了客户端代码修改的可能性,更加统一扩展

缺点

增加新产品时需要需要工厂类的代码,这违背了开闭原则

扩展

静态工厂

在开发中也有一部分将工厂类中的创建对象的功能定义为静态,这个就是静态工厂模式,它也不是23中设计模式中的

工厂类
java
package com.huangjiliang.design.heima.create.factory.simple;

/**
 * 静态简单工厂
 */
public class SimplePhonePlusFactory {

    public static Phone createPhone(String type) {
        Phone phone = null;
        switch (type) {
            case "mi":
                phone = new MiPhone();
                break;
            case "honor":
                phone = new HonorPhone();
            default:
                // 找不到该品牌手机
        }
        phone.playMusic();
        phone.playVideo();
        return phone;
    }
}
手机店
java
package com.huangjiliang.design.heima.create.factory.simple;

/**
 * 手机商店
 */
public class PhonePlusStore {

    public Phone sellPhone(String type) {
        return SimplePhonePlusFactory.createPhone(type);
    }
}

工厂方法模式

针对简单工厂模式的缺点(增加一个具体产品时,需要修改工厂类,违反开闭原则),使用工厂方法模式就可以完美的解决

概念

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

结构

工厂方法模式的主要角色

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用这通过它访问具体工厂的工厂方法来创建产品
  • 具体工厂(Concrete Factory):主要是实现抽象工厂的抽象方法,完成具体产品的创建
  • 抽象产品(Abstract Product):定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品(Contrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应

实现

使用工厂方法模式对简单工厂进行改进,其UML图如下

image-20220918120621245

抽象工厂

java
package com.huangjiliang.design.heima.create.factory.method;

/**
 * 工厂方法【抽象工厂类】
 */
public interface PhoneFactory {

    Phone createPhone();

}

小米手机工厂

java
package com.huangjiliang.design.heima.create.factory.method;

/**
 * 工厂方法【创建小米手机具体工厂类】
 */
public class MiPhoneFactory implements PhoneFactory {

    @Override
    public Phone createPhone() {
        return new MiPhone();
    }

}

荣耀手机工厂

java
package com.huangjiliang.design.heima.create.factory.method;

/**
 * 工厂方法【创建荣耀手机具体工厂类】
 */
public class HonorPhoneFactory implements PhoneFactory {

    @Override
    public Phone createPhone() {
        return new HonorPhone();
    }

}

手机销售店

java
package com.huangjiliang.design.heima.create.factory.method;

/**
 * 手机商店
 */
public class PhoneStore {

    private PhoneFactory phoneFactory;

    public void setPhoneFactory(PhoneFactory phoneFactory) {
        this.phoneFactory = phoneFactory;
    }

    public Phone sellPhone() {
        Phone phone = phoneFactory.createPhone();
        phone.playVideo();
        phone.playMusic();
        return phone;
    }
}

从以上编写代码可以看到,要增加产品时就需要相应的增加工厂类,不需要修改工厂类的代码,这样就很好的解决了简单工厂模式违反开闭原则的特点

优缺点

优点

  • 用户只需要知道具体工厂的名称就可以得到所要的产品,无需知道产品的具体创建过程
  • 在系统增加新的产品时,只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则

缺点

  • 没增加一个产品就需要增加一个具体产品类和一个对应的具体工厂类,这就增加了系统的复杂度

抽象工厂模式

工厂方法模式考虑的只是不同等级【小米和荣耀】产品的生产,但是现实生活中许多工厂是综合性的工厂,能生产多品种同族的产品。比如:荣耀工厂可以生产手机,还可以生产电脑。荣耀生产的手机和电脑统称为同一个产品组,小米和荣耀统称为同一级别

而抽象工厂模式就是考虑不同族且多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为产品族

image-20220918123202081

概念

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无需指定所要产品的具体类就能得到同族的不同等级的产品的模式结构

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只是生产一个等级的产品,而抽象工厂模式可以生产多个等级的产品

结构

抽象工厂模式的主要角色如下

  • 抽象工厂(Abstract Factory):提供创建产品的接口,包含多个创建产品的方法,可以创建多个不同等级的产品
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建
  • 抽象产品(Abstract Product):定义了产品的规范,描述了产品的主要特征和功能,抽象工厂模式有多个抽象产品
  • 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系

实现

案例

现制造手机业务发生改变,不仅要生产手机还要生产电脑,如小米电脑、荣耀电脑等,要是按照工厂方法模式,需要多加定义荣耀电脑类、小米电脑类、荣耀电脑工厂、小米电脑工厂、抽象电脑工厂类,很容易发生类爆炸的情况。其中小米电脑、荣耀电脑就是一个产品等级,都是电脑;小米手机、荣耀手机也是一个产品等级,都是手机;荣耀电脑和荣耀手机是同一个产品族(属于荣耀品牌)、小米手机和小米电脑是同一个产品族(属于小米品牌)。所以这个案例可以使用抽象工厂模式实现。其UML类图如下

image-20220918190119156

抽象工厂

java
package com.huangjiliang.design.heima.create.factory.abstractFactory;

/**
 * 抽象工厂
 */
public interface IFactory {

    /**
     * 手机生产
     * @return
     */
    Phone createPhone();

    /**
     * 电脑生产
     * @return
     */
    Computer createComputer();

}

小米工厂-具体工厂

java
package com.huangjiliang.design.heima.create.factory.abstractFactory;

/**
 * 小米工厂
 */
public class MiFactory implements IFactory{
    @Override
    public Phone createPhone() {
        return new MiPhone();
    }

    @Override
    public Computer createComputer() {
        return new MiComputer();
    }
}

荣耀工厂-具体工厂

java
package com.huangjiliang.design.heima.create.factory.abstractFactory;

/**
 * 荣耀工厂
 */
public class HonorFactory implements IFactory{
    @Override
    public Phone createPhone() {
        return new HonorPhone();
    }

    @Override
    public Computer createComputer() {
        return new HonorComputer();
    }
}
  • 如果要加同一个产品族的话,比如VIVO,只需要在添加对应的工厂类即可,不需要修改其他的类

优缺点

优点:

  • 当一个产品族中的多个对象被设计成一起工作是,它能保证客户端始终只使用同一个产品族中的对象

缺点:

  • 当产品族中需要增加一个新的产品时,比如手环,所有的工厂类都需要进行修改

使用场景

  • 当需要创建的对象是一些列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢苹果品牌的手机和电脑
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和北部结构

扩展模式

简单工厂+配置文件解除耦合

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类加载配置文件的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可

步骤

1、定义配置文件

使用properties文件作为配置文件,名称为bean.properties

miPhone=com.huangjiliang.design.heima.create.factory.config.MiPhone
honorPhone=com.huangjiliang.design.heima.create.factory.config.HonorPhone

2、改进工厂类

java
package com.huangjiliang.design.heima.create.factory.config;

import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 简单工厂 + 读取配置文件创建对象
 */
public class SimplePhoneFactory {
    private static Map<String, Phone> phoneData = new ConcurrentHashMap<>(2);
    static {
        Properties p = new Properties();
        InputStream inputStream = SimplePhoneFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            p.load(inputStream);
            Set<Object> keys = p.keySet();
            for (Object key : keys) {
                String className = p.getProperty((String) key);
                Class<?> clazz = Class.forName(className);
                Phone phone = (Phone)clazz.newInstance();
                phoneData.put((String)key, phone);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Phone createPhone(String type) {
        return phoneData.get(type);
    }
}

3、测试类

java
package com.huangjiliang.design.heima.create.factory.config;

/**
 * 测试简单工厂+配置文件创建对象方式
 */
public class Client {

    public static void main(String[] args) {
        SimplePhoneFactory simplePhoneFactory = new SimplePhoneFactory();
        Phone miPhone = simplePhoneFactory.createPhone("miPhone");
        System.out.println(miPhone.getName());;
        miPhone.playMusic();
        miPhone.playVideo();
    }

}
  • 静态成员变量又来存储创建的对象(键值存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中。目的就是只需要执行一次

JDK源码解析-Collection.iterator方法

java
import java.util.*;

public class Client {

    public static void main(String[] args) {
       
        List<String> list = new ArrayList<>();
        list.add("11");
        list.add("22");
        list.add("33");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            System.out.println(next);
        }

    }
}
  • 对于以上使用迭代器遍历List集合,获取集合中的元素。而List集合获取迭代器的方法就使用到了工厂方法模式,其UML图如下

image-20220918195847792

  • Collection接口是抽象工厂类,ArrayList是具体的工厂类;
  • Iterator接口是抽象产品类,ArrayList的内部类Itr是具体产品类。
  • 在具体的工厂类中iterator()方法创建具体的产品类的对象