设计模式之工厂模式,但是宝可梦

news/2024/11/16 18:54:38 标签: 设计模式, 工厂模式, java

前言

工作一年了,业务代码写太多,还是得自驱提升点技术。希望工作一年后写出来的能有更多自己的思考。

正文

工厂模式是一种创建型设计模式,主要的目的还是在创建一个对象时提供更灵活、更易扩展的机制。

简单工厂模式

情景模拟

小智到商店去买精灵球,精灵球的种类繁多。商店店员会在库房中根据小智的要求拿对应的精灵球。在这个过程中,作为买家,小智不需要关注精灵球放在库房的哪一个位置。

java">/**
* 精灵球可以查看成功率,实现类有普通球和超级球
**/
public interface Ball {
    void showDetails();
}

class NormalBall implements Ball{

    @Override
    public void showDetails() {
        System.out.println("This is a normal ball. 15% to catch");
    }
}

class SuperBall implements Ball{

    @Override
    public void showDetails() {
        System.out.println("This is a super ball. 25% to catch");
    }
}

/**
 * 精灵球商店
 */
public class BallShop {
    public static Ball getBall(int ballCode){
        if (ballCode == 0){
        	// 去放普通球的地方...
            return new NormalBall();
        }
        if (ballCode == 1){
        	// 去放超级球的地方...
            return new SuperBall();
        }
        return null;
    }
}

对比一下直接new创建类和用工厂模式创建类:

java">	Ball ball1 = new NormalBall();
    Ball ball2 = new SuperBall();

	Ball normalBall = BallShop.getBall(0);
    Ball superBall = BallShop.getBall(1);

有一个很直观的区别:我根本不需要关心new的是什么实现类,我只需要传入对应的参数,告诉工厂我需要什么实现类。更规范一点,可以把0,1等数替换为枚举类,这样作为调用方,我无需关注怎么new的,new的什么类,我只需要相信工厂类会返回给我想要的类。

举例

DateFormat是简单工厂模式的经典运用,获取实例对象时,我只需要在getTimeInstance()方法传入类提供的枚举类,我无需关注内部实现细节,传入不同参数,后续的具体实现也就不同。

java">		DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.FULL);
        String format1 = timeInstance.format(new Date());
        DateFormat timeInstance2 = DateFormat.getTimeInstance(DateFormat.DATE_FIELD);
        String format2 = timeInstance2.format(new Date());
        System.out.println(format1);  // 中国标准时间 上午12:37:33
        System.out.println(format2);  // 上午12:37

不足

简单工厂模式对于简单场景是很友好的,实现很简单,如果能确保业务不再扩展,简单工厂模式是很好的选择。然而如果业务有扩展,简单工厂模式的弊端就体现出来了。

如果现在我新增了一个新的球种类——大师球。现在商店里的店员都需要知道大师球放在哪个位置,加重了商店店员的逻辑(工厂内创建逻辑)

java">class MasterBall implements Ball{

    @Override
    public void showDetails() {
        System.out.println("MasterBall!!. 100% to catch");
    }
}

对应的工厂类就要做相应的适配:

java">public class BallShop {
    public static Ball getBall(int ballCode){
        if (ballCode == 0){
            // 去放普通球的地方...
            return new NormalBall();
        }
        if (ballCode == 1){
            // 去放超级球的地方...
            return new SuperBall();
        }
        if (ballCode == 2){
        	// 去放大师球的地方...
            return new MasterBall();
        }
        return null;
    }
}

新增一个if分支的同时打破了开闭原则(对扩展开放,对修改封闭),其次,工厂类涵盖了所有的创建逻辑,高内聚。
为了解决业务会有新增的情况,根据面向对象编程的原则,抽象!引入工厂方法模式

工厂方法模式

场景模拟

由于精灵球的种类不断增多,店员没办法记住每种球的位置(内聚太多逻辑),所以精灵球商店把店面分成了几个区域,每个区域售卖一种球,一个店员负责一个区域,这个店员只需要关注这个区域售卖的球在哪里。作为买家小智,他只需要知道他想买哪种球,然后去对应的分区,他仍然不需要关心球在哪里存放。

于是工厂类修改为:

java">public interface BallShopNew {
    Ball findBall();
}

class NormalBallFactory implements BallShopNew{
    @Override
    public Ball findBall() {
        return new NormalBall();
    }
}

class SuperBallFactory implements BallShopNew{
    @Override
    public Ball findBall() {
        return new SuperBall();
    }
}

class MasterBallFactory implements BallShopNew{
    @Override
    public Ball findBall() {
        return new MasterBall();
    }
}

使用方式:

java">public class FactoryDemo {
    public static void main(String[] args) {
        Ball masterBall = new MasterBallFactory().findBall();
        Ball normalBall = new NormalBallFactory().findBall();
        masterBall.showDetails();
        normalBall.showDetails();
    }
}

现在如果新推出了一种新的球,只需要开辟新的分区,聘请一个新店员(创建新的实现类),遵循了开闭原则。

不足

如果这个时候,产品维度发生了扩展,商店不止卖球了,还要卖贴纸。当产品变成复数,每个工厂就要进行相应的修改来支持新的产品,或者新增对应数量的新的工厂和实现类。系统中的类会变得极其多。抽象工厂模式用于解决复数产品的场景。

抽象工厂模式

抽象工厂模式首先将产品抽象:

java">public interface Label {
    void showColor();
}

class NormalLabel implements Label{
    @Override
    public void showColor() {
        System.out.println("红白配色");
    }
}

class MasterLabel implements Label{
    @Override
    public void showColor() {
        System.out.println("紫色配色");
    }
}

再在抽象工厂中引入抽象产品。

java">public interface BallLabelShop {
    Ball findBall();
    Label findLabel();
}

class NormalBallLabelShop implements BallLabelShop{
    @Override
    public Ball findBall() {
        return new NormalBall();
    }
    @Override
    public Label findLabel() {
        return new NormalLabel();
    }
}

class MasterBallLabelShop implements BallLabelShop{
    @Override
    public Ball findBall() {
        return new MasterBall();
    }
    @Override
    public Label findLabel() {
        return new MasterLabel();
    }
}

使用:

java">public class FactoryDemo {
    public static void main(String[] args) {
        MasterBallLabelShop masterBallLabelShop = new MasterBallLabelShop();
        Ball ball = masterBallLabelShop.findBall();
        Label label = masterBallLabelShop.findLabel();
        ball.showDetails();
        label.showColor();
        
		NormalBallLabelShop normalBallLabelShop = new NormalBallLabelShop();
        Ball ball2 = normalBallLabelShop.findBall();
        Label label2 = normalBallLabelShop.findLabel();
        ball2.showDetails();
        label2.showColor();
    }
}

/**
MasterBall!!. 100% to catch
紫色配色
This is a normal ball. 15% to catch
红白配色
**/

这里可能就有读者会问,这不是和工厂方法模式差不多吗? 其实我的理解是抽象方法模式中,工厂的实现类是将以产品组合为单位的。
上面的例子,两个工厂分别是组合了NormalMaster两种类型,让同一工厂生产的是能配套的产品组合。回到工厂模式的维度,使用者只需要知道什么工厂会给他想要的产品组合。

总结

作为一个细分了三个种类的设计模式,到底该如何取舍?比起直接new一个对象,使用对应模式的好处到底在哪?

  • 简单工厂模式
    1. 根据传入的参数决定产出的对象,可以隐藏一些创建的细节
    2. 适用于需要根据条件创建不同对象的场景。
  • 工厂方法模式:
    1. 将简单工厂转化为抽象工厂的子类,每个子类负责相应对象的创建,将创建逻辑从简单工厂中解耦到各自实现类。
    2. 适用于要创建的对象会出现扩展的场景;或者是希望将创建逻辑分别封装在具体工厂类的场景。
  • 抽象工厂模式
    1. 抽象工厂提供接口,用于创建一系列相关或者相互依赖的对象。
    2. 适用于要创建复数种类的对象;或者是希望将创建逻辑封装在具体工厂类的场景。

http://www.niftyadmin.cn/n/5754531.html

相关文章

【青牛科技】D54123 漏电保护电路介绍及应用

1、具体应用: 相关产品介绍: D54123 应用框图: D54123 方案介绍: 当正常电源电流流过时,电容滤波至少保证 VS端电压为12V R1、R2可根据所用电网交流电压值来选择 C4 应大于 1μF,C2小于 1μF 必须接入 RP&…

掌握C#中的异步编程:async和await关键字详解

C#中的异步编程模式。异步编程是现代应用程序开发中不可或缺的一部分,尤其在处理I/O密集型任务或网络请求时尤为重要。下面是一篇专注于C#异步编程的文章。 前言 随着互联网应用的不断发展,用户对应用程序响应速度的要求越来越高。传统的同步编程模型往…

Casio推出情感AI宠物机器人Moflin

‍‍ Casio最近推出了一款名为Moflin的AI宠物机器人,这款机器人以其独特的情感互动功能吸引了广泛关注。Moflin通过先进的AI技术,能够学习和理解主人的言行,并根据环境和互动的变化调整自己的情感反应。通过与主人的互动,Moflin可…

K8S 查看pod节点的磁盘和内存使用情况

查看某个节点的磁盘使用率: kubectl exec -it pod名称 -n 命名空间 – df -h 查询所有节点的已使用内存: kubectl top pods --all-namespaces | grep itsm 查询某个节点的总内存, kubectl describe pod itsr-domain-59f4ff5854-hzb68 --nam…

Tensorflow基本概念

简介:本文从Graph讲到Session,同时讲解了tf.constant创建tensor的用法和variable需要初始化的知识点,可以给你打好一个学习Tensorflow的基础。本文都是基于TensorFlow1.14.0的版本下运行。 本专栏将会系统的讲解TensorFlow在1.14.0版本下的各…

10款高效音频剪辑工具,让声音编辑更上一层楼。

音频剪辑在音频,视频,广告制作,游戏开发,广播等领域中都有广泛的应用。通过音频剪辑,创作者可以通将不同的音频片段进行剪切、拼接、混音等操作,创作出风格各异的音乐作品。如果你也正在为音频创作而努力的…

Go语言的零值可用性:优势与限制

Go语言以其简洁和高效的设计理念而著称,其中之一便是“零值可用”的特性。这一特性使得许多类型在未显式初始化时即可直接安全地使用,大大简化了代码的初始化过程。然而,并非所有类型都支持零值可用,且在使用时也存在一定的限制。…

如何解决sourcetree 一打开就闪退问题

找到sourcetree的软件位置(图标右击 打开文件位置) 往上找 找到 AppData ,然后进入到这个文件下【\AppData\Local\Atlassian】 删除 sourcetree 的缓存 (可能不止一个 类似这种的都删除)