JAVA设计模式--简单工厂模式

我们都知道工厂是用来生产产品的,在程序语言中所谓的工厂就是为我们创建实例化对象的,工厂模式专门负责将大量有共同接口的类实例化,程序中可以动态决

将哪一个类实例化,工厂模式在一般的书中都认为分为两种,一种是简单工厂模式,另一种是工厂方法模式。

1、简单工厂模式结构

看图中的三类角色:

抽象产品角色:担任这个角色的类一般是工厂模式所创建的对象的父类,抽象产品角色可以用一个Java接口或者抽象类来实现。

具体产品角色:工厂中创建的所有实例都是具体产品角色的实例对象。

工厂角色:担任这个角色的是工厂方法模式的核心,工厂类根据传入参量来动态决定创建哪一个具体产品对象,与外部类有着紧密的业务联系,这个角色往往

由一个java类实现。

举个切实相关的例子,假如我们在做项目的时候需要一个导出的功能,系统要支持可以导出word、pdf、excel等格式的文件。这时候我们就按照简单工厂模式来

设计这个程序:

抽象产品角色我们用一个抽象类来实现,抽象类中包含一个抽象的导出方法,包含一些基本属性,比如说文件名称。

具体产品角色:ExportWord、ExportPdf、ExportExcel三个JAVA类都继承抽象类,然后实现父类中的抽象方法。

工厂中我们通过客户端传递过来的参数决定具体去创建哪个子类的实例化对象。

2、多层次的产品结构

在系统中我们还会遇到更为复杂的产品结构,举个例子:


针对这种具有多层级产品的情况,简单工厂模式采用的是以不变应万变的策略,一律使用同一工厂类,这样做有利有弊,下面就具体说一下它的利弊:

优点:设计简单,产品类的等级结构不会反应到工厂类来

缺点:其一,这个工厂类包含了所有产品的创建逻辑,形成了一个无所不知的全能类,当这个类出问题的时候,客户端与整个产品链的联系都中断了;

      其二,当我们要增加一种产品的时候,必须要修改工厂类,功能拓展比较麻烦,不满足我们设计程序的"开--闭"原则。

      其三,简单工厂模式使用静态方法作为工厂方法,静态方法无法由子类继承,因此,工厂角色无法基于继承的等级结构。

注:"开--闭"原则,一个软件应当对外拓展开放,对修改关闭,也就是说我们在设计一个模块的时候,应当让这个模块具有不被修改的前提下可拓展的

能力。听着挺别扭,举个书里写的例子:

玉帝招安美猴王,将猴子封为弼马温,我们将”玉帝管理天庭秩序“看做一个软件实体,那么他给猴子封官,纳进自己的团队,这就是”开“,另一方面

这一做法避免了猴子破坏天庭秩序,这就是”闭“。

3、简单工厂模式在Java中的应用

书中有一个典型的例子,就是我们常用来进行日期格式化的DateFormat类

看一段代码:

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
public class SimpleFactoryDemo1 {
	public static void main(String[] args) throws ParseException {
		Locale locale =Locale.CHINA;
		Date d=new Date();
		System.out.println(d);
		System.out.println(DateFormat.getDateInstance(DateFormat.DEFAULT,locale).format(d)); 
	}
}
这段代码是获取了一个中文格式的日期。

乍一看,DateFormat.getDateInstance()好像是在获取自己的实例对象,其实不是,看JDK源代码很清楚的看到DateFormat类是抽象类,是无法进行实例化的,

那么我们看一下到底什么情况?

getDateFormat源代码如下:

    /**
     * Gets the date formatter with the default formatting style
     * for the default locale.
     * @return a date formatter.
     */
    public final static DateFormat getDateInstance()
    {
        return get(0, DEFAULT, 2, Locale.getDefault());
    }

    /**
     * Gets the date formatter with the given formatting style
     * for the default locale.
     * @param style the given formatting style. For example,
     * SHORT for "M/d/yy" in the US locale.
     * @return a date formatter.
     */
    public final static DateFormat getDateInstance(int style)
    {
        return get(0, style, 2, Locale.getDefault());
    }

    /**
     * Gets the date formatter with the given formatting style
     * for the given locale.
     * @param style the given formatting style. For example,
     * SHORT for "M/d/yy" in the US locale.
     * @param aLocale the given locale.
     * @return a date formatter.
     */
    public final static DateFormat getDateInstance(int style,
                                                 Locale aLocale)
    {
        return get(0, style, 2, aLocale);
    }
我们看到三种getDateFormat方法最终都调用了get()方法,我们再跟踪一下这个get方法:

    /**
     * Creates a DateFormat with the given time and/or date style in the given
     * locale.
     * @param timeStyle a value from 0 to 3 indicating the time format,
     * ignored if flags is 2
     * @param dateStyle a value from 0 to 3 indicating the time format,
     * ignored if flags is 1
     * @param flags either 1 for a time format, 2 for a date format,
     * or 3 for a date/time format
     * @param loc the locale for the format
     */
    private static DateFormat get(int timeStyle, int dateStyle,
                                  int flags, Locale loc) {
        if ((flags & 1) != 0) {
            if (timeStyle < 0 || timeStyle > 3) {
                throw new IllegalArgumentException("Illegal time style " + timeStyle);
            }
        } else {
            timeStyle = -1;
        }
        if ((flags & 2) != 0) {
            if (dateStyle < 0 || dateStyle > 3) {
                throw new IllegalArgumentException("Illegal date style " + dateStyle);
            }
        } else {
            dateStyle = -1;
        }
        try {
            // Check whether a provider can provide an implementation that's closer 
            // to the requested locale than what the Java runtime itself can provide.
            LocaleServiceProviderPool pool =
                LocaleServiceProviderPool.getPool(DateFormatProvider.class);
            if (pool.hasProviders()) {
                DateFormat providersInstance = pool.getLocalizedObject(
                                                    DateFormatGetter.INSTANCE,
                                                    loc, 
                                                    timeStyle,
                                                    dateStyle,
                                                    flags);
                if (providersInstance != null) {
                    return providersInstance;
                }
            }

            return new SimpleDateFormat(timeStyle, dateStyle, loc);
        } catch (MissingResourceException e) {
            return new SimpleDateFormat("M/d/yy h:mm a");
        }
    }
看到了吧,最终返回的是SimpleDateFormat类的实例化对象,因为SimpleDateFormat是DateFormat的子类,所以getDateInstance()方法的返回类型可以是

DateFormat类,这也是多态性的一种体现。

实例总结:由于使用了简单工厂模式,客户端完全不用关心工厂方法所返还的对象时怎么创建和构成的,工厂方法将实例化哪些对象和具体如何实例化这些对象的

细节隐藏起来,使得这些对象的使用简化起来。与一般的简单工厂模式不同的是,上面这个例子工厂角色与抽象产品角色合并为一个类DateFormat,也就是抽象

产品角色负责了具体产品的创建,这也是简单工厂模式的一个特例

以后想到什么应用实例再补充…………

-----------------------------------------------------------------------------以下内容续写与2015.12.10----------------------------------------------------------------------------------------------

实际项目中有个实例,场景是服务器端向客户端发送不同类型的消息,然后客户端对收到的消息进行解析处理。

设计的时候我们让这些不同类型的消息都继承同一个类:BaseProtocolMessage。

package com.hirain.testmanagement.protocol.model;

/**
 * 服务器与客户端之间通信消息的基类.
 * @author haicheng.cao
 */
public abstract class BaseProtocolMessage implements Serializable {
}

然后我们定义一个解析消息的抽象类:AnalysisService,也就是我们的抽象产品类。

package com.hirain.hinats.analysis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hirain.hinats.handler.ClientHandler;
import com.hirain.testmanagement.protocol.model.BaseProtocolMessage;

/**
 * 抽象类,提供解析INTA发送来的消息的接口,具体实现由子类实现.
 * 
 * @author haicheng.cao
 * 
 */
public abstract class AnalysisService {

	/**
	 * 客户端监听.
	 */
	public ClientHandler clientHandler;

	/**
	 * 日志记录.
	 */
	public Logger logger = LoggerFactory.getLogger(AnalysisService.class);

	/**
	 * 抽象方法,由子类实现,解析消息内容.
	 * 
	 * @param msg 消息内容
	 * @return 响应值
	 */
	public abstract BaseProtocolMessage analysisMessage(BaseProtocolMessage msg);

	/**
	 * SET 方法.
	 * 
	 * @param clientHandler 客户端监听
	 */
	public void setClientHandler(ClientHandler clientHandler) {
		this.clientHandler = clientHandler;
	}

}
然后就是具体产品类继承抽象产品类,来实现对不同类型消息的解析:

看几个具体产品类的例子:

package com.hirain.hinats.analysis;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.hirain.hinats.util.CreateCarDirUtil;
import com.hirain.testmanagement.common.constant.HiNatsConstant;
import com.hirain.testmanagement.common.hinatsutil.HiFileTranUtil;
import com.hirain.testmanagement.protocol.model.BaseProtocolMessage;
import com.hirain.testmanagement.protocol.model.TestSelectInfo;

/**
 * 解析select(所选用例)消息并生成ini文件.
 * 
 * @author haicheng.cao
 * 
 */
public class AnalysisSelect extends AnalysisService {

	@Override
	public BaseProtocolMessage analysisMessage(BaseProtocolMessage msg) {
		// 创建车型目录下相关文件夹
		CreateCarDirUtil.createDir(msg.getCarName());
		// 生成ini文件
		TestSelectInfo select = (TestSelectInfo) msg;
		List<String> prints = new ArrayList<String>();
		prints.add("[TC_SELECT]");
		if (select != null) {
			List<String> casecode = select.getCaseCode();
			if (casecode != null && casecode.size() > 0) {
				prints.add("TestCaseSum=" + casecode.size());
				int count = 0;
				for (String cas : casecode) {
					prints.add("TestCase" + count + "=" + cas);
					count++;
				}
			}
		}
		String path = HiFileTranUtil.getIniDirPath(select.getCarName(), select.getSpecShortName());
		HiFileTranUtil.writeTxt(path + File.separator + HiNatsConstant.SELECTINI, prints);
		logger.info("生成select ini文件成功:" + path + File.separator + HiNatsConstant.SELECTINI);
		return null;
	}
}
package com.hirain.hinats.analysis;

import java.io.File;
import java.util.List;
import com.hirain.testmanagement.common.constant.HiNatsConstant;
import com.hirain.testmanagement.common.hinatsutil.HiFileTranUtil;
import com.hirain.testmanagement.protocol.model.BaseProtocolMessage;
import com.hirain.testmanagement.protocol.model.GatewayIniInfo;

/**
 * 解析网关路由消息并生成INI文件.
 * 
 * @author haicheng.cao
 * 
 */
public class AnalysisGateway extends AnalysisService {

	@Override
	public BaseProtocolMessage analysisMessage(BaseProtocolMessage msg) {
		GatewayIniInfo gwInfo = (GatewayIniInfo) msg;
		List<String> data = gwInfo.getGatewayInfo();
		String path = HiFileTranUtil.getIniDirPath(gwInfo.getCarName(), gwInfo.getSpecShortName());
		HiFileTranUtil.writeTxt(path + File.separator + HiNatsConstant.GATEWAY_INI, data);
		logger.info("生成gateway ini文件成功:" + path + File.separator + HiNatsConstant.GATEWAY_INI);
		return null;
	}
}
产品类我们都设计好了,接下来就得设计我们的核心工厂类了,看代码就可以了

package com.hirain.hinats.analysis;

import com.hirain.testmanagement.protocol.model.BootloaderFileTranInfo;
import com.hirain.testmanagement.protocol.model.BootloaderParaInfo;
import com.hirain.testmanagement.protocol.model.BootloaderTestRunInfo;
import com.hirain.testmanagement.protocol.model.DiagTestRunInfo;
import com.hirain.testmanagement.protocol.model.DnCheckCANStress;
import com.hirain.testmanagement.protocol.model.DnConfigIniInfo;
import com.hirain.testmanagement.protocol.model.DtcWriteInfo;
import com.hirain.testmanagement.protocol.model.EMTestRunInfo;
import com.hirain.testmanagement.protocol.model.FileTranInfo;
import com.hirain.testmanagement.protocol.model.GatewayIniInfo;
import com.hirain.testmanagement.protocol.model.ParameterIniInfo;
import com.hirain.testmanagement.protocol.model.TestRunInfo;
import com.hirain.testmanagement.protocol.model.TestSelectInfo;

/**
 * 工厂类,获得解析消息产品实例对象.
 * 
 * @author haicheng.cao
 * 
 */
public class AnalysisFactory {

	/**
	 * 获得实例对象的方法.
	 * 
	 * @param message 消息,根据消息类型返回不同的实例化对象.
	 * @return AnalysisService的子类实例化对象
	 */
	public static AnalysisService getInstance(Object message) {
		AnalysisService analysis = null;
		if (message instanceof TestSelectInfo) {
			analysis = new AnalysisSelect();
		} else if (message instanceof ParameterIniInfo) {
			analysis = new AnalysisPara();
		} else if (message instanceof DtcWriteInfo) {
			analysis = new AnalysisDtc();
		} else if (message instanceof TestRunInfo) {
			analysis = new AnalysisTestRun();
		} else if (message instanceof DiagTestRunInfo) {
			analysis = new AnalysisDiagTestRun();
		} else if (message instanceof GatewayIniInfo) {
			analysis = new AnalysisGateway();
		} else if (message instanceof FileTranInfo) {
			analysis = new AnalysisTranFile();
		} else if (message instanceof EMTestRunInfo ) {
			analysis = new AnalysisEMRun();
		} else if (message instanceof BootloaderParaInfo) {
			analysis = new AnalysisBootloaderPara();
		} else if (message instanceof BootloaderFileTranInfo) {
			analysis = new AnalysisBootloaderTranFile();
		} else if (message instanceof BootloaderTestRunInfo) {
			analysis = new AnalysisBootloaderTestRun();
		} else if (message instanceof DnConfigIniInfo) {
			analysis = new AnalysisDnInfo();
		} else if (message instanceof DnCheckCANStress) {
			analysis = new AnalysisCheckDn();
		}
		return analysis;
	}
}

接下来贴上外部客户端调用的代码:

	/**
	 * 接收到消息触发事件.
	 * 
	 * @param session 会话.
	 * @param message 消息.
	 */
	@Override
	public void messageReceived(IoSession session, Object message) {
		System.out.println("Received Message: " + message);
		this.session = session;
		if (message instanceof BaseProtocolMessage) {
			BaseProtocolMessage baseMessage = (BaseProtocolMessage) message;
			//从工厂中获取解析类
			AnalysisService analysis = AnalysisFactory.getInstance(message);
			analysis.setClientHandler(this);
			// 解析消息
			BaseProtocolMessage retrMsg = analysis.analysisMessage(baseMessage);
			// 如果有对应的响应消息,就将响应消息发送给服务器.
			if (retrMsg != null) {
				session.write(retrMsg);
			}
		}
	}

以上就是我在实际开发中对这个简单工厂模式的应用,还算蛮合理的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值