Java设计模式(5:设计模式的分类及工厂模式详解)
一、设计模式的分类
总的来说,设计模式可以分为三大类:创建型模式、结构型模式、行为型模式,具体如下图:
二、工厂模式
工厂模式分为简单工厂模式、工厂方法模式和抽象工厂模式。其中简单工厂模式并不属于23种设计模式,但并不影响它的广泛使用。在JDK
的源码当中,就存在着许多这样的例子。
2.1 简单工厂模式
我们先来看一段代码:
public static void main(String[] args) {
// 日历类
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("当前时间为:" + simpleDateFormat.format(calendar.getTime()));
calendar.add(Calendar.HOUR,2);
System.out.println("当前时间加了两个小时后,时间是: " + simpleDateFormat.format(calendar.getTime()));
}
这段代码,大家应该比较熟悉,通过对Calendar
的一系列操作,打印出当前时间和当前时间加两个小时后的时间,这里我们来看看结果:
结果正和我们想象的一样,两次打印出来的时间相隔两个小时。但我们今天的重点是Calendar calendar = Calendar.getInstance()
这段代码,通过getInstance()
方法拿到了Calendar
类的实例。来看看具体的源代码:
public static Calendar getInstance(){
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
// 代码不全,有兴趣的朋友可以去看JDK源码
private static Calendar createCalendar(TimeZone zone, Locale aLocale){
// 中间的代码省略.....
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
// 中间的代码省略.....
return cal;
}
可以看出,getInstance()
方法里面调用了createCalendar()
方法来得到Calendar
类的实例,最后返回给调用者。而createCalendar()
方法中通过switch(){case}
的判断来返回所对应的Calendar
类的实例,这其实就是简单工厂模式的一种应用。
看完简单工厂模式在JDK
中的应用之后,我们来设计一下自己的例子:
小明家新开了一家小工厂,接了一单生意,帮助海尔(Haier)集团生产冰箱,并需要设计相应的方案。小明本身也是程序员出身,思考一会后就写出了下面的代码:
/**
* 冰箱
*/
public interface IFridge {
// 生产冰箱
public void createFridge();
}
/**
* 海尔
*/
public class Haier implements IFridge {
@Override
public void createFridge() {
System.out.println("生产海尔冰箱...");
}
}
客户端调用代码:
public static void main(String[] args) {
IFridge iFridge = new Haier();
iFridge.createFridge();
}
看上面的代码,父类IFridge
类指向子类Haier
类的引用,应用层需要依赖于Haier
。如果业务扩展,后续增加格力(Gree
)甚至更多,那么客户端这里的代码会越来越臃肿。所以,我们要想办法将这种依赖减弱,将创建IFridge
对象的细节隐藏掉。我们用简单工厂模式优化一下:
创建Gree
格力类
/**
* 格力
*/
public class Gree implements IFridge {
@Override
public void createFridge() {
System.out.println("生产格力冰箱...");
}
}
创建FridgeFactory
工厂类
/**
* 冰箱工厂
*/
public class FridgeFactory {
// 创建对应的 IFridge 实例
public static IFridge createFridge(String name){
if ("haier".equals(name)){
return new Haier();
} else if ("gree".equals(name)){
return new Gree();
}
return null;
}
}
修改客户端调用的代码:
public static void main(String[] args) {
// 海尔
IFridge haier = FridgeFactory.createFridge("haier");
haier.createFridge();
// 格力
IFridge gree = FridgeFactory.createFridge("gree");
gree.createFridge();
}
这样来看,虽然代码多了,但维护起来以及扩展起来就方便很多,来看一看类图:
当然,上面的FridgeFactory
代码中依旧有些问题,如果我们需要增加生产美的(Midea
)冰箱,那么我们就需要去修改createFridge()
方法的代码,显然违背了开闭原则,我们来改造一下:
修改FridgeFactory
工厂类
/**
* 冰箱工厂
*/
public class FridgeFactory {
// 创建对应的 IFridge 实例
public static IFridge createFridge(String className){
try {
if (null != className && !"".equals(className)){
// 反射
return (IFridge)Class.forName(className).newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
修改客户端调用的代码
public static void main(String[] args) {
// com.xxx.Haier 换成 自己 项目中 Haier 所在的位置 海尔
IFridge haier = FridgeFactory.createFridge("com.xxx.Haier");
haier.createFridge();
// com.xxx.Gree 换成 自己 项目中 Gree 所在的位置 格力
IFridge gree = FridgeFactory.createFridge("com.xxx.Gree");
gree.createFridge();
}
优化之后,我们再也不需要随着业务的提升而去修改FridgeFactory
类中的代码了。但是依旧有一个问题,createFridge()
方法中的参数是字符串,如果有人乱填怎么办,那不就报错了,所以再来优化一下:
修改FridgeFactory
工厂类
/**
* 冰箱工厂
*/
public class FridgeFactory {
// 创建对应的 IFridge 实例
public static IFridge createFridge(Class<? extends IFridge> clazz){
try {
if (clazz != null){
return clazz.newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
修改客户端调用的代码
public static void main(String[] args) {
// 海尔
FridgeFactory.createFridge(Haier.class).createFridge();
// 格力
FridgeFactory.createFridge(Gree.class).createFridge();
}
再来看一下类图:
简单工厂模式虽然好用,但也有它的局限性:工厂类的职责过重,不利于扩展更为复杂产品结构。
2.2 工厂方法模式
定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。
在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符合开闭原则。
随着小明家新工厂的生意火爆,各类的订单都纷涌而至,各个牌子的厂家都想让小明家的工厂生产冰箱,小明无奈只能开了分工厂,并根据客户的品牌名给工厂取了对应的名字,其中海尔工厂生产海尔的冰箱,格力工厂生产格力的冰箱,美的工厂生产美的的冰箱。用代码演化就是下面这般:
IFridgeFactory
类接口
public interface IFridgeFactory {
public IFridge createIFridge();
}
海尔
// 海尔 工厂
public class HaierFactory implements IFridgeFactory {
@Override
public IFridge createIFridge() {
return new Haier();
}
}
格力
// 格力 工厂
public class GreeFactory implements IFridgeFactory {
@Override
public IFridge createIFridge() {
return new Gree();
}
}
美的
/**
* 美的
*/
public class Midea implements IFridge {
@Override
public void createFridge() {
System.out.println("生产美的冰箱...");
}
}
// 美的
public class MideaFactory implements IFridgeFactory {
@Override
public IFridge createIFridge() {
return new Midea();
}
}
客户端调用:
public static void main(String[] args) {
// 格力
new GreeFactory().createIFridge().createFridge();
// 海尔
new HaierFactory().createIFridge().createFridge();
// 美的
new MideaFactory().createIFridge().createFridge();
}
这里其实就是细化了工厂,将业务拆分,利用了设计模式原则中的单一职责原则,让每个品牌对应工厂只干一件事,不去掺和其他品牌的事情。来看一看类图:
工厂方法模式适用于一下场景:
- 创建对象需要大量重复的代码
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 一个类通过其子类来指定创建哪个对象
工厂方法模式也有缺点:
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
2.3 抽象工厂模式
定义:提供一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类。
这个定义读起来相当的拗口,很抽象,不好理解。还是和上面的例子结合来说明:
在生产完一批冰箱并上市售卖之后,美的、格力、海尔等公司非常满意,慢慢的将自己家的空调、热水器也交给小明家的工厂去生产了。小明为此在对应的品牌工厂有开辟了对应的生产设备的空间(这里为了大家看的方便,我将所有的代码都放上去):
冰箱、空调、热水器接口
// 冰箱
public interface IFridge {
// 生产冰箱
public void createFridge();
}
// 空调
public interface IAirConditioner {
// 生产空调
public void createAirConditioner();
}
// 热水器
public interface IWaterHeater {
// 生产热水器
public void createWaterHeater();
}
海尔
/**
* 海尔 冰箱
*/
public class HaierFridge implements IFridge{
@Override
public void createFridge() {
System.out.println("生产海尔冰箱...");
}
}
// 海尔 空调
public class HaierAirConditioner implements IAirConditioner {
@Override
public void createAirConditioner() {
System.out.println("生产海尔空调...");
}
}
// 海尔热水器
public class HaierWaterHeater implements IWaterHeater {
@Override
public void createWaterHeater() {
System.out.println("生产海尔热水器...");
}
}
格力
/**
* 格力 冰箱
*/
public class GreeFridge implements IFridge {
@Override
public void createFridge() {
System.out.println("生产格力冰箱...");
}
}
// 格力 空调
public class GreeAirConditioner implements IAirConditioner {
@Override
public void createAirConditioner() {
System.out.println("生产格力空调...");
}
}
// 格力热水器
public class GreeWaterHeater implements IWaterHeater {
@Override
public void createWaterHeater() {
System.out.println("生产格力热水器...");
}
}
美的
/**
* 美的 冰箱
*/
public class MideaFridge implements IFridge{
@Override
public void createFridge() {
System.out.println("生产美的冰箱...");
}
}
// 美的 空调
public class MideaAirConditioner implements IAirConditioner {
@Override
public void createAirConditioner() {
System.out.println("生产美的空调...");
}
}
// 美的热水器
public class MideaWaterHeater implements IWaterHeater {
@Override
public void createWaterHeater() {
System.out.println("生产美的热水器...");
}
}
工厂接口
public interface IFactory {
// 冰箱
public IFridge createIFridge();
// 空调
public IAirConditioner createIConditioner();
// 热水器
public IWaterHeater createIWaterHeater();
}
海尔工厂
// 海尔 工厂
public class HaierFactory implements IFactory {
// 冰箱
@Override
public IFridge createIFridge() {
return new HaierFridge();
}
// 空调
@Override
public IAirConditioner createIConditioner() {
return new HaierAirConditioner();
}
// 热水器
@Override
public IWaterHeater createIWaterHeater() {
return new HaierWaterHeater();
}
}
格力工厂
// 格力
public class GreeFactory implements IFactory {
// 冰箱
@Override
public IFridge createIFridge() {
return new GreeFridge();
}
// 空调
@Override
public IAirConditioner createIConditioner() {
return new GreeAirConditioner();
}
// 热水器
@Override
public IWaterHeater createIWaterHeater() {
return new GreeWaterHeater();
}
}
美的工厂
// 美的
public class MideaFactory implements IFactory {
// 冰箱
@Override
public IFridge createIFridge() {
return new MideaFridge();
}
// 空调
@Override
public IAirConditioner createIConditioner() {
return new MideaAirConditioner();
}
// 热水器
@Override
public IWaterHeater createIWaterHeater() {
return new MideaWaterHeater();
}
}
客户端调用
public static void main(String[] args) {
// 海尔工厂
HaierFactory haierFactory = new HaierFactory();
haierFactory.createIFridge().createFridge();
haierFactory.createIConditioner().createAirConditioner();
haierFactory.createIWaterHeater().createWaterHeater();
// 格力工厂
GreeFactory greeFactory = new GreeFactory();
greeFactory.createIFridge().createFridge();
greeFactory.createIConditioner().createAirConditioner();
greeFactory.createIWaterHeater().createWaterHeater();
// 美的工厂
MideaFactory mideaFactory = new MideaFactory();
mideaFactory.createIFridge().createFridge();
mideaFactory.createIConditioner().createAirConditioner();
mideaFactory.createIWaterHeater().createWaterHeater();
}
类图
从上面一大堆的代码,尤其是类图,我们可以很明显的感觉到,抽象工厂可以完美清晰的描述海尔、格力、美的三个品牌的冰箱、空调、热水器的庞大体系。但也正因为如此,抽象工厂给我们的视觉冲击有些大,能很明显的感觉到系统的复杂性、抽象性以及系统的极难扩展性;并且这里还隐藏着一个违背开闭原则的问题:
在工厂接口
IFactory
类中,如果在日后的产品升级当中,需要增加生产洗衣机的业务,那这里修改之后,所有实现IFactory
接口的类都需要变动,很大程度增加了系统的不稳定性。
也正因为如此,在实际的业务开发中,我们不应该有着强烈的强迫症和洁癖,认为一个系统的结构设计必须要完美的符合各种原则。要结合实际的业务去思考,如果系统结构的等级更新不频繁的话,不遵守某些原则也是有必要性的,毕竟所有的技术都是为业务而服务的。
Java设计模式(5:设计模式的分类及工厂模式详解)的更多相关文章
- JAVA 设计模式之 工厂模式详解
一.简单工厂模式 简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类 的实例.属于创建型模式,但它不属于 GOF,23 种设计模式 (参考资料: ht ...
- JS设计模式——工厂模式详解
它的领域中同其它模式的不同之处在于它并没有明确要求我们使用一个构造器.取而代之,一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象的类型. 简单工厂模式:使用一个类(通常 ...
- 抽象工厂模式详解 —— head first 设计模式
项目实例 假设你有一家 pizza 店,你有很多种 pizza,要在系统中显示你所有 pizza 种类.实现这个功能并不难,使用普通方式实现: public class PizzaStore { Pi ...
- java之简单工厂模式详解
设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于 ...
- 设计模式之第2章-抽象工厂模式(Java实现)
设计模式之第2章-抽象工厂模式(Java实现) “上次是我的不对,贿赂作者让我先讲来着,不过老婆大人大人有大量,不与我计较,这次还让我先把上次未讲完的应用场景部分给补充上去,有妻如此,夫复何求.”(说 ...
- 《设计模式面试小炒》策略和工厂模式替代业务场景中复杂的ifelse
<设计模式面试小炒>策略和工厂模式替代业务场景中复杂的ifelse 我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统, ...
- Java开源生鲜电商平台-盈利模式详解(源码可下载)
Java开源生鲜电商平台-盈利模式详解(源码可下载) 该平台提供一个联合买家与卖家的一个平台.(类似淘宝购物,这里指的是食材的购买.) 平台有以下的盈利模式:(类似的平台有美菜网,食材网等) 1. 订 ...
- Java和Ibatis调用存储过程并取得返回值详解
Java和Ibatis调用存储过程并取得返回值详解 2011-07-19 17:33 jiandanfeng2 CSDN博客 字号:T | T 本文主要介绍了Java和Ibatis调用存储过程的方法, ...
- [转]JAVA环境变量JAVA_HOME、CLASSPATH、PATH设置详解
[转] JAVA环境变量JAVA_HOME.CLASSPATH.PATH设置详解 - dreamman的日志 - 网易博客http://blog.163.com/dreamman_yx/blog/st ...
随机推荐
- 修改Maven项目默认JDK版本
问题: 1.创建maven项目的时候,jdk版本是1.5版本,而自己安装的是1.7或者1.8版本. 2.每次右键项目名-maven->update project 时候,项目jdk版本变了,变回 ...
- 【技巧】使用xshell和xftp连接centos连接配置
说明:xshell用来执行指令,xftp用来上传和下载文件. ① 这是xshell连接属性: ②.这是xftp连接属性 附件:这里给个xshelll和xftp的免安装的破解版本地址.侵删. 度娘链接: ...
- 手写一个LRU工具类
LRU概述 LRU算法,即最近最少使用算法.其使用场景非常广泛,像我们日常用的手机的后台应用展示,软件的复制粘贴板等. 本文将基于算法思想手写一个具有LRU算法功能的Java工具类. 结构设计 在插入 ...
- StreamReader & StreamWriter
这节讲StreamReader & StreamWriter,这两个类用于操作字符或者字符串,它将流的操作封装在了底层,相对来说用法比较简单,但是它不支持Seek()方法. 先看一下代码: F ...
- 通过CRM系统改变传统工作模式
在现在这个互联网时代,同行业的竞争越发激烈,因此许多企业都选择使用CRM来提高企业的销售业绩.CRM客户关系管理系统是能够优化企业的销售流程.维护良好的客户关系.对销售流程进行管理的强大工具.但是很多 ...
- 了解常用数据库MySQL、Oracle、MongoDB
本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net 注:转载文章 什么是数据库 简单的说,数据库(英文 Dtabase)就是一个存放数据的仓库,这个仓库是按照一定的数据结果( ...
- Java堆的理解
堆的核心概述 所有的对象实例以及数组都应当在运行时分配在堆上 从实际实用角度看 --"几乎所有的对象实例都在堆中分配内存" 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这 ...
- 『动善时』JMeter基础 — 19、JMeter配置元件【随机变量】
目录 1.随机变量介绍 2.随机变量界面详解 3.随机变量的使用 (1)测试计划内包含的元件 (2)线程组界面内容 (3)随机变量界面内容 (4)HTTP请求界面内容 (5)查看结果 1.随机变量介绍 ...
- Spring Cloud Alibaba Nacos Discovery 实战
Nacos 作为服务注册中心,可以快速简单的将服务自动注册到 Nacos 服务端,并且能够动态无感知的刷新某个服务实例的服务列表,为分布式系统提供服务注册与发现功能 一.创建服务 1.创建项目 pom ...
- HTML的表格元素
一.HTML的表格元素 1.table元素 <table> 标签定义 HTML 表格.简单的 HTML 表格由 table 元素以及一个或多个 tr.th 或 td 元素组成.tr 元素定 ...