设计模式详解(二):工厂方法模式、抽象工厂模式

2. 工厂方法模式

(1)概念

工厂方法模式的定义是:定义一个用于创建对象的接口,让子类决定实现哪一个类。

即工厂父类负责定义创建产品对象的公共接口,工厂子类负责生成具体的产品对象。

将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

工厂方法模式是简单工厂模式的延伸与改进,既继承了其封装性等优点,又弥补了其缺陷(提高扩展性),使其符合原则的要求。

在工厂方法模式中,每一个具体工厂只能生产一种具体产品。具体工厂与具体产品一一对应。

(2)类图、典型代码

Factory(抽象类或接口):抽象工厂类,声明了工厂方法,用于返回一个产品。抽象工厂是工厂方法模式的核心,它与应用程序无关。

ConcreteFactory(实现类):具体工厂类,实现抽象工厂类中声明的方法,可由客户端调用,返回一个具体产品类的实例。

Product(抽象类或接口):抽象产品类,定义了产品的接口,是产品对象的共同父类或接口。

ConcreteProduct(实现类):具体产品类,实现了抽象产品类的方法,其具体产品由对应的具体工厂创建。

典型代码如下:

//抽象工厂类
public interface Factory {
    public Product factoryMethod();
}
//具体工厂类之一
public class ConcreteFactory implements Factory {
    @Override
    public Product factoryMethod() {
        return new ConcreteFactory();
    }
}

(3)举例

原有一个工厂生产一种电视机,现分为两个子工厂:海尔工厂生产海尔电视机,海信工厂生产海信电视机。

根据工厂方法模式设计类图如下:

实现代码如下:

//抽象产品类,定义所有产品必须实现的方法
public interface TV {
    public void play();
}
//具体产品类1--海尔电视
public class HaierTV implements TV {

    @Override
    public void play() {
        // 海尔电视的功能
        System.out.println("海尔电视播放中...");
    }

}
//具体产品类2--海信电视
public class HisenseTV implements TV {

    @Override
    public void play() {
        // 海信电视的功能
        System.out.println("海信电视播放中...");
    }

}
//抽象工厂类,定义所有工厂必须实现的方法
public interface TVFactory {
    public TV produceTV();
}
//具体产品类1--海尔电视
public class HaierTV implements TV {

    @Override
    public void play() {
        // 海尔电视的功能
        System.out.println("海尔电视播放中...");
    }

}
//具体产品类2--海信电视
public class HisenseTV implements TV {

    @Override
    public void play() {
        // 海信电视的功能
        System.out.println("海信电视播放中...");
    }

}
//客户端调用类--调用具体电视工厂生产对应电视
public class Client {

    public static void main(String[] args) {
        TVFactory factory;
        TV tv;

        // 产生海尔电视并调用其功能
        factory = new HaierTVFactory();
        tv = factory.produceTV();
        tv.play();

        // 产生海信电视并调用其功能
        factory = new HisenseTVFactory();
        tv = factory.produceTV();
        tv.play();
    }

}

输出结果如下:

海尔电视播放中...
海信电视播放中...

如果此时还需要增加一种电视机品牌TCL,则只需要新建一个实现TV接口的具体产品类和一个实现TVFactory接口的具体工厂类即可,不需要修改其他代码:

//新增具体产品类3--TCL电视
public class TCLTV implements TV {

    @Override
    public void play() {
        // TCL电视的功能
        System.out.println("TCL电视播放中...");
    }

}
//新增具体工厂类3--专门生产TCL电视的TCL工厂
public class TCLTVFactory implements TVFactory {

    @Override
    public TV produceTV() {
        // 生产一个TCL电视的对象
        return new TCLTV();
    }

}

在客户端调用类中增加调用TCL工厂生产TCL电视的语句:

        // 产生TCL电视并调用其功能
        factory = new TCLTVFactory();
        tv = factory.produceTV();
        tv.play();

输出结果如下:

海尔电视播放中...
海信电视播放中...
TCL电视播放中...

(4)优缺点、适用场景

工厂方法模式的优点:

  用户只需关心所需产品对应的工厂,无须关心创建细节(屏蔽产品类),甚至不需要知道具体产品的类名;

  典型的解耦框架,高层模块需要知道产品的抽象类,其他的实现类都不用关心;

  良好的封装性,代码结构清晰,优秀的扩展性,同时符合开闭原则。

工厂方法模式的缺点:

  在添加新产品时成对增加了类的个数,增加了系统的复杂度,编译和运行更多的类也会增加系统的开销;

  考虑到可扩展性引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度。

工厂方法模式的适用场景:

  需要灵活、可扩展的框架时;

  当一个类(比如客户端类)不知道所需要的对象的类时(需要知道其对应的工厂);

  一个类通过其子类来确定创建那个对象。

3. 抽象工厂模式

(1)概念

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

抽象工厂模式是工厂方法模式的泛化版,即工厂方法模式只是抽象工厂模式的一种特殊情况。

在抽象工厂模式中,每一个具体工厂可以生产多个具体产品。

(2)类图、典型代码

 

AbstractFactory(抽象类或接口):抽象工厂类,用于声明生产产品的方法。

ConcreteFactory(实现类):具体工厂类,具体实现生产产品的方法,返回一个具体产品。

AbstractProduct(抽象类或接口):抽象产品类,定义了每种产品的功能方法。

ConcreteProduct(实现类):具体产品类,具体实现了抽象产品类定义的功能方法。

典型代码如下:

// 抽象工厂类
public interface AbstractFactory {
    public AbstractProductA createProductA();
    public AbstractProductB createProductB();
}
// 具体工厂类之一
public class ConcreteFactory implements AbstractFactory{
    public AbstractProductA createProductA(){
        return new ConcreteProductA();
    }
    public AbstractProductB createProductB(){
        return new ConcreteProductB();
    }
}

(3)举例

一个电器工厂可以生产多种电器,比如海尔工厂可以生产海尔电视和海尔空调,TCL工厂可以生产TCL电视和TCL空调。

根据抽象工厂模式设计类图如下:

实现代码如下:

//抽象产品类1--电视类
public interface TV {
    public void play();
}
//电视具体产品类1--海尔电视
public class HaierTV implements TV {

    @Override
    public void play() {
        System.out.println("海尔电视播放中...");
    }

}
//电视具体产品类2--TCL电视
public class TCLTV implements TV {

    @Override
    public void play() {
        System.out.println("TCL电视播放中...");
    }

}
//抽象产品类2--空调类
public interface AirConditioner {
    public void changeTemperature();
}
//空调具体产品类1--海尔空调
public class HaierAirConditioner implements AirConditioner {

    @Override
    public void changeTemperature() {
        System.out.println("海尔空调吹风中...");
    }

}
//空调具体产品类2--TCL空调
public class TCLAirConditioner implements AirConditioner {

    @Override
    public void changeTemperature() {
        System.out.println("TCL空调吹风中...");
    }

}
//抽象工厂类,定义所有工厂必须实现的方法
public interface Factory {
    public TV produceTV();

    public AirConditioner produceAirConditioner();
}
//具体工厂类1--海尔工厂
public class HaierFactory implements Factory{

    @Override
    public TV produceTV() {
        return new HaierTV();
    }

    @Override
    public AirConditioner produceAirConditioner() {
        return new HaierAirConditioner();
    }

}
//具体工厂类2--TCL工厂
public class TCLFactory implements Factory{

    @Override
    public TV produceTV() {
        return new TCLTV();
    }

    @Override
    public AirConditioner produceAirConditioner() {
        return new TCLAirConditioner();
    }

}

运行结果如下:

海尔电视播放中...
海尔空调吹风中...
———————————————
TCL电视播放中...
TCL空调吹风中...

(4)优缺点、适用场景

抽象工厂模式的优点:

  隔离了具体类的生成,使客户并不知道什么被创建;

  产品内的约束为非公开状态(比如不同产品的生产比例,这对调用工厂类的高层模块是透明的);

抽象工厂模式的缺点:

  在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品(对接口进行扩展会导致所有子类的修改);

抽象工厂模式的适用场景:

  一个系统不依赖产品类实例如何被创建、组合和表达的细节时;

  系统中有多个产品族,而每次只使用其中某一产品族时;

  属于同一产品族的产品将一起使用;

  多个对象有相同的约束时。

在实际的应用开发中,一般将具体类的类名写入配置文件中,再通过Java的反射机制读取XML格式的配置文件,根据存储在XML文件中的类名字符串生成对象。

新建XML文件config.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <className>xxxFactory</className>
</config>

新建工具类文件XMLUtil.java如下:

package com.test.util;

import java.io.File;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XMLUtil {
    // 该方法用于从XML配置文件中提取类名字符串,并返回一个实例对象
    public static Object getBean() {
        try {
            // 创建DOM文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc = builder.parse(new File(
                    "./src/com/test/util/config.xml"));// 若不在同一路径下,必须指定文件具体路径,用正斜杠/或双反斜杠\

            // 获取包含类名的文本节点
            NodeList nlist = doc.getElementsByTagName("className");
            Node classNode = nlist.item(0).getFirstChild();
            String cName = classNode.getNodeValue();

            // 通过类名生成实例对象并将其返回
            Class c = Class.forName("com.test.factory_method." + cName);// 若不在同一路径下,必须写出类的全名
            Object obj = c.newInstance();
            return obj;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

改进工厂方法模式中的客户端代码如下:

//客户端调用类--调用具体电视工厂生产对应电视
public class Client {

    public static void main(String[] args) {
        TVFactory factory;
        TV tv;

//        // 产生海尔电视并调用其功能
//        factory = new HaierTVFactory();
//        tv = factory.produceTV();
//        tv.play();
//
//        // 产生海信电视并调用其功能
//        factory = new HisenseTVFactory();
//        tv = factory.produceTV();
//        tv.play();
//        
//        // 产生TCL电视并调用其功能
//        factory = new TCLTVFactory();
//        tv = factory.produceTV();
//        tv.play();
        
        // 将具体类名写入配置文件中,再通过Java反射机制读取XML格式文件,根据类名生成对象并返回
        factory = (TVFactory) XMLUtil.getBean();
        tv = factory.produceTV();
        tv.play();
    }

}

将config.xml文件中的“xxxFactory”更改为需要生成对象的类名“HaierTVFactory”,运行客户端结果如下:

海尔电视播放中...

抽象工厂模式中例子的客户端改进与此相同。

 

6大设计原则,与常见设计模式(概述):http://www.cnblogs.com/LangZXG/p/6204142.html

类图基础知识:http://www.cnblogs.com/LangZXG/p/6208716.html

注:转载请注明出处   http://www.cnblogs.com/LangZXG/p/6249425.html

原文地址:https://www.cnblogs.com/LangZXG/p/6249425.html