设计模式详解(二):工厂方法模式、抽象工厂模式
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