前言

   设计模式是软件工程中一些问题的统一解决方案的模型,它的出现是为了解决一些普遍存在的,却不能被语言特性直接解决的问题,随着软件工程的发展,设计模式也会不断的进行更新,本文介绍的是经典设计模式-简单工厂模式以及来自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的优化的更多相关文章

  1. Java策略模式以及来自lambda的优化

    前言    设计模式是软件工程中一些问题的统一解决方案的模型,它的出现是为了解决一些普遍存在的,却不能被语言特性直接解决的问题,随着软件工程的发展,设计模式也会不断的进行更新,本文介绍的是经典设计模式 ...

  2. !!转!!java 简单工厂模式

    举两个例子以快速明白Java中的简单工厂模式: 女娲抟土造人话说:“天地开辟,未有人民,女娲抟土为人.”女娲需要用土造出一个个的人,但在女娲造出人之前,人的概念只存在于女娲的思想里面.女娲造人,这就是 ...

  3. Java简单工厂模式

    Java简单工厂模式 在阎宏博士的<JAVA与模式>一书中开头是这样描述简单工厂模式的:简单工厂模式是类的创建模式,又叫做静态工厂方法(Static Factory Method)模式.简 ...

  4. 设计模式(二)——Java简单工厂模式

    简单工厂模式 案例: 披萨的项目(要便于披萨种类的扩展,要便于维护) 1)披萨的种类很多(比如 GreekPizz.CheesePizz 等) 2)披萨的制作有 prepare,bake, cut, ...

  5. JAVA简单工厂模式(从现实生活角度理解代码原理)

    简单工厂模式(Simple Factory),说他简单是因为我们可以将此模式比作一个简单的民间作坊,他们只有固定的生产线生产固定的产品.也可以称他为静态工厂设计模式,类似于之前提到过静态代理设计模式, ...

  6. Java简单工厂模式(SimpleFactoryMode)

    何为简单工厂模式? 由一个工厂类根据传入的参数,动态创建并返回相应的具体的实例! 三个构成元素: 1.工厂类 2.抽象产品 3.具体产品 优点: 1.提高扩展性 2.隐藏具体的实现类,并不需要知道产品 ...

  7. java 简单工厂模式实现

    简单工厂模式:也可以叫做静态工厂方法,属于类创建型模式,根据不同的参数,返回不同的类实现. 主要包含了三个角色: A.抽象产品角色 一般用接口 或是 抽象类实现 B.具体的产品角色,具体的类的实现 C ...

  8. Java命令模式以及来自lambda的优化

    前言    设计模式是软件工程中一些问题的统一解决方案的模型,它的出现是为了解决一些普遍存在的,却不能被语言特性直接解决的问题,随着软件工程的发展,设计模式也会不断的进行更新,本文介绍的是经典设计模式 ...

  9. (转) java 简单工厂模式(实现一个计算器)

    package com.simpleFactory; /** * 运算类 * @author Administrator * */ public class Operation { private d ...

随机推荐

  1. 对#ifndef的理解

    由于对#ifndef的用法不太理解,在询问了老师#ifndef的含义以及查找资料后,对#ifndef总结了以下几点: <1> #ifndef是宏定义的一种,是三种预处理功能(宏定义,文件包 ...

  2. 【集美大学1411_助教博客】个人作业2——英语学习APP案例分析 成绩

    个人作业2--英语学习APP案例分析,截止发稿时间全班31人,提交31,未提交0人.有一名同学已经写了作业但忘记提交了,这次给分了,但下不为例.由于助教这周有点忙,所以点评得非常不及时,请同学们见谅. ...

  3. 201521123025<<java程序设计>>第9周学习总结

    1. 本周学习总结 2.书面作业 Q1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免? 经常出现Array ...

  4. 201521123051《Java程序设计》第十三周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  5. Linux下的定时任务 - Cron服务

    最近搞咕自己的笔记系统,虽然现在是个人的使用,对于数据库的数据还是比较少,但是安全还是一个我必须注意的东西. (特别是前段时间中了比特币的病毒之后,更是让我关注了我的主机的安全的问题.) 今天的随记是 ...

  6. Vagrant下共享目录下静态文件(js/jpg/png等)修改完运行报错

    利用Vagrant部署开发环境,使用目录共享模式,在本地磁盘进行开发,通过虚拟机环境运行开发的页面. 接下来打开页面,看上去一切正常,接下来将发生一个神奇的事情,你修改一个css文件,在刷新浏览器,发 ...

  7. Hibernate table schema 的设置与应用

    hibernate在实现实体映射时,DB无需强行指定.部署时会较对DB户名和密码,根据用户名以访问的表完成实体映射.如果一个帐号可以访问一个数据库的下多个表,以oracle为例用户user1下面有表t ...

  8. Java_注解_01_注解(Annotation)详解

    一.注解的概念 Annotation(注解)是插入代码中的元数据(元数据从metadata一词译来,就是“描述数据的数据”的意思),在JDK5.0及以后版本引入.它可以在编译期使用预编译工具进行处理, ...

  9. Android 字体修改,所有的细节都在这里 | 开篇

    版权声明: 本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有. 每周会统一更新到这里,如果喜欢,可关注公众号获取最新文章. 未经允许,不得转载. 序 在 Android 下使 ...

  10. [UIKit学习]07.关于如何选择UIButton、UILable、UIImageView

    如何选择UIButton.UILable.UIImageView 在不添加手势的前提下,只要不涉及到点击和多状态表现就尽量不要选择UIButton