赞
踩
如果有不对的地方请指教,本文属于自己对设计模式的理解。
简单工厂模式不是GoF23种设计模式中的一种,但却是学习其他设计模式的基础。
1.简单工厂模式介绍
在实际的软件开发过程中,有时需要创建一些来自于相同父类的类的实例,为此可以专门定义一个类来负责创建这些类的实例,这些被创建的实例具有共同的父类。在这种情况下,可以通过传入不同的参数从而获得不同的对象,利用Java语言的特性,习惯上将创建其他类实例的方法定义为static方法,外部不需要实例化这个类就可以直接调用该方法来获得需要的对象,该方法也称为静态工厂方法,这样的一个设计模式就是我们将要学习的第一个也是最简单的设计模式之一 简单工厂模式。
2.理解简单工厂模式
简单工厂模式示意图如图所示,用户无需知道苹果、橙、香蕉如何创建,只需要知道水果的名字则可以得到对应的水果。
考虑一个简单的软件应用场景,一个软件系统可以提供多个外观不同的按钮(如圆形按钮、矩形按钮、菱形按钮等),这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得他们可以呈现不同的外观,如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入该方法即可返回一个相应的按钮对象,此时就可以使用简单工厂模式。
简而言之,简单工厂模式通过不同的参数来创建不同的实例化对象。用户无需知道对象的创建细节,只需要知道对象所对应的参数即可。
3.简单工厂模式定义
简单工厂模式(Simple Factory Pattern)定义为:简单工厂模式又称为静态工厂方法(Static Factory Method)模式,他属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
4.简单工厂模式结构
简单工厂模式结构比较简单,其核心是工厂类,下面将学习其模式结构。
简单工厂模式包含如下角色:
(1)Factory(工厂角色)
工厂角色即工厂类,他是简单工厂模式的核心,负责实现创建所有实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),他返回一个抽象产品类Product,所有的具体产品都是抽象产品的子类。
(2)Product(抽象产品角色)
抽象产品角色是简单工厂模式所创建的所有对象的父类,负责描述所有实例所共有的公共接口,他的引入将提高系统的灵活性,使得在工厂类中只需定义一个工厂方法,因为所有创建的具体产品对象都是其子类对象。
(3)ConceteProduct(具体产品角色)
具体产品角色是简单工厂模式的创建目标,所有创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现定义在抽象产品中的抽象方法。
5.简单工厂模式实例
(1)实例说明
某电视机厂专为各个电视机品牌代工生产各类电视机,当需要海尔牌电视机时只需要在调用该工厂的工厂方法时传入参数Haier,需要海信电视机时只需要传入参数Hisense,工厂可以根据传入的不同参数返回不同品牌的电视机。现使用简单工厂模式来模拟电视机工厂的生产过程。
(2)实例类图
(3)代码实例及解释
a.抽象产品类TV(电视机类)
- package cpm.lhp;
-
- public interface TV {
- //TV作为抽象产品类,它可以是一个接口,也可以是一个抽象类,在这里定义为接口,接口用实现
- public void play();
- }
TV作为产品抽象类,它可以是一个接口,也可以是一个抽象类,其中包含了所有产品都具有的业务方法play()。
b.具体产品类HaierTV(海尔电视机类)
- package cpm.lhp;
-
- public class HaierTV implements TV {
-
- @Override
- public void play() {
- System.out.println("海尔电视机播放中...");
- }
- }
HaierTV是抽象产品TV接口的子类,它是一种具体产品,实现了在TV接口中定义的业务方法play()。
c.具体产品类HisenseTV(海信电视机类)
- package cpm.lhp;
-
- public class HisenseTV implements TV {
-
- @Override
- public void play() {
- System.out.println("海信电视机播放中...");
- }
- }
HisenseTV是抽象产品TV接口的另一个子类,即另一种具体产品,不同的具体产品在实现业务方法时有所不同。
d.工厂类TVFactory(电视机工厂类)
- package com.lhp.TV;
-
- public class TVFactory {
-
- public static TV produceTV(String brand) throws Exception {
-
- if (brand.equalsIgnoreCase("Haier")) {
-
- System.out.println("电视机工厂生产海尔电视机");
- return new HaierTV();
- } else if (brand.equalsIgnoreCase("Hisense")) {
-
- System.out.println("电视机工厂生产海信电视机");
- return new HisenceTV();
- } else if (brand.equalsIgnoreCase("TCL")) {
-
- System.out.println("电视机工厂生产TCL电视机");
- return new TCLTV();
- } else {
- throw new Exception("对不起,暂不能生产该品牌电视机");
- }
- }
- }
TVFactory是工厂类他是整个系统的核心,它提供了静态工厂方法produceTV(),工厂方法中包含一个字符串类型的参数,在内部业务逻辑中根据参数值的不同实例化不同的具体产品类,返回相应的对象。
e.辅助代码
作为第一个正式的学习设计模式,为了更好地体现简单工厂模式的特性,再次引入了一个工具类-----XMLUtilTV,通过它可以从XML格式的配置文件中读取节点获取数据,如品牌名称等信息,如果需要修改品牌名称,无须修改客户端代码,只需修改配置文件。通过配置文件可以极大提高系统的扩展性,让软件实体更符合开闭原则。(想了解什么是开闭原则,后面的博客我会介绍)
(1)XML操作工具类XMLUtilTV
- package com.lhp.TV;
-
- import java.io.File;
- import javax.xml.parsers.*;
- import org.w3c.dom.*;
-
- public class XMLUtilTV {
-
- //该方法用于从XML配置文件中提取品牌名称,并返回该品牌名称
- public static String getBrandName() {
- try {
- DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = dFactory.newDocumentBuilder();
- Document doc = builder.parse(new File("configTV.xml"));
-
- //获取包含品牌名称的文本节点
- NodeList n1 = doc.getElementsByTagName("brandName");
- Node classNode = n1.item(0).getFirstChild();
- String brandName = classNode.getNodeValue().trim();
- return brandName;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- }
在该工具类中,通过Java语言提供的DOM(Document Object Model,文档对象模型)API来实现对XML文档的操作,在DOM API中,XML文档以树状结构存储在内存中,可以通过相关的类对XML进行读取、修改等操作。
在XMLUtilTV类中,提供了一个静态方法getBrandName()用于获取存储在XML配置文件configTV,xml中的brandName标签中的内容,即电视机品牌名,再将该品牌名返回给调用该方法的客户端测试类Client。
(2)配置文件congigTV.xml
- <?xml version="1.0"?>
- <config>
- <brandName>Haier</brandName>
- <!--<brandName>TCL</brandName>-->
- </config>
目前大部分软件项目的配置文件都采用XML格式,主要是因为修改XML文件后无需编译源代码即可使用,且为纯文本格式,几乎所有的编译器都可以编辑XMM文档。为了使系统更符合开闭原则和依赖倒转原则,需要做到“将抽象写在代码中,将具体写在配置里”,通过修改无须编译的配置文件来提高系统的可扩展性和灵活性。
在配置文件configTV.xml中,其根节点为config,其中包含一个名为brandName的子节点,用于存储电视机品牌名,通过上述的XMLUtilTV类来读取其中存储的字符串并返回给客户端测试类。
(3)客户端测试类Client
- package com.lhp.TV;
-
- public class Client {
-
- public static void main(String[] arg) {
- try {
- TV tv;//抽象类型定义
- String brandName = XMLUtilTV.getBrandName();
- tv = TVFactory.produceTV(brandName);
- tv.play();
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
-
- }
- }
在Client类中包含一个main函数作为本实例的人口函数,在main函数中,以抽象类型TV来定义电视机对象,通过调用XMLUtilTV类的静态getBrandName()读取存储在XML文档中的电视机品牌字符串,再以该字符串作为实参带人工厂类TVFactory的静态工厂方法produceTV()中,获取对应的产品对象tv。 因为无论是哪种品牌的电视机都是TV类的子类,根据里氏代换原则,父类对象在运行时可以用子类对象来替换,因此程序可以正确执行,可以通过修改配置文件configTV. xml中的brandName节点中的字符串来获取不同品牌的电视机对象tv,不同电视机对象的play()方法将有不同的运行结果。
如果需要更换电视机品牌,无须修改客户端代码及类库代码,只需要修改配置文件即可,提高了系统的灵活性。如果需要增加新类型的电视机,即增加新的具体产品类,则需要修改工厂类,这在一定程度上违反了开闭原则,但是无须修改客户端测试类,还是带来一定程度的灵活性。
5.结果及分析
如果在配置文件中将<brandName>节点中的内容设置为:Haier,则输出结果如下:
如果在配置文件中将<brandName>节点中的内容设置为:TCL,则输出结果如下:
如果希望该系统能够支持TCL牌电视机,则需要增加一个新的具体产品类TCLTV,代码如下:
- package com.lhp.TV;
-
- public class TCLTV implements TV {
-
- public void play() {
- System.out.println("TCL电视机播放中...");
- }
- }
同时还需要修改工厂类TVFactory中的工厂方法,在其判断逻辑中增加一个新的分支,代码如下:
- else if (brand.equalsIgnoreCase("TCL")) {
- System.out.println("电视机工厂生产TCL电视机");
- return new TCLTV();
- } else {
- throw new Exception("对不起,暂不能生产该品牌电视机");
- }
运行结果:
6.模式优缺点
1.简单工厂模式的优点
(1)工厂类含有必要的逻辑判断,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分隔,它提供了专门的工厂类用于创建对象。
(2)客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用类的记忆量。
(3)通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
2.简单工厂模式的缺点
(1)由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
(2)使用简单工厂模式将会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度。
(3)系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
(4)简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构,代码如下:
- package Test;
-
- public class SuperClass {
- public static void display(){
- System.out.println("Super Class");
- }
- }
- package Test;
-
- public class SubClass extends SuperClass {
- public static void display(){
- System.out.println("Sub Class");
- }
- }
- package Test;
-
- public class Client {
- public static void main(String arg[]){
- SuperClass demo;
- demo=new SubClass();
- demo.display();
- }
- }
该代码输出结果为:
也就是说,虽然子类可以继承和覆盖父类的静态方法,但是如果在定义时使用的是父类,即使实例化的是子类也无法访问子类覆盖后的静态方法。这将导致包含静态工厂方法的工厂类无法像产品类一样提供抽象层与抽象定义,也无法通过具体类来进行扩展。
虽然简单工程模式存在种种问题,但他是学习其他工厂模式的一个入门,在一些并不复杂的环境下也经常使用简单工厂模式。
7.模式适用环境
在以下情况下可以使用简单工厂模式
(1)工厂类负责创建的对象比较少;由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
(2)客户端只知道传入工厂类的参数,对于如何创建对象不关心;客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。