设计模式-工厂模式-场景以及优缺点-目的就是应对变化 (国江面试回答的)

时间:2021-10-22 21:15:30

 总结:

我自己写了个例子:有兴趣的可以下载看看,参考:工厂模式

工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。   
一个抽象工厂类,可以派生出多个具体工厂类。   
每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。   
一个抽象工厂类,可以派生出多个具体工厂类。   
每个具体工厂类可以创建多个具体产品类的实例。   
    
区别:
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。   
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。 
 

一、工厂模式的好处:

(1) 工厂模式是为了解耦可以将对象的创建和使用分离,如果不分离,不但违反了设计模式的开闭原则,需要需要使用另一个子类的话,需要修改源代码 ,把对象的创建和使用的过程分开。就是Class A 想调用 Class B ,那么A只是调用B的方法,而至于B的实例化,就交给工厂类。

(2)工厂模式可以降低代码重复。如果创建对象B的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。我们可以这些创建对象B的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的创建过程的修改维护。(当然,我个人觉得也可以把这些创建过程的代码放到类的构造函数里,同样可以降低重复率,而且构造函数本身的作用也是初始化对象。不过,这样也会导致构造函数过于复杂,做的事太多,不符合java 的设计原则。)

由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建B的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。同理,想把所有调用B的地方改成B的子类B1,只需要在对应生产B的工厂中或者工厂的方法中修改其生产的对象为B1即可,而不需要找到所有的new B()改为new B1()。

(3)因为工厂管理了对象的创建逻辑,使用者并不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误 

(4)可以通过参数设置,返回不同的构造函数,不需要修改使用类的地方。如果一个类有多个构造方法(构造的重写),我们也可以将它抽出来,放到工厂中,一个构造方法对应一个工厂方法并命名一个友好的名字,这样我们就不再只是根据参数的不同来判断,而是可以根据工厂的方法名来直观判断将要创建的对象的特点。这对于使用者来说,体验比较好。

举个例子:

一个数据库工厂:可以返回一个数据库实例,可以是mysql,oracle等。

这个工厂就可以把数据库连接需要的用户名,地址,密码等封装好,直接返回对应的数据库对象就好。不需要调用者自己初始化,减少了写错密码等等这些错误。调用者只负责使用,不需要管怎么去创建、初始化对象。 

      在Java语言中,我们通常有以下几种创建对象的方式:

       (1) 使用new关键字直接创建对象;

       (2) 通过反射机制创建对象;

       (3) 通过clone()方法创建对象;

       (4) 通过工厂类创建对象。

      毫无疑问,在客户端代码中直接使用new关键字是最简单的一种创建对象的方式,但是它的灵活性较差,下面通过一个简单的示例来加以说明: 

设计模式-工厂模式-场景以及优缺点-目的就是应对变化 (国江面试回答的)
class LoginAction {  
    private UserDAO udao;  
      
    public LoginAction() {  
        udao = new JDBCUserDAO(); //创建对象  
    }  
      
    public void execute() {  
        //其他代码  
        udao.findUserById(); //使用对象  
        //其他代码  
    }  
}  
设计模式-工厂模式-场景以及优缺点-目的就是应对变化 (国江面试回答的)

 

      在LoginAction类中定义了一个UserDAO类型的对象udao,在LoginAction的构造函数中创建了JDBCUserDAO类型的udao对象,并在execute()方法中调用了udao对象的findUserById()方法,这段代码看上去并没有什么问题。下面我们来分析一下LoginAction和UserDAO之间的关系,LoginAction类负责创建了一个UserDAO子类的对象并使用UserDAO的方法来完成相应的业务处理,也就是说LoginAction即负责udao的创建又负责udao的使用,创建对象和使用对象的职责耦合在一起,这样的设计会导致一个很严重的问题:如果在LoginAction中希望能够使用UserDAO的另一个子类如HibernateUserDAO类型的对象,必须修改LoginAction类的源代码,违反了“开闭原则”。如何解决该问题?(对设计模式六大原则不了解参考:23种设计模式介绍(一)---- 创建型模式)

      最常用的一种解决方法是将udao对象的创建职责从LoginAction类中移除,在LoginAction类之外创建对象,那么谁来负责创建UserDAO对象呢?答案是:工厂类。通过引入工厂类,客户类(如LoginAction)不涉及对象的创建,对象的创建者也不会涉及对象的使用。引入工厂类UserDAOFactory之后的结构如图1所示:

设计模式-工厂模式-场景以及优缺点-目的就是应对变化 (国江面试回答的)

图1 引入工厂类之后的结构图

       工厂类的引入将降低因为产品或工厂类改变所造成的维护工作量。如果UserDAO的某个子类的构造函数发生改变或者要需要添加或移除不同的子类,只要维护UserDAOFactory的代码,而不会影响到LoginAction;如果UserDAO的接口发生改变,例如添加、移除方法或改变方法名,只需要修改LoginAction,不会给UserDAOFactory带来任何影响。

       在所有的工厂模式中,我们都强调一点:两个类A和B之间的关系应该仅仅是A创建B或是A使用B,而不能两种关系都有。将对象的创建和使用分离(美团面试题,工厂模式的优点),也使得系统更加符合“单一职责原则”,有利于对功能的复用和系统的维护。

       此外,将对象的创建和使用分离还有一个好处:防止用来实例化一个类的数据和代码在多个类中到处都是,可以将有关创建的知识搬移到一个工厂类中,这在Joshua Kerievsky的《重构与模式》一书中有专门的一节来进行介绍。因为有时候我们创建一个对象不只是简单调用其构造函数,还需要设置一些参数,可能还需要配置环境,如果将这些代码散落在每一个创建对象的客户类中,势必会出现代码重复、创建蔓延的问题,而这些客户类其实无须承担对象的创建工作,它们只需使用已创建好的对象就可以了。此时,可以引入工厂类来封装对象的创建逻辑和客户代码的实例化/配置选项。

      使用工厂类还有一个“不是特别明显的”优点,一个类可能拥有多个构造函数,而在Java、C#等语言中构造函数名字都与类名相同,客户端只能通过传入不同的参数来调用不同的构造函数创建对象,从构造函数和参数列表中也许大家根本不了解不同构造函数所构造的产品的差异。但如果将对象的创建过程封装在工厂类中,我们可以提供一系列名字完全不同的工厂方法,每一个工厂方法对应一个构造函数,客户端可以以一种更加可读、易懂的方式来创建对象,而且,从一组工厂方法中选择一个意义明确的工厂方法,比从一组名称相同参数不同的构造函数中选择一个构造函数要方便很多。如图2所示:

设计模式-工厂模式-场景以及优缺点-目的就是应对变化 (国江面试回答的)

       在图2中,矩形工厂类RectangleFactory提供了两个工厂方法createRectangle()和createSquare(),一个用于创建长方形,一个用于创建正方形,这两个方法比直接通过构造函数来创建长方形或正方形对象意义更加明确,也在一定程度上降低了客户端调用时出错的概率。

      那么,有人可能会问,是否需要为设计中的每一个类都配备一个工厂类?答案是:具体情况具体分析。如果产品类很简单,而且不存在太多变数,其构造过程也很简单,此时无须为其提供工厂类,直接在使用之前实例化即可,例如Java语言中的String类,我们就无须为它专门提供一个StringFactory,这样做反而有点像杀鸡用牛刀,大材小用,而且会导致工厂泛滥,增加系统的复杂度

 

二、工厂模式适用的一些场景(不仅限于以下场景):

1. 对象的创建过程/实例化准备工作很复杂,需要初始化很多参数、查询数据库等。

2.类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。 

以下是关于工厂模式的其他的描述:

1.模式描述

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

2.模式作用

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

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

3.适用场景

消费者不关心它所要创建对象的类(产品类)的时候。

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

等等。//TODO

例如:hibernate里通过sessionFactory创建session、通过代理方式生成ws客户端时,通过工厂构建报文中格式化数据的对象。

4.模式要素

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

5.类图

设计模式-工厂模式-场景以及优缺点-目的就是应对变化 (国江面试回答的)

 

6.模式实例代码

工厂:

package com.demoFound.factoryMethod.factory;

import com.demoFound.factoryMethod.message.IMyMessage;

/**
 * 工厂方法模式_工厂接口
 * 
 * @author popkidorc
 * 
 */
public interface IMyMessageFactory {

    public IMyMessage createMessage(String messageType);
}
package com.demoFound.factoryMethod.factory;

import java.util.HashMap;
import java.util.Map;

import com.demoFound.factoryMethod.message.IMyMessage;
import com.demoFound.factoryMethod.message.MyMessageEmail;
import com.demoFound.factoryMethod.message.MyMessageOaTodo;
import com.demoFound.factoryMethod.message.MyMessageSms;

/**
 * 工厂方法模式_工厂实现
 * 
 * @author popkidorc
 * 
 */
public class MyMessageFactory implements IMyMessageFactory {

    @Override
    public IMyMessage createMessage(String messageType) {
        // 这里的方式是:消费者知道自己想要什么产品;若生产何种产品完全由工厂决定,则这里不应该传入控制生产的参数。
        IMyMessage myMessage;
        Map<String, Object> messageParam = new HashMap<String, Object>();
        // 根据某些条件去选择究竟创建哪一个具体的实现对象,条件可以传入的,也可以从其它途径获取。
        // sms
        if ("SMS".equals(messageType)) {
            myMessage = new MyMessageSms();
            messageParam.put("PHONENUM", "123456789");
        } else
        // OA待办
        if ("OA".equals(messageType)) {
            myMessage = new MyMessageOaTodo();
            messageParam.put("OAUSERNAME", "testUser");
        } else
        // email
        if ("EMAIL".equals(messageType)) {
            myMessage = new MyMessageEmail();
            messageParam.put("EMAIL", "test@test.com");
        } else
        // 默认生产email这个产品
        {
            myMessage = new MyMessageEmail();
            messageParam.put("EMAIL", "test@test.com");
        }
        myMessage.setMessageParam(messageParam);
        return myMessage;
    }
}

产品:

package com.demoFound.factoryMethod.message;

import java.util.Map;

/**
 * 工厂方法模式_产品接口
 * 
 * @author popkidorc
 * 
 */
public interface IMyMessage {

    public Map<String, Object> getMessageParam();

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

    public void sendMesage() throws Exception;// 发送通知/消息

}
package com.demoFound.factoryMethod.message;

import java.util.Map;

/**
 * 工厂方法模式_虚拟产品类
 * 
 * @author popkidorc
 * 
 */
public abstract class MyAbstractMessage implements IMyMessage {

    private Map<String, Object> messageParam;// 这里可以理解为生产产品所需要的原材料库。最好是个自定义的对象,这里为了不引起误解使用Map。

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

    @Override
    public void setMessageParam(Map<String, Object> messageParam) {
        this.messageParam = messageParam;
    }
}
package com.demoFound.factoryMethod.message;

/**
 * 工厂方法模式_email产品
 * 
 * @author popkidorc
 * 
 */
public class MyMessageEmail extends MyAbstractMessage {

    @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"));
    }

}
package com.demoFound.factoryMethod.message;

/**
 * 工厂方法模式_oa待办产品
 * 
 * @author popkidorc
 * 
 */
public class MyMessageOaTodo extends MyAbstractMessage {

    @Override
    public void sendMesage() throws Exception {
        // TODO Auto-generated method stub
        if (null == getMessageParam()
                || null == getMessageParam().get("OAUSERNAME")
                || "".equals(getMessageParam().get("OAUSERNAME"))) {
            throw new Exception("发送OA待办,需要传入OAUSERNAME参数");// 为了简单起见异常也不自定义了
        }// 这里的参数需求就比较多了不一一处理了

        System.out
                .println("我是OA待办,发送通知给" + getMessageParam().get("OAUSERNAME"));
    }

}
package com.demoFound.factoryMethod.message;

/**
 * 工厂方法模式_sms产品
 * 
 * @author popkidorc
 * 
 */
public class MyMessageSms extends MyAbstractMessage {

    @Override
    public void sendMesage() throws Exception {
        // TODO Auto-generated method stub
        if (null == getMessageParam()
                || null == getMessageParam().get("PHONENUM")
                || "".equals(getMessageParam().get("PHONENUM"))) {
            throw new Exception("发送短信,需要传入PHONENUM参数");// 为了简单起见异常也不自定义了
        }// 另外短信信息,以及其他各种协议参数等等都要处理

        System.out.println("我是短信,发送通知给" + getMessageParam().get("PHONENUM"));
    }

}

消费者:

package com.demoFound.factoryMethod;

import com.demoFound.factoryMethod.factory.IMyMessageFactory;
import com.demoFound.factoryMethod.factory.MyMessageFactory;
import com.demoFound.factoryMethod.message.IMyMessage;

/**
 * 工厂方法模式_消费者类
 * 
 * @author popkidorc
 * 
 */
public class MyFactoryMethodMain {

    public static void main(String[] args) {
        IMyMessageFactory myMessageFactory = new MyMessageFactory();
        IMyMessage myMessage;
        // 对于这个消费者来说,不用知道如何生产message这个产品,耦合度降低
        try {
            // 先来一个短信通知
            myMessage = myMessageFactory.createMessage("SMS");
            myMessage.sendMesage();

            // 来一个oa待办
            myMessage = myMessageFactory.createMessage("OA");
            myMessage.sendMesage();

            // 来一个邮件通知
            myMessage = myMessageFactory.createMessage("EMAIL");
            myMessage.sendMesage();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

参考:关于工厂模式,一般什么情况下使用?

参考:Java技术_每天掌握一种设计模式(003)_使用场景及简单实例(创建型:工厂方法)

参考:关于工厂模式的作用。为什么要用工厂模式?

参考:创建对象与使用对象——谈谈工厂的作用