浅谈设计模式 - 简单工厂模式(二)

简介: 浅谈设计模式 - 简单工厂模式(二)

前言:



对于学习设计模式,我推荐:《HeadFirst设计模式》《大话设计模式》。另外设计模式推崇学以致用。看到任何知识之前,先想想我能学到什么,带着问题去看待问题,将会使得学习事半功倍,否则就是事倍功半。

不要过分拘泥于设计模式的类和形式,只要记住一点:将变与不变抽离的过程就是设计模式


为什么设计模式学了就忘?


  • 不敢尝试(当然也不要过度自信,看到代码就想用设计模式)
  • 过于关注设计模式的结构,忘记了业务本身。
  • 很多时候我们拘泥于形式和设计,纠结于用什么设计模式,其实设计模式本身就是继承,封装,多态的三者结合,很多时候只要可以解决问题,就不需要用过多的技巧
  • 学习之前神志不清
  • 大致就是邯郸学步,看到别人学设计模式,自己也跑去学设计模式

很多人学了设计模式之后隔了一段时间之后,发现自己不使用,忘得一干二净(我也是)。所以希望这些设计模式更多的是结合一些比较实际一点的需求(尽量),毕竟设计模式学了就是拿来用的,如果不用不如不要学,去看点动漫电视剧啥的放松一下。


什么是简单工厂模式?



现实理解:


简单工厂从字面意思来看,就如同我们平常的工厂一般,我们想要重复的生产某样物品,就需要建设工厂不断生产。我们需要给工厂下达指令,比如生产一批“苹果”,生成一批“香蕉”。我们只需要只会工厂生产,而不需要去理会内部的细节。


工厂模式:


简单工厂模式,是一种创建型设计模式,定义简单工厂,负责为具体操作对象生成需要的操作类,把创建对象和使用对象进行分开,使用对象方只需要传入调用简单工厂的工厂方法进行创建对象。


简单工厂的特点:


  1. 返回抽象的接口或者父类,由工厂管理子类创建过程
  2. 让创建过程变成一个黑盒
  3. 封闭创建过程,客户端只需要关注结果。


工厂模式优缺点:


优点:

  1. 使用创建工厂的方法,我们实现了获取具体对象和生产对象的解耦,由生产对象的工厂通过我们传入的参数生产对应的对象,调用方只需要传递需要生产的对象来实现具体的效果。
  2. 解耦了创建和被创建的过程。
  3. 根据不同的逻辑判断生成不同的具体对象。


缺点:


  1. 每增加一个工厂对象具体的实现类,就需要增加if/else不利于维护
  2. 大量的子类会造成工厂类的迅速膨胀和臃肿
  3. 简单工厂的方法一般处理简单的业务逻辑,如果创建逻辑复杂不建议使用。


实际案例:



下面的案例是个人理解,可能存在偏差,不同人理解有差异。欢迎给出建议。


场景模拟:


我们以经典的任天堂游戏坦克大战为例,在进入游戏的关卡的时候,会出现我方的坦克和敌人的坦克,我方坦克和地方坦克不仅形状不同,而且很脆,但是敌人的坦克根据颜色需要打好几枪才会毁灭,那么如果用代码来模拟是什么样的呢?


不使用设计模式:


根据场景,我设计了如下的图表


网络异常,图片无法展示
|


按照正常方式,我们的定义了一个坦克的父类,接着我们需要定义三个子类来继承父类坦克,以实现自己的扩展。当我们需要创建坦克的时候,我们需要纠结所有的细节,比如到底是创建我方坦克还是敌人坦克,我方的坦克位置,敌人的坦克位置,我方的血量,敌人的血量,等等,从创建坦克到销毁坦克的所有过程,都由我们进行参与。


+ 坦克抽象类 Tank.java
+ 老鼠坦克 MouseTank.java
+ 我方坦克 MyTank.java
+ 巨型坦克 BigTank.java
+ 测试类 Main.java
复制代码


具体的代码实现如下:

  • 坦克的抽象类:


/**
 * 坦克的抽象类,定义坦克的行为
 *
 * @author zxd
 * @version 1.0
 * @date 2021/1/25 0:14
 */
public abstract class Tank {
    /**
     * 坦克hp
     */
    protected int hp;
    /**
     * 坦克子弹
     */
    protected List<Object> bullet;
    /**
     * 移动的方法s
     */
    abstract void move();
    /**
     * 攻击
     */
    abstract void attack();
    /**
     * 停止
     */
    abstract void stop();
}
复制代码


  • 老鼠坦克


/**
 * 老鼠坦克
 *
 * @author zxd
 * @version 1.0
 * @date 2021/1/25 22:02
 */
public class MouseTank extends Tank implements Runnable {
    public void display() {
        System.err.println("长得尖尖的,很像老鼠");
    }
    public MouseTank() {
        // 坦克假设只有一条命
        hp = 1;
        new Thread(this).start();
        bullet = new ArrayList<>();
        // 初始化添加六发子弹
        bullet.add(new Object());
        bullet.add(new Object());
        bullet.add(new Object());
        bullet.add(new Object());
        bullet.add(new Object());
    }
    @Override
    void move() {
        System.err.println("老鼠坦克移动");
    }
    @Override
    void attack() {
        System.err.println("老鼠坦克开枪");
        // ..弹出子弹
        if (bullet.size() <= 0) {
            System.err.println("老鼠坦克没有子弹了");
            return;
        }
        // 老鼠坦克一次性开两枪
        bullet.remove(bullet.get(bullet.size() - 1));
    }
    @Override
    void stop() {
        System.err.println("停止");
    }
    @Override
    public void run() {
        while (true) {
            // 一旦创建就开始移动
            move();
            // 漫无目的开枪
            attack();
            attack();
            // 做完一轮操作歇一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 随机停止
            if (new Random(100).nextInt() % 2 == 0) {
                stop();
            }
        }
    }
}
复制代码


  • 巨型坦克


/**
 * 巨型坦克
 *
 * @author zxd
 * @version 1.0
 * @date 2021/1/25 22:14
 */
public class BigTank extends Tank implements Runnable{
    public void display() {
        System.err.println("巨型坦克");
    }
    public BigTank() {
        // 带颜色的坦克有很多条命
        hp = 5;
        new Thread(this).start();
        bullet = new ArrayList<>();
        // 初始化添加三发子弹
        bullet.add(new Object());
        bullet.add(new Object());
        bullet.add(new Object());
    }
    @Override
    void move() {
        System.err.println("巨型坦克移动");
    }
    @Override
    void attack() {
        System.err.println("巨型坦克开枪");
        // ..弹出子弹
        if (bullet.size() <= 0) {
            System.err.println("巨型坦克没有子弹了");
            return;
        }
        // 老鼠坦克一次性开两枪
        bullet.remove(bullet.get(bullet.size() - 1));
    }
    @Override
    void stop() {
        System.err.println("巨型坦克停止");
    }
    @Override
    public void run() {
        while (true) {
            // 一旦创建就开始移动
            move();
            // 漫无目的开枪
            attack();
            // 做完一轮操作歇两秒,
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 随机停止,活动没有老鼠坦克频繁
            if (new Random(1000).nextInt() % 2 == 0) {
                stop();
            }
        }
    }
}
复制代码


  • 我方坦克


/**
 * 我方坦克
 *
 * @author zxd
 * @version 1.0
 * @date 2021/1/25 21:58
 */
public class MyTank extends Tank{
    public MyTank() {
        // 我方坦克假设只有一条命
        hp = 1;
        bullet = new ArrayList<>();
        // 初始化添加三发子弹
        bullet.add(new Object());
        bullet.add(new Object());
        bullet.add(new Object());
    }
    @Override
    void move() {
        System.err.println("移动");
    }
    @Override
    void attack() {
        System.err.println("攻击地方坦克");
        // ..弹出子弹
        if(bullet.size() == 0){
            System.err.println("没有子弹了");
            return;
        }
        bullet.remove(bullet.get(bullet.size() -1));
    }
    @Override
    void stop() {
        System.err.println("停止");
    }
}
复制代码


  • 测试类:

建议使用单元测试,这里图方便没有用


/**
     * 这种频繁的new,让我们逐渐变成面向过程编程。。。。
     * @param args
     */
    public static void main(String[] args) {
        // 虽然我们可以自己生产坦克,但是我们每次都需要自己手动去生产对应的坦克。这种频繁的new 操作
        Tank bigTank1 = new BigTank();
        Tank bigTank2 = new BigTank();
        Tank bigTank3 = new BigTank();
        Tank bigTank4 = new BigTank();
        // 有多少个对象,就有多少个new
        Tank mouseTank1 = new MouseTank();
        Tank mouseTank2 = new MouseTank();
        Tank mouseTank3 = new MouseTank();
        Tank mouseTank4 = new MouseTank();
        // 我方坦克,需要自己操作
        Tank myTank1 = new MyTank();
        Tank myTank2 = new MyTank();
    }/*//运行结果:
        停止
        老鼠坦克移动
        老鼠坦克开枪
        老鼠坦克开枪
        停止
        老鼠坦克移动
        老鼠坦克开枪
        巨型坦克移动
        巨型坦克开枪
        老鼠坦克开枪
        老鼠坦克没有子弹了
        停止
        老鼠坦克移动
        老鼠坦克开枪
        老鼠坦克没有子弹了
        老鼠坦克开枪
        老鼠坦克没有子弹了
        巨型坦克移动
        巨型坦克开枪
    */
复制代码


上面的代码有什么问题:


咋看一下好像没啥问题呀,我们既有定义抽象的父类,同时又定义了子类去继承,在需要的时候我们直接new就是了。


其实问题就出在new这一步,可以说我们写烂代码的第一步就是new。因为我们掉进了“细节”的陷阱,下面我们分析一下我们的代码有什么问题:


  1. 我要加一个坦克,虽然可以继承,但是如果要加入到战场,需要我们记住新坦克,并且new出来
  2. 我想要老鼠坦克,却不小心new了一个普通地方坦克,当代码较少的时候可能没啥问题,但是如果代码多了,我们要花大量时间查找
  3. 我们的测试类掌控了一切,他的活太重了,不仅需要new,还需要new之后的所有操作。


用简单工厂模式改进:


既然知道了有什么问题,那么我们可以加入一个简单工厂类来管理坦克的创建过程


网络异常,图片无法展示
|


增加工厂类 TankFactory.java

用工厂来管理具体的坦克创建过程:


/**
 * 坦克工厂,专门负责生产坦克
 *
 * @author zxd
 * @version 1.0
 * @date 2021/1/25 22:27
 */
public class TankFactory {
    /**
     * 创建坦克
     * @return
     */
    public Tank createTank(String check){
        Tank tank = null;
        if(Objects.equals(check, "my")){
            tank = new MyTank();
        }else if(Objects.equals(check, "mouse")){
            tank = new MouseTank();
        }else if (Objects.equals(check, "big")){
            tank = new BigTank();
        }else {
            throw new UnsupportedOperationException("当前坦克不支持生产");
        }
        return tank;
    }
}
复制代码


我们重写单元测试:


/**
 * 单元测试
 *
 * @author zxd
 * @version 1.0
 * @date 2021/1/25 22:15
 */
public class Main {
    /**
     * 我们将生产坦克的过程全部交给了工厂来处理
     * 可能还是奇怪,这和刚才没有什么区别呀?
     * 我们来看下区别:
     * 1. 创建的过程没有了,虽然是一个简单的new,但是new的过程交给了工厂
     * 2. 我们后续如果要在坦克加入别的东西,只需要去改工厂类和具体的实现类,不需要该此处代码
     * 3. 如果不支持的操作,工厂还可以通知我们这样做不对
     * @param args
     */
    public static void main(String[] args) {
        TankFactory tankFactory = new TankFactory();
        Tank my = tankFactory.createTank("my");
        Tank mouse = tankFactory.createTank("mouse");
        Tank big = tankFactory.createTank("big");
        // 我要一个没有的设计过的坦克
        Tank mybig = tankFactory.createTank("mybig");
    }/*//
    运行结果:
    Exception in thread "main" 老鼠坦克移动
    巨型坦克移动
    老鼠坦克开枪
    巨型坦克开枪
    老鼠坦克开枪
    java.lang.UnsupportedOperationException: 当前坦克不支持生产
  at com.headfirst.factory.use.TankFactory.createTank(TankFactory.java:27)
  at com.headfirst.factory.use.Main.main(Main.java:33)
    */
}
复制代码


改进之后有什么变化:

  1. 首先,我们把创建的具体过程交给了工厂,不在需要关注创建的细节
  2. 如果需要修改创建的过程,不需要改客户端代码,只需要修改工厂的代码
  3. 扩展同样只需要继承工厂的生产抽象对象即可。


简单工厂模式在spring中的体现:


@Bean注解让我们可以在被Spring管理的对象定义Bean的创建过程,而此时这个类就类似一个工厂,对象的创建细节被封装在具体的方法之中,同时这种方式也是一种单例设计模式,我们定义的@Bean是单例的,在需要的地方可以使用Spring的注解进行注入而不需要自己new对象。


总结:


案例可能不是十分贴切,因为仅仅只有一个new方法是不需要用工厂模式的,但是这里是个人思考之后觉得最能够联想到的情况,就使用了坦克这个例子作为文章的主体。

相关文章
|
区块链 Python
图片转ico
图片转ico
538 0
|
JSON 前端开发 数据格式
vue-cli3读取本地json文件
vue-cli3读取本地json文件
297 1
|
2月前
|
运维 负载均衡 安全
别再混为一谈了!一文读懂内网穿透的三大技术:VPN、反向代理与零信任
总而言之,三者并非简单的替代关系,而是演进与互补。理解其核心差异,才能为企业构建起既高效又安全的远程访问体系。
|
2月前
|
机器学习/深度学习 传感器 算法
【无人机三维路径规划】PSO无人机路径规划3D城市(Matlab实现)
【无人机三维路径规划】PSO无人机路径规划3D城市(Matlab实现)
176 0
|
Kubernetes 开发者 容器
"Kubernetes的生死抉择:揭秘Pod容器重启策略如何决定应用命运的惊天大戏"
【8月更文挑战第20天】Kubernetes (k8s) 是一个强大的容器编排平台,其中Pod是最小的运行单元。Pod的重启策略确保服务连续性,主要有Always(总是重启)、OnFailure(失败时重启)和Never(从不重启)。默认策略为Always。根据不同场景,如Web服务、批处理作业或一次性任务,可以选择合适的策略。K8s还支持健康检查等高级机制来控制容器重启。合理配置这些策略对维护应用稳定性至关重要。
358 4
|
Shell Linux 开发工具
"开发者的救星:揭秘如何用adb神器征服Android设备,开启高效调试之旅!"
【8月更文挑战第20天】Android Debug Bridge (adb) 是 Android 开发者必备工具,用于实现计算机与 Android 设备间通讯,执行调试及命令操作。adb 提供了丰富的命令行接口,覆盖从基础设备管理到复杂系统操作的需求。本文详细介绍 adb 的安装配置流程,并列举实用命令示例,包括设备连接管理、应用安装调试、文件系统访问等基础功能,以及端口转发、日志查看等高级技巧。此外,还提供了常见问题的故障排除指南,帮助开发者快速解决问题。掌握 adb 将极大提升 Android 开发效率,助力项目顺利推进。
461 0
|
Web App开发 数据可视化 搜索推荐
博士科研最好用的科研绘图工具有哪些?
该博客介绍了几种博士科研中最好用的科研绘图工具,包括ChiPlot、Veusz、Echarts、MeedPeer和Python可视化库,并提供了它们的优缺点分析。
759 2
博士科研最好用的科研绘图工具有哪些?
|
Kubernetes 负载均衡 网络协议
在K8S中,Service的类型有哪几种,请说⼀下他们的用途?
在K8S中,Service的类型有哪几种,请说⼀下他们的用途?
|
安全 数据安全/隐私保护 Python
版权保卫战的新武器!揭秘数字水印如何成为知识产权的守护神!
【8月更文挑战第22天】数字水印技术在知识产权保护中至关重要。它通过在数字媒体中嵌入不可见信息(如版权标识),在不影响原内容的前提下实现作品的版权保护、防篡改及非法分发追踪。本文将概述数字水印的概念、技术原理(包括空间域与频域方法),并提供Python代码示例展示水印的嵌入与提取过程。此外,还将分享一个出版社如何运用数字水印成功维护自身版权的真实案例,以此展现数字水印在实际应用中的价值与潜力。
714 0
|
存储 缓存 监控
通用研发提效问题之动态调整干预能力,如何解决
通用研发提效问题之动态调整干预能力,如何解决