热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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图
simple-factory-mode

优点

  • 解耦,将需求类与实现类分离开了,通过工厂类进行交互
  • 无论是添加,修改还是删除新的子类,都十分的容易,不会影响到其他的类
  • 复用,子类可以多次复用,而不是每次都需要复制原先的代码

可优化点

  • 依旧是针对switch语句的优化
  • 违背了开闭原则,即增加新的子类之后,原先的工厂类的代码还需要做改动,开放了修改

优化思路

  • 传统使用反射来完成修改的关闭,这里我不想使用反射来完成,试试lambda能否完成它的职责

使用lambda进行优化

前面提到简单工厂模式的封装模式是对对象的构造进行封装,那么如果采用函数接口替换switch语句的话,选择的函数应该是Supplier(无参构造函数) 或者Funtion(有参构造函数),这里我们选择无参构造函数来进行优化,使用Map存储这些构造方法,并利用函数语言的懒加载特性,使得直到真正调用实例化对象的某一方法时,才真正调用构造函数,代码如下。

使用supplier封装构造器优化后的Factory类

public class adventFactory {
    private static final Map>> 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 constructor;

    adventEnum(Supplier constructor) {
        this.constructor = constructor;
    }

    public Supplier 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太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
author-avatar
百变精灵6810
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有