【23种设计模式专题】二 工厂模式

程序猿学社的GitHub,欢迎Star
github技术专题
本文已记录到github

前言

通过上一篇文章,我们已经知道程序猿是有女(男)朋友,也知道如何保证只会有一个对象(男女朋友),本文我们来看一看工厂模式。

社长:“老王,我们开始继续23中设计模式之工厂模式”
隔壁老王: “社长,工厂模式有什么用,为什么要用工厂模式?”
社长: “我先买一个小关子,先来看一看传统的写法”

小故事

周末一大早,6点钟左右,我就被吵醒,然后一脸呆萌呆萌的看着我,原来是忘记给我家乖乖喂食物咯,看了一下存放猫粮的袋子,也弹尽粮绝咯,只能上宠物店去购买猫粮。他的名字叫汤圆

传统方式

通过代码,我们实现去宠物店购买猫粮的这样一个需求。

package com.cxyxs.designmode.factory;
/**
 * Description:
 * Author: 程序猿学社
 * Date:  2020/4/3 23:16
 * Modified By:
 */
public interface PetShop {
    void buy();
}

class  Meat implements  PetShop{

    @Override
    public void buy() {
        System.out.println("社长给汤圆购买肉干");
    }
}

/**
 * 提供者
 * =======================
 * 调用者
 */
class  Test{
    public static void main(String[] args) {
        PetShop ps = new Meat();
        ps.buy();
    }
}


  • 这种写法违反了迪米特法则也就是最少知道原则,通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
  • 结合生活实际理解,例如我这里是去购买肉干,我有必要知道这个肉是如何生产出来的吗
  • 实际上也违反勒开闭原则,对扩展开放(提供者),对修改关闭(调用者)
    例如我们把类Meat改为Meat1,我们可以发现,我们需要修改两个地方,一个是提供者,还有一个是调用者,两边都要改动,这就违反了对修改关闭这一条,也没有实现程序的一个解耦。

隔壁老王: “社长,你也说了,上面这种方式,提供者一变,调用者就得跟着变,在项目开发过程中,如何也出现这样的问题,那大家还能不能愉快的玩耍咯”
社长:“别急,我们可以通过简单工厂来实现”

简单工厂(第一种)

从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例

肉干毕竟不能当饭吃,所以,还得买猫粮,肉干只能当零食吃。所以这时候我们的需求又发生了改变。

  • 不清楚这个图如何画的,可以百度查一下UML类图
package com.cxyxs.designmode.factory.simple;

/**
 * Description:宠物店
 * Author: 程序猿学社
 * Date:  2020/4/3 23:16
 * Modified By:
 */
public interface PetShop {
    void buy();
}

class  Meat implements  PetShop{

    @Override
    public void buy() {
        System.out.println("社长给汤圆购买肉干");
    }
}

class  Foot implements  PetShop{

    @Override
    public void buy() {
        System.out.println("社长给汤圆购买猫粮");
    }
}


class  CatFootFacoty{
    public  static  PetShop buy(String name){
        switch (name){
            case "猫粮":
                return  new Foot();
            case "肉干":
                return  new Meat();
        }
        return  null;
    }
}

/**
 * 提供者代码
 * =======================
 * 调用端代码
 */
class  Test{
    public static void main(String[] args) {
        PetShop ps = CatFootFacoty.buy("肉干");
        PetShop ps1 = CatFootFacoty.buy("猫粮");
        ps.buy();
        ps1.buy();
    }
}


好处:

  • 在这里借助了一个工厂类CatFootFacoty,实现解耦,我们不用关心肉干和猫粮是生产生产的,只需要告诉工厂,我需要这两样东西。由工厂统一管理。
  • 产品很少的情况下,可以实现简单工厂模式

缺点:

  • 如果产品一多,工厂类就会变得很臃肿,不利于维护,同时他违反咯开闭原则,开闭原则很重要的一点,当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化。
  • 例如我现在需要新增一个购买鱼的产品需求,需要新增一个鱼类,再修改工厂类,类会变得越来越多,如果上百个产品,这就意味着上百个类,增加了程序的复杂度。

工厂方法模式(第二种)

跟简单工厂相比,工厂方法模式是简单工厂的plus版本,越来越流程化。不像简单工厂一个工厂,既生产肉干,又生产猫粮等等,工厂方法模式,就是把具体生产的工作,交给具体的工厂来负责,例如,生产肉干的是一个工厂,生产猫粮的又是一个工厂。

  • 所以工厂方法模式符合开闭原则

社长: “我们刚刚已经了解到简单工厂一些优缺点,其中很重要的一点就是不符合开闭原则,我们来看一看工厂方法模式”
社长: “既然工厂方法模式是简单工厂的plus版本,我们直接把简单工厂的代码拿过来,新创建一个包methodmodel,老王,有没有跟上我的节奏”
隔壁老王: “欧了,已经搞定咯,如何改造成工厂方法模式*
社长: "话不多说,先看看类图,再看代码 "

  • 这个类图代码写完后,可以自动生成,在idea中,右击

    再把对应的类拖进来就可以自动生成,
package com.cxyxs.designmode.factory.methodmodel;


/**
 * Description:宠物店
 * Author:程序猿学社
 * Date:  2020/4/3 23:16
 * Modified By:
 */
public interface PetShop {
    void buy();
}

class  Meat implements PetShop {

    @Override
    public void buy() {
        System.out.println("社长给汤圆购买肉干");
    }
}

class  Foot implements PetShop {

    @Override
    public void buy() {
        System.out.println("社长给汤圆购买猫粮");
    }
}


interface  Factory{
    PetShop food();
}

class   MeatFactory implements  Factory{

    @Override
    public PetShop food() {
        return new Meat();
    }
}

class  FootFactory implements  Factory{

    @Override
    public PetShop food() {
        return new Foot();
    }
}



/**
 * 提供者代码
 * =======================
 * 调用端代码
 */
class  Test{
    public static void main(String[] args) {
        Factory factory = new MeatFactory();
        FootFactory footFactory = new FootFactory();

        PetShop food = factory.food();
        PetShop food1 = footFactory.food();

        food.buy();
        food1.buy();
    }
}


隔壁老王: “社长,你这代码,我没有看出什么好处来,只知道你这代码越来越复杂,绕的头都晕咯。”
社长: “老王,别急呀,我们之前已经知道简单工厂是不符合开闭原则的,也就是说,尽量不要在已经的代码上进行修改,应该进行扩展”
社长: “我家的汤圆现在已经不满足于肉干和猫粮咯,需要吃鱼,我们看看,我们如何在现有的代码上进行改动”

/**
 * 扩展吃鱼的部分开头
 */
class  Fish implements PetShop {

    @Override
    public void buy() {
        System.out.println("社长给汤圆购买小鱼鱼");
    }
}
class FishFactory implements  Factory{

    @Override
    public PetShop food() {
        return new Fish() ;
    }
}

/**
 * 提供者代码
 * =======================
 * 调用端代码
 */
class  Test{
    public static void main(String[] args) {
        Factory factory = new MeatFactory();
        FootFactory footFactory = new FootFactory();

        Factory fishFactory = new FishFactory();

        PetShop food = factory.food();
        PetShop food1 = footFactory.food();
        PetShop food2 = fishFactory.food();

        food.buy();
        food1.buy();
        food2.buy();
    }
}
  • 扩展产品,具体工厂也跟着扩展,不需要修改以前的代码,遵守了开闭原则。

好处:

  • 提供者修改代码后,调用者是不知道的,迪米特法则,也就是最少知道原则。
  • 在简单工厂上做咯优化,扩展产品,不需要修改以前的代码,只需要扩展一个产品和一个具体工厂即可

隔壁老王: “就拿你上面main方法里面的MeatFactory举例,假设MeatFactory类变为MeatFactory123,还是需要修改提供者和调用者两边的代码”
社长 “工厂名称有一套规范,只需要提供者保证尽量不改动类名就行,不然就是一个死循环,看看mybatis工厂类,版本变动,工厂名也不会变动。mybatis开发就相当于提供者,我们使用人员,就相当于调用者,如果mybatis工厂名变动,我们开发也不知道,这体验是不是很不好。所以,这个问题,不用担心,都有规范的”

抽象工厂模式(第三种)

社长: “我们之前只是很简单的实现吃,猫还会吃、睡等行为(多个产品等级)。产品等级一多,工厂类就会变得越来越臃肿”

使用工厂方法模式

  • 需要使用到12个类,工厂的涉及类就有6个
package com.cxyxs.designmode.factory.abstrastinterface;

/**
 * Description:
 * Author: 程序猿学社
 * Date:  2020/4/4 18:20
 * Modified By:
 */
public interface Foot {
    void eat();
}

class  Meat implements  Foot{

    @Override
    public void eat() {
        System.out.println("给汤圆吃肉干");
    }
}

class  Fish  implements  Foot{

    @Override
    public void eat() {
        System.out.println("给汤圆吃小鱼仔");
    }
}

interface Toy{
    void play();
}

class  CatTeaser implements Toy{

    @Override
    public void play() {
        System.out.println("社长利用逗猫棒跟汤圆玩耍");
    }
}

class Ball implements  Toy{

    @Override
    public void play() {
        System.out.println("汤圆一个人跟小球进行玩耍");
    }
}

/**
 * 食物工厂代码
 */
interface FootFactory{
    public Foot proFoot();
}
class MeatFactory implements  FootFactory{

    @Override
    public Foot proFoot() {
        return new Meat();
    }
}

class FishFactory implements  FootFactory{

    @Override
    public Foot proFoot() {
        return new Fish();
    }
}

/**
 * 玩具工厂
 */
interface ToyFactory{
    public Toy proToy();
}
class  CatTeaserFactory implements  ToyFactory{

    @Override
    public Toy proToy() {
        return new CatTeaser();
    }
}

class  BallFactory implements  ToyFactory{

    @Override
    public Toy proToy() {
        return new Ball();
    }
}

使用抽象工厂实现

社长: “先看看类图,再根据类图实现对应的代码”

package com.cxyxs.designmode.factory.abstrastinterface.plus;

/**
 * Description:
 * Author: 程序猿学社
 * Date:  2020/4/4 18:20
 * Modified By:
 */
public interface Foot {
    void eat();
}

class  Meat implements Foot {

    @Override
    public void eat() {
        System.out.println("给汤圆吃肉干");
    }
}

class  Fish  implements Foot {

    @Override
    public void eat() {
        System.out.println("给汤圆吃小鱼仔");
    }
}

interface Toy{
    void play();
}

class  CatTeaser implements Toy {

    @Override
    public void play() {
        System.out.println("社长利用逗猫棒跟汤圆玩耍");
    }
}

class Ball implements Toy {

    @Override
    public void play() {
        System.out.println("汤圆一个人跟小球进行玩耍");
    }
}

/**
 * 食物工厂代码
 */
interface Factory{
    public Foot proFoot();
    public Toy proToy();
}

class MeatAndCatTeaserFactory implements Factory {

    @Override
    public Foot proFoot() {
        return new Meat();
    }

    @Override
    public Toy proToy() {
        return new CatTeaser();
    }
}

class FishAndBallFactory implements  Factory{

    @Override
    public Foot proFoot() {
        return new Fish();
    }

    @Override
    public Toy proToy() {
        return new Ball();
    }
}
  • 工厂类由之前的6个,变为3个。减少了工厂类的臃肿

优点:

  • 抽象工厂可以理解为简单工厂和工厂方法模式的一个汇总
  • 对产品进行了抽象,适合一些维度有关联的(也就是说,有逻辑关系),例如,我举的这个例子,在工厂方法中,会有吃的工厂和玩的工厂,而在抽象工厂中,直接把这两个具体工厂直接抽象出来,合二为一。

缺点:

  • 要求抽象的多个产品,之前有逻辑关系
  • 后续需要生产喝的东西,需要修改抽象工厂,同时各个具体工厂也需要修改

隔壁老王: “社长,在抽象工厂模式中,吃和玩都是捆绑的关系,你这是捆绑销售,如果,我只想要实现吃,应该怎么办?”
社长: “如果只想实现吃,就可以使用工厂方法模式,应该根据具体问题,具体选择,使用那个模式。”

总结:

  • 不管是简单工厂、工厂方法模式、抽象工厂模式,我们应该灵活运用,主要的目的,还是为了解耦,写出可扩展性、可读的代码。

原创不易,不要白嫖,觉得有用的社友,给我点赞,让更多的老铁看到这篇文章。
因技术能力有限,如文中有不合理的地方,希望各位大佬指出,在下方评论留言,谢谢,希望大家一起进步,一起成长。

作者:程序猿学社
原创公众号:『程序猿学社』,专注于java技术栈,分享java各个技术系列专题,以及各个技术点的面试题。
原创不易,转载请注明来源(注明:来源于公众号:程序猿学社, 作者:程序猿学社)。

发布了291 篇原创文章 · 获赞 1312 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/qq_16855077/article/details/105182371