当前位置: 首页 > article >正文

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

前言

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

正文

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

简单工厂模式

情景模拟

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

/**
* 精灵球可以查看成功率,实现类有普通球和超级球
**/
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创建类和用工厂模式创建类:

	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()方法传入类提供的枚举类,我无需关注内部实现细节,传入不同参数,后续的具体实现也就不同。

		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

不足

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

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

class MasterBall implements Ball{

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

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

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

工厂方法模式

场景模拟

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

于是工厂类修改为:

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();
    }
}

使用方式:

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

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

不足

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

抽象工厂模式

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

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("紫色配色");
    }
}

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

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();
    }
}

使用:

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.kler.cn/a/394076.html

相关文章:

  • 如何保护 Microsoft 网络免受中间人攻击
  • Scala学习记录,case class,迭代器
  • 探索Python的HTTP利器:Requests库的神秘面纱
  • 前端:块级元素和行内元素
  • @ComponentScan:Spring Boot中的自动装配大师
  • 限流算法(令牌通漏桶计数器)
  • 【Node.js]
  • TCON 相关知识
  • Git - 命令杂谈 - merge、rebase和cherry-pick
  • git修改当前分支名称并推送到远程仓库
  • 【新手友好】用Pyspark和GraphX解析复杂网络数据
  • 【数据分享】中国食品工业年鉴(1984-2023) PDF
  • 确保HTML邮件兼容所有PC和移动设备的样式
  • Vue Canvas实现区域拉框选择
  • Jmeter中的配置原件(五)
  • 微服务电商平台课程四: 搭建本地前端服务
  • WPF学习之路,控件的只读、是否可以、是否可见属性控制
  • 〔 MySQL 〕数据类型
  • 基于HTTP编写ping操作
  • Day44 | 动态规划 :状态机DP 买卖股票的最佳时机IV买卖股票的最佳时机III
  • 【大数据学习 | HBASE高级】rowkey的设计,hbase的预分区和压缩
  • redis 原理篇 31 redis内存回收 内存淘汰策略
  • 【混沌系统】洛伦兹吸引子-Python动画
  • vueRouter路由切换时实现页面子元素动画效果, 左右两侧滑入滑出效果
  • 数据分析编程:SQL,Python or SPL?
  • 机器学习—为什么我们需要激活函数