设计模式初解——工厂模式

491 阅读10分钟

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

一、介绍

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

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

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

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

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

应用实例

  1. 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现
  2. Hibernate 换数据库只需换方言和驱动就可以

优点

  1. 一个调用者想创建一个对象,只要知道其名称就可以了
  2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以
  3. 屏蔽产品的具体实现,调用者只关心产品的接口

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

使用场景

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

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

二、工厂模式的三种形式

  1. 简单工厂(Simple Factory)模式:又称静态工厂方法模式(Static Factory Method Pattern)
  2. 工厂方法(Factory Method)模式:又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式
  3. 抽象工厂(Abstract Factory)模式:又称工具箱(Kit 或 ToolKit)模式

三、简单工厂模式

简单工厂模式(又叫作静态工厂方法模式), 其属于创建型设计模式,但并不属于23种Gof设计模式之一。
定义:简单工厂模式属于创建型设计模式,又叫作静态工厂方法模式,这是由一个工厂对象决定创建出哪一种产品类的实例。

实现

场景描述:比如说有个农场公司,专门向市场销售各类水果。在这个系统里需要描述下列的水果

  • 葡萄 Grape
  • 草莓 Strawberry
  • 苹果 Apple
/**
 * 定义一个适用于所有水果的接口,定义出所有水果必须具备的方法:
 * 种植plant(),生长grow(),收获harvest()
 */
public interface Fruit {
    //种植
    void plant();

    //生长
    void grow();

    //收获
    void harvest();

    default  void log(String message){
        System.out.println(message);
    }
}
/**
 * 苹果类
 */
public class Apple implements Fruit {

    //树龄
    private Integer treeAge;

    @Override
    public void plant() {
        log("Apple开始种植...");
    }

    @Override
    public void grow() {
        log("Apple开始生长...");
    }

    @Override
    public void harvest() {
        log("Apple开始收货...");
    }

    public Integer getTreeAge() {
        return treeAge;
    }

    public void setTreeAge(Integer treeAge) {
        this.treeAge = treeAge;
    }
}
/**
 * 工厂类,负责生产水果
 */
public class FruitFactory {
    public static Fruit getFruit(String type) {
        switch (type) {
            case "apple":
                return new Apple();
            default:
                throw new RuntimeException("农场没有这种水果");
        }
    }
}

测试: 输出结果: 特点:它是一个具体的类,非接口、抽象类。有一个重要的getFruit()方法,利用switch创建产品并返回

优点:

  • 解耦,将需求类与实现类分离了,通过工厂类进行交互,实现了责任的分割
  • 无论是添加,修改,删除子类,都十分的容易,不会影响到其他的类
  • 复用,子类可以多次复用

缺点:

  • 扩展性差(我想增加一种水果,除了新增一个水果产品类,还需要修改工厂类方法)
  • 不同的产品需要不同额外参数的时候不支持
  • 不符合开闭原则\color{red}{开闭原则}:对扩展开放,对修改关闭

四、工厂方法模式

提供一个用于创建对象的接口(工厂接口),让其实现类(工厂实现类)决定实例化哪一个类(产品类),并且由该实现类创建对应类的实例

作用 可以一定程度上解耦,消费者和产品实现类隔离开,只依赖产品接口(抽象产品),产品实现类如何改动与消费者完全无关。

可以一定程度增加扩展性,若增加一个产品实现,只需要实现产品接口,修改工厂创建产品的方法,消费者可以无感知(若消费者不关心具体产品是什么的情况)。 可以一定程度增加代码的封装性、可读性。清楚的代码结构,对于消费者来说很少的代码量就可以完成很多工作。 另外,抽象工厂才是实际意义的工厂模式,工厂方法只是抽象工厂的一个比较常见的情况。

适用场景
消费者不关心它所要创建对象的类(产品类)的时候。
消费者知道它所要创建对象的类(产品类),但不关心如何创建的时候。

实现

提供一个产品类的接口。产品类均要实现这个接口(也可以是abstract类,即抽象产品)。 提供一个工厂类的接口。工厂类均要实现这个接口(即抽象工厂)。 由工厂实现类创建产品类的实例。工厂实现类应有一个方法,用来实例化产品类。

/**
 * 工厂接口
 */
public interface IMessageFactory {
    public IMessage createMessage(String messageType);
}
/**
 * 产品接口
 */
public interface IMessage {
    public Map<String, Object> getMessageParam();

    public void setMessageParam(Map<String, Object> messageParam);

    public void sendMesage() throws Exception;
}
/**
 * 虚拟产品类
 */
public abstract class AbstractMessage implements IMessage {

    private Map<String, Object> messageParam;

    @Override
    public Map<String, Object> getMessageParam() {
        return messageParam;
    }

    @Override
    public void setMessageParam(Map<String, Object> messageParam) {
        this.messageParam = messageParam;
    }
}
/**
 * 邮件产品类
 */
public class EmailMessage extends AbstractMessage {
    @Override
    public void sendMesage() throws Exception {
        // TODO Auto-generated method stub
        if (null == getMessageParam() || null == getMessageParam().get("EMAIL")
                || "".equals(getMessageParam().get("EMAIL"))) {
            throw new Exception("发送短信,需要传入EMAIL参数");
        }
        System.out.println("我是邮件,发送通知给" + getMessageParam().get("EMAIL"));
    }
}
/**
 * 短信产品类
 */
public class SmsMessage extends AbstractMessage {
    @Override
    public void sendMesage() throws Exception {
        if (null == getMessageParam()
                || null == getMessageParam().get("PHONENUM")
                || "".equals(getMessageParam().get("PHONENUM"))) {
            throw new Exception("发送短信,需要传入PHONENUM参数");
        }
        System.out.println("我是短信,发送通知给" + getMessageParam().get("PHONENUM"));
    }
}
/**
 * 工厂实现
 */
public class MessageFactory implements IMessageFactory {
    @Override
    public IMessage createMessage(String messageType) {
        // 这里的方式是:消费者知道自己想要什么产品;若生产何种产品完全由工厂决定,则这里不应该传入控制生产的参数。
        IMessage message;
        Map<String, Object> messageParam = new HashMap<String, Object>();
        // 根据某些条件去选择究竟创建哪一个具体的实现对象,条件可以传入的,也可以从其它途径获取。
        // sms
        if ("SMS".equals(messageType)) {
            message = new SmsMessage();
            messageParam.put("PHONENUM", "123456789");
        } else {
            message = new EmailMessage();
            messageParam.put("EMAIL", "123456@qq.com");
        }
        message.setMessageParam(messageParam);
        return message;
    }
}

测试: 输出结果:

五、抽象工厂模式

为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

与工厂方法模式的区别

抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。

  • 工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。
  • 在抽象工厂模式中,有一个产品族的概念:所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品称为一个等级结构。

适用场景

  1. 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
  2. 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码
  3. 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现

为了更好理解产品等级结构与产品族,我们看两张图 横着一行是一个产品族,竖着一列是一个产品等级结构。

实现

业务场景:现在需要录视频还要手记,如Java手记、Python手记等,要是按照工厂模式,需要创建Java手记类、python手记类、Java手记工厂、python 手记工厂、手记工厂类,很容易发生类爆炸情况。其中Java视频、python 视频是一个产品等级都是视频,Java手记、python手记又是一个产品等级,Java视频和Java手记是同一产品族。

//手记工厂
public abstract class Article {
    public abstract void produce();
}
//视频工厂
public abstract class Video {
    public abstract void produce();
}
//Java手记工厂
public class JavaArticle extends Article {
    @Override
    public void produce() {
        System.out.println("编写Java课程手记");
    }
}
//Java视频工厂
public class JavaVedio extends Video {
    @Override
    public void produce() {
        System.out.println("录制Java课程视频");
    }
}
//Python手记工厂
public class PythonArticle extends Article {
    @Override
    public void produce() {
        System.out.println("编写Python课程手记");
    }
}
//Python视频工厂
public class PythonVideo extends Video{
    @Override
    public void produce() {
        System.out.println("录制Python课程视频");
    }
}
//抽象工厂
public interface CourseFactory {
    Video getVideo();

    Article getArticle();
}

测试: 输出结果:

优点:

  • 具体产品在应用层代码隔离,无须关心创建细节
  • 将一系列的产品族统一到一起创建
  • 最主要的优点就是可以在类的内部对产品族进行约束,所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理

缺点:

  • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口
  • 增加了系统的抽象性和理解难度
  • 产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改,所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。