Java简单工厂模式以及来自lambda的优化
前言
设计模式是软件工程中一些问题的统一解决方案的模型,它的出现是为了解决一些普遍存在的,却不能被语言特性直接解决的问题,随着软件工程的发展,设计模式也会不断的进行更新,本文介绍的是经典设计模式-简单工厂模式以及来自java8的lambda的对它的优化。
什么是简单工厂模式
概念
定义一个工厂类,对实现了同一接口的一些类进行实例的创建。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例
简单理解
我的理解是工厂模式好比一个容器,里面装了有许多共同特征的对象,通提供过工厂对外提供的方法向外提供实例化子类的功能,和现实的中的工厂很像。简明点说,是许多对象的集合,根据需求对外提供不同的对象。
例子
场景描述
在写了几个设计模式的博客之后我发现每次都要虚构一个不存在的例子很费脑筋,于是我决定后面的例子用我平常喜欢玩的一些游戏来描述,感觉会更有意思:)
在一片古老的魔法大陆上,有许多隐世的秘宝等待探险者去挖掘,可这样的机会往往也伴随着危险,所以探险者们往往需要结伴而行,一般来说,一个不会在野外直接当掉的队伍至少需要保证三种类型的职业(坦克,输出,治疗,俗称'铁三角')。因此,在这样的需求下,久而久之,魔法大路上诞生了一家'冒险者雇佣兵工厂',没有人知道这家工厂是何时诞生,也不知道里面究竟有怎样的实力...只是知道,你给它钱,和你需要的职业,它就会提供一个对应职业的雇佣兵助你完成这次冒险....
有一天,有一个战士(坦克)阿呆收到消息,有一个叫做'火焰洞窟'里面可能有好东西,可他身边没有伙伴一个人显然是不能去送死的,于是为了快速凑到伙伴,他想到了雇佣兵工厂...他需要一个能够释放冰霜法术的法师(输出)(冰属性可以克制火焰洞窟里的怪物)和一个能够疗伤的牧师(治疗)这两个职业,下面在客户端中模拟场景
传统实现
首页抽象坦克,输出,治疗为探险者接口,提供一个战斗的技能的方法
探险者接口
public interface adventurer {
/**
* 使用战斗技能
*/
void useBattleSkill();
}
战士,冰霜法师,牧师实现探险者接口,作为子类提供不同的战斗技能实现
战士类
public class warrior implements adventurer {
@Override
public void useBattleSkill() {
System.out.println("盾牌格挡!");
}
}
冰霜法师类
public class frostMage implements adventurer {
@Override
public void useBattleSkill() {
System.out.println("寒冰箭!");
}
}
牧师类
public class priests implements adventurer {
@Override
public void useBattleSkill() {
System.out.println("快速治疗!");
}
}
冒险者工厂类,根据不同的职业需求实例化不同的冒险者给客户端
public class adventFactory {
public static adventurer createAdventurer(String professionType) {
adventurer adventurer;
switch (professionType) {
case "战士":
adventurer = new warrior();
break;
case "冰霜法师":
adventurer = new frostMage();
break;
case "牧师":
adventurer = new priests();
break;
default:
throw new IllegalArgumentException("我们没这种职业!");
}
return adventurer;
}
}
客户端类,模拟三个职业进入火焰洞窟并使用各自的技能
public class Client {
public static void main(String[] args) {
//通过冒险者工厂实例化出战士,冰霜法师,牧师
adventurer warrior = adventFactory.createAdventurer("战士");
adventurer frostMage = adventFactory.createAdventurer("冰霜法师");
adventurer priest = adventFactory.createAdventurer("牧师");
//进入火焰洞窟
System.out.println("================进入火焰洞窟================");
warrior.useBattleSkill();
frostMage.useBattleSkill();
priest.useBattleSkill();
}
}
控制台结果
================进入火焰洞窟================
盾牌格挡!
寒冰箭!
快速治疗!
如同上文所讲,雇佣兵工厂通过switch语句根据不同的输出实例化不同的对象给客户端调用,这样客户端只需要和工厂打交道,有什么需求提供给工厂,工厂实例化出对应对象返回,所以工厂可以理解为是对象实例化的集合。
总结与思考
总结
为了增加趣味性(主要是我自己的..编例子很无聊T_T),本文使用了MMORPG游戏的铁三角的组队进副本的例子,冒险者工厂为冒险者提供不同职业的冒险者,冒险者不需要与具体的同伴沟通,通过工厂就可以完成需求,可以说是将需求者与雇佣兵这两类人给解耦了,通过冒险工厂来交互。从封装角度来说,之前写的命令模式,策略模式都是对行为的封装,而工厂模式是对对象构造器的封装,这一点也为后面的lambda的优化选择接口提供了依据。
下面是uml图

优点
- 解耦,将需求类与实现类分离开了,通过工厂类进行交互
- 无论是添加,修改还是删除新的子类,都十分的容易,不会影响到其他的类
- 复用,子类可以多次复用,而不是每次都需要复制原先的代码
可优化点
- 依旧是针对switch语句的优化
- 违背了开闭原则,即增加新的子类之后,原先的工厂类的代码还需要做改动,开放了修改
优化思路
- 传统使用反射来完成修改的关闭,这里我不想使用反射来完成,试试lambda能否完成它的职责
使用lambda进行优化
前面提到简单工厂模式的封装模式是对对象的构造进行封装,那么如果采用函数接口替换switch语句的话,选择的函数应该是Supplier<T>(无参构造函数) 或者Funtion<T,R>(有参构造函数),这里我们选择无参构造函数来进行优化,使用Map存储这些构造方法,并利用函数语言的懒加载特性,使得直到真正调用实例化对象的某一方法时,才真正调用构造函数,代码如下。
使用supplier封装构造器优化后的Factory类
public class adventFactory {
private static final Map<String, Optional<Supplier<adventurer>>> MAP = new ConcurrentHashMap<>();
static {
MAP.put("战士", Optional.of(warrior::new));
MAP.put("冰霜法师", Optional.of(frostMage::new));
MAP.put("牧师", Optional.of(priests::new));
}
public static adventurer createAdventurer(String professionType) {
//get(professionType)获得optional对象,orElseThrow用于防止或者异常参数,get()及早求值,执行对象的实例化,直到这一步函数才真正的执行
return MAP.get(professionType)
.orElseThrow(() -> new IllegalArgumentException("我们工厂没这种职业!"))
.get();
}
}
客户端代码与原先一模一样,这里就不显示了,下面说明一下这个Factory类。
使用supplier函数接口将构造器封装,并存储在MAP中,注意这里与传统的直接存实例好的对象进去不同,这里存储的只是构造过程,并不会真正的占用空间,除非客户端调用create方法需要这个对象了,才会实例化出来,这里利用了函数的懒加载特性。同时为了防止可恶的空指针异常或者是需求并不存在的类,在supplier的基础上使用了optional类进行包装,避免了各类if判断,可以看出使用了lambda优化之后,已经不存在任何的条件判断语句(switch,if)了,将面向对象与函数语言特性相结合,感觉很不错。
枚举的进一步优化
前面提到可优化点的时候提到了简单工厂方法违背了开闭原则,然而经过lambda优化之后的方式虽然消除了switch与if分支,但是似乎并没有克服这个问题,工厂类依旧是违背这个原则的,那么可不可能再次优化呢?我认为这种需要传入魔法值来做一些事情的方法或者设计模式,枚举都是一个不错的选择,下面尝试使用枚举。
使用枚举变量封装这些构造器,这样不仅可以使得工厂可以将修改关闭,同时也省去了optional类的包装,因为你传入的参数只能是枚举变量已经定义好的。下面是代码。
枚举类,内部存一个supplier对象,存放各大职业的构造器,对外暴露getConstructor方法进行实例化
public enum adventEnum {
WARRIOR(warrior::new),
MAGE_FROST(frostMage::new),
PRIESTS(priests::new);
private final Supplier<adventurer> constructor;
adventEnum(Supplier<adventurer> constructor) {
this.constructor = constructor;
}
public Supplier<adventurer> getConstructor() {
return constructor;
}
}
工厂类
public class adventFactory {
public static adventurer createAdventurer(adventEnum adventEnum) {
adventEnum.getConstructor().get();
}
}
工厂类十分简洁,然而不仅简洁,还完美继承了上面的所有优势,并且克服了劣势。
客户端
import static com.lambda.enums.adventEnum.*;
public class Client {
public static void main(String[] args) {
//通过冒险者工厂实例化出战士,冰霜法师,牧师
adventurer warrior = adventFactory.createAdventurer(WARRIOR);
adventurer frostMage = adventFactory.createAdventurer(MAGE_FROST);
adventurer priest = adventFactory.createAdventurer(PRIESTS);
//进入火焰洞窟
System.out.println("================进入火焰洞窟================");
warrior.useBattleSkill();
frostMage.useBattleSkill();
priest.useBattleSkill();
}
}
客户端的调用参数变成了枚举类,这里静态导入枚举类,我一直觉得使用枚举变量的代码拥有一种自注释的特性,即不需写注释就可以看的很明了。
结尾
麻雀虽小,五脏俱全,例子很简单,但是最后的成果是面向对象语言+函数式语言+枚举的结合,可以看到这种组合效果是十分棒的,代码不仅简洁易用性高同时还保持了健壮性与可扩展性,希望大家可以多尝试,我认为多种语言范式的组合的语言可能是第三代语言或者更新的语言发展的趋势吧(Scala,C#等)_,大家下篇再见。
关于本文代码
本文的代码与md文章同步更新在github中的simple-factory-mode模块下,欢迎fork
Java简单工厂模式以及来自lambda的优化的更多相关文章
- Java策略模式以及来自lambda的优化
前言 设计模式是软件工程中一些问题的统一解决方案的模型,它的出现是为了解决一些普遍存在的,却不能被语言特性直接解决的问题,随着软件工程的发展,设计模式也会不断的进行更新,本文介绍的是经典设计模式 ...
- !!转!!java 简单工厂模式
举两个例子以快速明白Java中的简单工厂模式: 女娲抟土造人话说:“天地开辟,未有人民,女娲抟土为人.”女娲需要用土造出一个个的人,但在女娲造出人之前,人的概念只存在于女娲的思想里面.女娲造人,这就是 ...
- Java简单工厂模式
Java简单工厂模式 在阎宏博士的<JAVA与模式>一书中开头是这样描述简单工厂模式的:简单工厂模式是类的创建模式,又叫做静态工厂方法(Static Factory Method)模式.简 ...
- 设计模式(二)——Java简单工厂模式
简单工厂模式 案例: 披萨的项目(要便于披萨种类的扩展,要便于维护) 1)披萨的种类很多(比如 GreekPizz.CheesePizz 等) 2)披萨的制作有 prepare,bake, cut, ...
- JAVA简单工厂模式(从现实生活角度理解代码原理)
简单工厂模式(Simple Factory),说他简单是因为我们可以将此模式比作一个简单的民间作坊,他们只有固定的生产线生产固定的产品.也可以称他为静态工厂设计模式,类似于之前提到过静态代理设计模式, ...
- Java简单工厂模式(SimpleFactoryMode)
何为简单工厂模式? 由一个工厂类根据传入的参数,动态创建并返回相应的具体的实例! 三个构成元素: 1.工厂类 2.抽象产品 3.具体产品 优点: 1.提高扩展性 2.隐藏具体的实现类,并不需要知道产品 ...
- java 简单工厂模式实现
简单工厂模式:也可以叫做静态工厂方法,属于类创建型模式,根据不同的参数,返回不同的类实现. 主要包含了三个角色: A.抽象产品角色 一般用接口 或是 抽象类实现 B.具体的产品角色,具体的类的实现 C ...
- Java命令模式以及来自lambda的优化
前言 设计模式是软件工程中一些问题的统一解决方案的模型,它的出现是为了解决一些普遍存在的,却不能被语言特性直接解决的问题,随着软件工程的发展,设计模式也会不断的进行更新,本文介绍的是经典设计模式 ...
- (转) java 简单工厂模式(实现一个计算器)
package com.simpleFactory; /** * 运算类 * @author Administrator * */ public class Operation { private d ...
随机推荐
- 201521123055 《Java程序设计》第5周学习总结
1. 本章学习总结 2. 书面作业 Q1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改正该错误.并分析输出结果. 1. ...
- Java 课程设计 "Give it up"小游戏(团队)
JAVA课程设计 "永不言弃"小游戏(From :Niverse) 通过Swing技术创建游戏的登陆注册界面,使用mySQL数据库技术完成用户的各项信息保存和游戏完成后的成绩保存. ...
- 201521123087《Java程序设计》第14周学习总结
1. 本周学习总结 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名)在自己建立的数据库上执行常见SQL语句(截图)-参考 ...
- 今天的第一个程序-南阳acm输入三个数排序
#include<stdio.h>main(){ int a,b,c,t; scanf("%d%d%d",&a,&b,&c); ...
- 2017年9月19日 JavaScript语法操作
关于JavaScript个人还是觉得比较有意思的 {在</html>后面写或者在</body>前面写(必须紧贴</body>)} <script> va ...
- Activiti第三篇【连接、排他网关、指定任务处理人、组任务】
连线 上面我们已将学过了流程变量了,可以在[任务服务.运行时服务.流程开始.完成某个任务时设置流程变量],而我们的连接就是流程变量的实际应用了-. 定义流程图 我们并不是所有的流程都是按一条的路径来走 ...
- Struts2-在js中使用struts2标签
1, 支行是下拉列表,自助银行也是下拉列表,它们是级联关系; <tr> <th width="17%"><span>*</span> ...
- xml跟sql查找
xml小白笔记 ....... <sql id="wDishesColumns"> a.id AS "id", a.pid AS "pid ...
- oracle 权限
一.介绍这一部分我们主要看看oracle中如何管理权限和角色,权限和角色的区别在哪里.当刚刚建立用户时,用户没有任何权限,也不能执行任何操作.如果要执行某种特定的数据库操作,则必须为其授予系统的权限: ...
- 100%解决ios上audio不能自动播放的问题
由于ios的安全机制问题,不允许audio和video自动播放,所以想要使audio标签自动播放那是实现不了的,即使给play()也是播放不了. 解决方法: 首先,创建audio标签:<audi ...