设计模式系列篇(二)——工厂模式

What

当创建逻辑比较复杂时,我们就可以考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用分离开来。工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂,其中,简单工厂和工厂方法比较常用,抽象工厂在实际项目种并不常用。

Why

为什么需要工厂模式呢?当创建对象的逻辑比较复杂时,如果将对象创建和使用放在一起,会导致代码的可维护性和可扩展性都很差。
那么,什么情况下可视为创建对象的逻辑比较复杂呢?大致可以总结为以下两种情况:

  1. 创建对象时需要动态的根据不同的类型创建不同的对象,即含有较多的if-else分支。此时,使用工厂模式将对象创建逻辑封装至工厂类中,使得对象创建过程对对象使用来说是透明的。
  2. 对象创建过程需要做很多初始化操作,此时可以使用工厂模式,将复杂的初始化操作封装到工厂类中。

借助工厂模式,可以达到以下效果:
封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
代码复用:创建代码抽离到独立的工厂类之后可以复用。
隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

How

接下来,我们通过一个简单需求,来介绍工厂模式的实现方法。
需求如下:
一台笔记本电脑由CPU、内存条、硬盘等硬件组成,而CPU、内存条、硬盘有不同的品牌。比如,常见的CPU有Intel、AMD、IBM等品牌。一台笔记本的生产其实就是将生产好的CPU、内存条、硬盘进行组装即可。同时,笔记本也有不同的品牌。现在,我们将笔记本的生产过程进行抽象,翻译成代码。

首先,我们先以CPU的生产过程来介绍简单工厂和工厂方法两种方式。这里,我创建了CPU抽象类(Cpu),以及不同的品牌CPU类(IntelCpu、AmdCpu、IbmCpu)。Cpu类含有两个成员变量:brand和useTech,分别代表品牌和使用技术(仅做简单示例);对于CPU生产过程,我们这里并没有展开去实现,只是打印说明了一下,关键还是想让大家重点关注工厂模式的实现方法。

public abstract class Cpu {
    private String brand;
    private String useTech;

    public Cpu(String brand, String useTech) {
        this.brand = brand;
        this.useTech = useTech;
    }

    public abstract void create();
}

public class IntelCpu extends Cpu {
    public IntelCpu() {
        super("Intel", "Intel use intel's unique technology");
        // Omitting complex initialization logic
        // ...
    }


    @Override
    public void create() {
        System.out.println("Intel CPU is creating");

        // Omitting complex creating logic
        // ...

        System.out.println("Intel CPU is created finished");
    }
}
// AmdCpu、IbmCpu代码和IntelCpu结构一致,因此省略

1. 简单工厂
简单工厂实现其实很简单,就是根据传入类型返回不同的对象。

public class CpuFactory {
    public static Cpu getCpuCreator(String brand) {
        Cpu cpu = null;
        if (brand == null) {
            throw new IllegalArgumentException("brand can not be empty");
        }

        if ("AMD".equals(brand)) {
            cpu = new AmdCpu();
        } else if ("Intel".equals(brand)) {
            cpu = new IntelCpu();
        } else if ("IBM".equals(brand)) {
            cpu = new IbmCpu();
        } else {
            throw new IllegalStateException(String.format("The brand of %s does not exist", brand));
        }

        return cpu;
    }
}

那么,笔记本的组装过程翻译为代码如下:

public class Laptop {
    private Cpu cpu;
    private Memory memory;

    public Laptop() {

    }

    public void create(String cpuBrand, String memoryBrand) {
        System.out.println("Laptop is creating");

        cpu = CpuFactory.getCpuCreator(cpuBrand);
        cpu.create();

        System.out.println("Laptop is created finished");
    }
}

在上面的代码实现中,我们每次调用 CpuFactory 的 getCpuCreator() 的时候,都要创建一个新的 cpu 对象。实际上,如果 cpu 可以复用,为了节省内存和对象创建的时间,我们可以将 cpu 对象事先创建好缓存起来。当调用 getCpuCreator() 函数的时候,我们从缓存中取出 cpu 对象直接使用。实现代码如下:

public class CpuFactory {
    private static final Map<String, Cpu> cachedCreators = new HashMap<>();
    static {
        cachedCreators.put("AMD", new AmdCpu());
        cachedCreators.put("Intel", new IntelCpu());
        cachedCreators.put("IBM", new IbmCpu());
    }

    public static Cpu getCpuCreator(String brand) {
        if (brand == null || brand.isEmpty()) {
            throw new IllegalArgumentException("brand can not be empty");
        }

        if (cachedCreators.containsKey(brand)) {
            return cachedCreators.get(brand);
        }

        throw new IllegalStateException(String.format("The brand of %s does not exist", brand));
    }
}

简单工厂模式的这两种实现方式非常简单,但如果要添加新的cpu品牌,必须得更改CpuFactory类;但是如果不需要频繁添加的话,这样实现也没问题,并不违反开闭原则。但如果需要频繁更改的话,简单工厂模式就要升级下了,这就是接下来要讲的工厂方法模式。

2. 工厂方法
工厂方法模式可以解决上述提到的问题,它为每一个cpu品牌都创建一个工厂类,然后用一个Map类将这些工厂类缓存起来,根据传入的类型值返回对应的工厂对象,最后再用该工厂去创建对象。实现方式如下:

public interface ICpuFactory {
    Cpu getCpuCreator();
}

public class AmdCpuFactory implements ICpuFactory {
    @Override
    public Cpu getCpuCreator() {
        return new AmdCpu();
    }
}

public class IntelCpuFactory implements ICpuFactory {
    @Override
    public Cpu getCpuCreator() {
        return new IntelCpu();
    }
}

public class IbmCpuFactory implements ICpuFactory {
    @Override
    public Cpu getCpuCreator() {
        return new IbmCpu();
    }
}

public class CpuFactoryMap {
    private static final Map<String, ICpuFactory> cachedCreatorFactories = new HashMap<>();
    static {
        cachedCreatorFactories.put("AMD", new AmdCpuFactory());
        cachedCreatorFactories.put("Intel", new IntelCpuFactory());
        cachedCreatorFactories.put("IBM", new IbmCpuFactory());
    }

    public static ICpuFactory getCpuCreatorFactory(String brand) {
        if (brand == null || brand.isEmpty()) {
            throw new IllegalArgumentException("brand can not be empty");
        }

        if (cachedCreatorFactories.containsKey(brand)) {
            return cachedCreatorFactories.get(brand);
        }

        throw new IllegalStateException(String.format("The brand of %s does not exist", brand));
    }
}

此时,笔记本的组装代码如下:

public class Laptop {
    private Cpu cpu;
    private Memory memory;

    public Laptop() {

    }

    public void createByFactoryMethod(String cpuBrand, String memoryBrand) {
        System.out.println("------------ factory method ------------");
        System.out.println("Laptop is creating");

        ICpuFactory cpuCreatorFactory = CpuFactoryMap.getCpuCreatorFactory(cpuBrand);
        cpuCreatorFactory.getCpuCreator().create();

        System.out.println("Laptop is created finished");
        System.out.println();
    }
}

此时当我们需要添加新的CPU品牌的时候,我们只需要创建新的 CPU 类和 CPU factory 类,并且在 CpuFactoryMap 类中,将新的 CPU factory 对象添加到 cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。

3. 抽象工厂
只对于CPU这一个组件,我们创建了3个工厂类。如果对于Memory、Driver等组件都和CPU一样,对每个组件都搞n个工厂类,这会导致类的个数非常多,增加了维护的成本;因此,我们可以只针对笔记本这个类建一个工厂,把所有组件的创建对象都放到这个工厂里面。这样的话,不同品牌的笔记本对应不同的笔记本工厂。实现方式如下:

public interface LaptopFactory {
    Cpu getCpuCreator();
    Memory getMemoryCreator();
}

public class MacLaptopFactory implements LaptopFactory {

    @Override
    public Cpu getCpuCreator() {
        return new IntelCpu();
    }

    @Override
    public Memory getMemoryCreator() {
        return new KingstonMemory();
    }
}

public class WindowsLaptopFactory implements LaptopFactory {

    @Override
    public Cpu getCpuCreator() {
        return new AmdCpu();
    }

    @Override
    public Memory getMemoryCreator() {
        return new CorsairMemory();
    }
}

public class LaptopFactoryMap {
    private static final Map<String, LaptopFactory> cachedLaptopFactories = new HashMap<>();
    static {
        cachedLaptopFactories.put("Mac", new MacLaptopFactory());
        cachedLaptopFactories.put("Windows", new WindowsLaptopFactory());
    }

    public static LaptopFactory getLaptopFactory(String laptopBrand){
        if (laptopBrand == null || laptopBrand.isEmpty()) {
            throw new IllegalArgumentException("brand can not be empty");
        }

        if (cachedLaptopFactories.containsKey(laptopBrand)) {
            return cachedLaptopFactories.get(laptopBrand);
        }

        throw new IllegalStateException(String.format("The laptopBrand of %s does not exist", laptopBrand));
    }
}

此时,电脑组装代码如下:

public class Laptop {
    private Cpu cpu;
    private Memory memory;

    public Laptop() {

    }

    public void createByAbstractFactory(String laptopBrand) {
        System.out.println("------------ abstract factory ------------");
        System.out.println(String.format("%s Laptop is creating", laptopBrand));

        LaptopFactory laptopFactory = LaptopFactoryMap.getLaptopFactory(laptopBrand);
        laptopFactory.getCpuCreator().create();
        laptopFactory.getMemoryCreator().create();

        System.out.println(String.format("%s Laptop is created finished", laptopBrand));
        System.out.println();
    }
}

抽象工厂的核心就是一个工厂类里面支持同时创建多个对象,这样就可以有效地减少工厂类的个数。

代码地址

i-learning

写在最后

如果你觉得我写的文章帮到了你,欢迎点赞、评论、分享、赞赏哦,你们的鼓励是我不断创作的动力~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353