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

前言

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

正文

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

简单工厂模式

情景模拟

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

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. 适用于要创建复数种类的对象;或者是希望将创建逻辑封装在具体工厂类的场景。
相关推荐
乐之者v5 分钟前
leetCode43.字符串相乘
java·数据结构·算法
大圣数据星球3 小时前
Fluss 写入数据湖实战
大数据·设计模式·flink
suweijie7683 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿4 小时前
List深拷贝后,数据还是被串改
java
思忖小下4 小时前
梳理你的思路(从OOP到架构设计)_设计模式Template Method模式
设计模式·模板方法模式·eit
xlsw_7 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹8 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭8 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫9 小时前
泛型(2)
java
超爱吃士力架9 小时前
邀请逻辑
java·linux·后端