设计模式之工厂模式,史上最强,不服来辩!

设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,如果设计模式没学会,抽象能力肯定就不会太强。常见的设计模式有 23 种,今天我们只聊最简单的工厂模式。

工厂模式是属于创建型模式的,通过工厂获取一个一个的新对象。说白了,工厂就是用来 new(创建)对象的,因此把它化分到创建型这一类中。

简单工厂模式

简单工厂模式,正如其名,和名字一样简单,非常简单。下面先看一段代码:

public class FoodFactory {
    public static Food makeFood(String name) {
        if ("kuaican".equals(name)) {
            Food kuaican = new KuaiCanFood();
            kuaican.setPrice(20.00);
            return kuaican;
        } 

        if ("hamburger".equals(name)) {
            Food hamburger = new HamburgerFood();
            hamburger.setPrice(22.00);
            hamburger.setMeat("beef");
            return hamburger;
        } 

        // ......

        return new RuntimeException("not food");
    }
}

需要注意的是,上面中的 KuaiCanFood 和 HamburgerFood 都继承自 Food。

简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。

我们强调职责单一原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。

Java 各种框架中,简单工厂模式是非常常见的,比如JedisConnectionFactory

Redis连接工厂
Redis连接工厂

基本上 Java 中需要创建连接的框架,都是用了工厂模式。

工厂模式

简单工厂模式很简单,在一般情况下,它都能满足我们的需要。但是,在某些特殊场景,它就满足不了我们的需求了。还是拿 Redis 为例,Redis 有单机模式,主从模式,哨兵模式,Cluster 模式。每种模式的连接都不一样,因此我们需要引入多个工厂。

这种情况下,就需要引入多个简单工厂模式。比如:JedisConnectionFactoryJredisConnectionFactoryLettuceConnectionFactorySrpConnectionFactory等等。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。

还是以 Food 为例。

public interface FoodFactory {
    Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
    @Override
    public Food makeFood(String name) {
        if ("套餐A".equals(name)) {
            Food kuaican = new KuaiCanFood();
            kuaican.setPrice(20.00);
            kuaican.setName("快餐");
            return kuaican;
        } 
        
        if ("套餐B".equals(name)) {
            Food mifan = new MiFanFood();
            mifan.setPrice(18.00);
            mifan.setName("五常大米");
            return mifan;
        } 
        
        return null;
    }
}
public class AmericanFoodFactory implements FoodFactory {
    @Override
    public Food makeFood(String name) {
        if ("套餐A".equals(name)) {
            Food hamburger = new HamburgerFood();
            hamburger.setPrice(22.00);
            hamburger.setName("hamburger");
            return hamburger;
        } 
        
        if ("套餐B".equals(name)) {
            Food sandwich = new SandwichFood();
            sandwich.setPrice(25.00);
            sandwich.setName("三明治");
            return sandwich;
        } 
        
        return null;
    }
}

其中,KuaiCanFood、MiFanFood、HamburgerFood、SandwichFood 都派生自 Food。

消费者调用:

public class Consumer {
    public static void main(String[] args) {
        // 先选择一个具体的工厂
        FoodFactory factory = new ChineseFoodFactory();
        // 由选择的工厂产生具体的对象,不同的工厂造出不一样的对象
        Food food = factory.makeFood("套餐A");
    }
}

虽然都是调用 makeFood(“套餐A”) 制作套餐 A,但是,不同的工厂生产出来的完全不一样。

第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。

核心在于,我们需要在第一步选好我们需要的工厂。比如,我们有 FileFactory 接口,实现类有 AliyunOssFactory 和 TencentCosFactory,分别对应将文件写入阿里云 OSS 和腾讯云 COS 中。很显然,我们客户端或者消费者第一步就需要决定到底要实例化 AliyunOssFactory 还是 TencentCosFactory,这将决定之后的所有的操作。

工厂模式非常简单,我把他们都画到一张图上,希望大家能够看图就会:

工厂模式
工厂模式

现在会了还不是真会,一定要在实践代码中使用,不然过不了多久就会忘记!

抽象工厂模式

有了简单工厂模式,和工厂模式,为什么还有抽象工厂模式?

这就是当涉及到产品族的时候,就需要引入抽象工厂模式了。当一个产品族,存在多个不同类型的产品(比如我上面列举的 Redis 连接工厂)情况下,不同架构模式,选择不同的连接工厂的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。

Redis的连接工厂
Redis的连接工厂

RedisConnectionFactory的例子,我就不在多说了,建议spring-data-redis中的源码多看几遍。下面我再列举一个生活中的经典的例子,就是造一台手机。我们先不引入抽象工厂模式,看看怎么实现。

因为手机是由许多的构件组成的,我们将处理器 CPU 和主板 Board 进行抽象,然后 CPU 由 CpuFactory 生产,主板由 MainBoardFactory 生产。然后,我们再将 CPU 和主板搭配起来组合在一起,如下图:

抽象工厂模式
抽象工厂模式

这个时候的如果需要手机产品,只需这样调用:

public class Phone {
    private Cpu cpu;
    private MainBoard mainBoard;
    public Phone(Cpu cpu, MainBoard mainBoard){
        this.cpu = cpu;
        this.mainBoard = mainBoard;
    }
    public static void main(String[] args) {
        // 得到华为的处理器
        CpuFactory KirinFactory = new HuaweiCpuFactory();
        Cpu cpu = KirinFactory.getCpu();
        // 得到华为的主板
        MainBoardFactory mainBoardFactory = new HuaweiMainBoardFactory();
        MainBoard mainBoard = mainBoardFactory.getMainBoard();
        // 组装手机CPU和主板
        Phone huaweiMate = new Phone(cpu, mainBoard);
    }
}

单独看处理器 CPU 工厂和主板工厂,它们分别是前面我们说的工厂模式。这种方式也容易扩展,因为要给手机加配件的话,只需要加一个 XxxFactory 和相应的实现即可,不需要修改现有的工厂。

但是,这种方式有一个问题,那就是如果苹果🍎家产的 CPU 和华为产的主板不能兼容使用,因此不能出现随意组合。这就是产品族的概念,它代表了组成某个产品的一系列配件的集合。

当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、OS 工厂、显示屏工厂等等,我们直接定义手机工厂,每个手机工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。

public interface PhoneFactory {
    Cpu getCpu();
    MainBoard getMainBord();
    Display getDisplay();
}
public class HuaweiPhoneFactory implements PhoneFactory {
    @Override
    public Cpu getCpu() {
        return new HuaweiCpu();
    }
    @Override
    public MainBoard getMainBord() {
        return new HuaweiMainBoard();
    }
    @Override
    public Display getDisplay() {
        return new BoeDisplay();
    }
}
public class XiaomiPhoneFactory implements PhoneFactory {
    @Override
    public Cpu getCpu() {
        return new HuaweiCpu();
    }
    @Override
    public MainBoard getMainBord() {
        return new XiaomiMainBoard();
    }
    @Override
    public Display getDisplay() {
        return new VisionoxDisplay();
    }
}

这种情况下,对于生产来说,不再需要单独挑选 CPU 厂商、显示屏厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。

public class Test {
    public static void main(String[] args) {
        // 第一步就要选定一个“大品牌工厂”
        PhoneFactory factory = new HuaweiPhoneFactory();
        // 从这个大厂设计制造CPU
        Cpu cpu = factory.getCpu();
        // 从这个大厂生产手机主板
        MainBoard board = factory.getMainBord();
        // 从这个大厂生产手机显示屏
        Display boeDisplay = factory.getDisplay();
        // 生产一个华为 Mete 40 手机
        Phone huaweiMete40 = new Phone(cpu, board, boeDisplay);
    }
}

这样一个抽象工厂的代码和案例就讲完了,如果还有不懂的,我下次再拿我们现在生产项目中的案例给大家讲解!

最后总结一下,功能模式根据需求和功能的不同,你可以选择对应的简单工厂,普通工厂,和抽象工厂。抽象工厂看起来比较抽象,它暴露的问题也是显而易见的,比如我们要加个 NFC 模块,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则。但也不要完全照搬设计原则,毕竟有时候是需要打破原则,不是吗?比如在电商系统中的一些反范式设计等。原文:https://mp.weixin.qq.com/s/H_kb_BObQVkTYcrF6orkPw

设计模式之工厂模式,史上最强,不服来辩!

: » 设计模式之工厂模式,史上最强,不服来辩!

原创文章,作者:306829225,如若转载,请注明出处:https://blog.ytso.com/252289.html

(0)
上一篇 2022年5月4日
下一篇 2022年5月4日

相关推荐

发表回复

登录后才能评论