每日一个设计模式之工厂设计模式

Scroll Down

是什么(what)

工厂模式(Factory Pattern)是23种设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

工厂模式分类

image-1673091216070

我们来看看工厂模式的类图(取自Head First 设计模式一书):

工厂:
image-1673091329220

抽象工厂:
image-1673091361513

怎么用(how)

业务场景

  • 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。

  • 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。

  • 3、设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。

生活场景

  • 1.吃饸饹面
  • 2.春节回家买票

现有框架及JDK使用

1.JDK

简单工厂模式:java.util.Calendar

2.Spring

简单工厂BeanFactory

工厂方法FactoryBean

参考文章:

工厂模式在jdk和spring中应用

代码show

静态工厂模式

这个最常见了,项目中的辅助类,TextUtil.isEmpty等,类+静态方法。

@Test
public void test2() {
    Calendar instance = Calendar.getInstance();
    Integer integer = Integer.valueOf("3");
}

简单工厂模式

下面开始谈谈卖饸饹面。最近有点想吃饸饹面了,话说:郏县,山西,陕西,还是老家郏县的饸饹面好吃,哈哈。

首先你得有个店:HeleStore

package cn.wangzengqiang.study.designpattern.factory.own.a;

/**
 * 饸饹面店
 */
public class HeleStore {
    public Hele sellHele(String type) {
        Hele hele = null;
        if (type.equals("Jiaxian")) {
            hele = new JiaCountyHele();
        } else if (type.equals("Shanxi")) {
            hele = new ShanxiHele();
        } else if (type.equals("Shaanxi")) {
            hele = new ShaanxiHele();
        }
        hele.huoMian();
        hele.xiaMian();
        hele.putMutton();
        hele.putSeasoning();
        hele.putSoup();
        return hele;
    }
}

然后你得有各种风味的饸饹面:

package cn.wangzengqiang.study.designpattern.factory.own.a;

/**
 * 饸饹面
 */
public abstract class Hele {
    protected String name;

    /**
     * 和面
     */
    public void huoMian() {
        System.out.println("和面");
    }

    /**
     * 下面
     */
    public void xiaMian() {
        System.out.println("下面");
    }

    /**
     * 放羊肉
     */
    public void putMutton() {
        System.out.println("放羊肉");
    }

    /**
     * 放调味料
     */
    public void putSeasoning() {
        System.out.println("放羊油辣椒,味精");
    }

    /**
     * 浇汤
     */
    public void putSoup() {
        System.out.println("浇汤");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.a;

/**
 * 郏县饸饹面
 */
public class JiaCountyHele extends Hele {
    public JiaCountyHele() {
        this.name = "郏县饸饹面";
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.a;

/**
 * 陕西饸饹面
 */
public class ShaanxiHele extends Hele {
    public ShaanxiHele() {
        this.name = "陕西饸饹面";
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.a;

/**
 * 山西饸饹面
 */
public class ShanxiHele extends Hele {
    public ShanxiHele() {
        this.name = "山西饸饹面";
    }
}

测试:

package cn.wangzengqiang.study.designpattern.factory.own.a;

import org.junit.Test;

import java.util.Calendar;


public class HeleStoreTest {
    @Test
    public void test1() {
        //1.选择饸饹店
        HeleStore heleStore = new HeleStore();
        //2.卖饸饹
        Hele jiaCountyHele = heleStore.sellHele("Jiaxian");
        //3.打印饸饹相关信息
        System.out.println("您吃的是:" + jiaCountyHele.name);

    }

    /**
     * 静态工厂模式,参考:https://blog.csdn.net/m0_73533108/article/details/126846316
     */
    @Test
    public void test2() {
        Calendar instance = Calendar.getInstance();
        Integer integer = Integer.valueOf("3");
    }

}

类图:

image-1673091416745

现在这样的设计,虽然可以支持你卖饸饹面了,但是有点问题,生产饸饹的种类和你的HeleStore耦合度太高了,如果增加几种风味,删除几种风味,你得一直修改sellHele中的方法,所以我们需要做一定的修改,此时简单工厂模式就能派上用场了。
我们开始写个简单工厂,把产生饸饹的过程拿出来:

package cn.wangzengqiang.study.designpattern.factory.own.b;

import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;
import cn.wangzengqiang.study.designpattern.factory.own.a.JiaCountyHele;
import cn.wangzengqiang.study.designpattern.factory.own.a.ShaanxiHele;
import cn.wangzengqiang.study.designpattern.factory.own.a.ShanxiHele;

/**
 * 饸饹简单工厂
 */
public class SimpleHeleFactory {
    public Hele createHele(String type) {
        Hele hele = null;
        if (type.equals("Jiaxian")) {
            hele = new JiaCountyHele();
        } else if (type.equals("Shanxi")) {
            hele = new ShanxiHele();
        } else if (type.equals("Shaanxi")) {
            hele = new ShaanxiHele();
        }
        return hele;
    }
}

然后以组合的方式,让Store来使用:

package cn.wangzengqiang.study.designpattern.factory.own.b;

import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;

public class HeleStore {
    private SimpleHeleFactory factory;

    public HeleStore(SimpleHeleFactory factory) {
        this.factory = factory;
    }

    public Hele sellHele(String type) {
        Hele hele = factory.createHele(type);
        hele.huoMian();
        hele.xiaMian();
        hele.putMutton();
        hele.putSeasoning();
        hele.putSoup();
        return hele;

    }
}

测试1(未用工厂):

package cn.wangzengqiang.study.designpattern.factory.own.b;


import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;
import org.junit.Test;

public class HeleStoreTest {

    @Test
    public void test1() {
        //1.选择饸饹店
        SimpleHeleFactory simpleHeleFactory = new SimpleHeleFactory();
        HeleStore heleStore = new HeleStore(simpleHeleFactory);
        //2.卖饸饹
        Hele jiaxianHele = heleStore.sellHele("Shanxi");
        //3.打印饸饹相关信息
        System.out.println("您吃的是:" + jiaxianHele.getName());

    }
}

测试结果1:

和面
下面
放羊肉
放羊油辣椒,味精
浇汤
您吃的是:郏县饸饹面

测试2(简单工厂):

package cn.wangzengqiang.study.designpattern.factory.own.b;


import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;
import org.junit.Test;

public class HeleStoreTest {

    @Test
    public void test1() {
        //1.选择饸饹店
        SimpleHeleFactory simpleHeleFactory = new SimpleHeleFactory();
        HeleStore heleStore = new HeleStore(simpleHeleFactory);
        //2.卖饸饹
        Hele jiaxianHele = heleStore.sellHele("Shanxi");
        //3.打印饸饹相关信息
        System.out.println("您吃的是:" + jiaxianHele.getName());

    }
}

测试结果 2:

和面
下面
放羊肉
放羊油辣椒,味精
浇汤
您吃的是:山西饸饹面

类图:

image-1673091444182

现在你随便添加什么种类的饸饹,删除什么种类的饸饹就和Store无关了,就是么饸饹店只负责卖饸饹 这就是简单工厂模式。

工厂方法模式

定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。

工厂方法模式把类实例化的过程推迟到子类。

看完定义,下面我们用例子来展示。继续卖饸饹,由于使用了简单工厂模式,饸饹面生意那个好啊,所以决定去北京开个分店,再去上海开个分店。

既然有分店了,那总店就是抽象的了:

package cn.wangzengqiang.study.designpattern.factory.own.c;

import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;

/**
 * 饸饹店
 */
public abstract class HeleStore {
    public abstract Hele createHele(String type);

    public Hele sellHele(String type) {
        Hele hele = createHele(type);
        hele.huoMian();
        hele.xiaMian();
        hele.putMutton();
        hele.putSeasoning();
        hele.putSoup();
        return hele;
    }

}

然后在开两个分店:

package cn.wangzengqiang.study.designpattern.factory.own.c;


import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;
import cn.wangzengqiang.study.designpattern.factory.own.a.JiaCountyHele;
import cn.wangzengqiang.study.designpattern.factory.own.a.ShaanxiHele;
import cn.wangzengqiang.study.designpattern.factory.own.a.ShanxiHele;

/**
 * 北京饸饹店
 */
public class BeijingHeleStore extends HeleStore {
    @Override
    public Hele createHele(String type) {
        Hele hele = null;
        if (type.equals("Jiaxian")) {
            hele = new JiaCountyHele();
        } else if (type.equals("Shanxi")) {
            hele = new ShanxiHele();
        } else if (type.equals("Shaanxi")) {
            hele = new ShaanxiHele();
        }
        return hele;
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.c;


import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;
import cn.wangzengqiang.study.designpattern.factory.own.a.JiaCountyHele;
import cn.wangzengqiang.study.designpattern.factory.own.a.ShaanxiHele;
import cn.wangzengqiang.study.designpattern.factory.own.a.ShanxiHele;

/**
 * 上海饸饹店
 */
public class ShanghaiHeleStore extends HeleStore {
    @Override
    public Hele createHele(String type) {
        Hele hele = null;
        if (type.equals("Jiaxian")) {
            hele = new JiaCountyHele();
        } else if (type.equals("Shanxi")) {
            hele = new ShanxiHele();
        } else if (type.equals("Shaanxi")) {
            hele = new ShaanxiHele();
        }
        return hele;
    }
}

测试:

package cn.wangzengqiang.study.designpattern.factory.own.c;


import cn.wangzengqiang.study.designpattern.factory.own.a.Hele;
import org.junit.Test;


public class HeleStoreTest {
    @Test
    public void test1() {
        //1.选择饸饹店
        HeleStore heleStore = new BeijingHeleStore();
        //2.卖饸饹
        Hele jiaxianHele = heleStore.sellHele("Shaanxi");
        //3.打印饸饹相关信息
        System.out.println("您吃的是:" + jiaxianHele.getName());

    }

}

测试结果:

和面
下面
放羊肉
放羊油辣椒,味精
浇汤
您吃的是:陕西饸饹面

类图:

image-1673091470269

然后就是各个北京口味的饸饹了,这代码就不贴了。

可以看出我们把制作饸饹面的过程以抽象方法的形式让子类去决定了,对照定义:
1、定义了创建对象的一个接口:public abstract Hele createHele(String type);

2、由子类决定实例化的类,可以看到我们的饸饹是子类生成的。

可能有人会说,我用简单工厂模式也行啊,但是如果10来个城市*5种风味,那么岂不是简单工厂里面需要50多个if,再说北京饸饹面分店就不能有点自己的秘诀,当然由它自己定最好。

抽象工厂模式(Abstract Factory Pattern)

提供一个接口,用于创建相关的或依赖对象的家族,而不需要明确指定具体类。

这定义有点绕口,算了,还是拿例子来说。继续卖饸饹面,咱们生意这么好,难免有些分店开始动歪脑子,开始使用劣质肉等,砸我们的品牌。

所以我们要拿钱在每个城市建立自己的原料场,保证高质量原料的供应。

于是我们新建一个提供原料的接口:

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 提供饸饹的原料工厂
 */
public interface HeleRawMaterialFactory {
    /**
     * 生产肉
     *
     * @return
     */
    Meat createMeat();

    /**
     * 生产原料:面,辣椒等
     */
    RawMaterial createRawMaterial();


}
package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 郏县饸饹原料工厂
 */
public class JiaCountyHeleRawMaterialFactory implements HeleRawMaterialFactory {
    @Override
    public Meat createMeat() {
        return new Mutton();
    }

    @Override
    public RawMaterial createRawMaterial() {
        return new JiaCountyFeatureRawMaterial();
    }
}

基础原料类:

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 肉
 */
public class Meat {
    protected String name;
}

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 羊肉
 */
public class Mutton extends Meat {
    public Mutton() {
        this.name = "羊肉";
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 原料
 */
public class RawMaterial {
    protected String name;
}

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 郏县特色原料
 */
public class JiaCountyFeatureRawMaterial extends RawMaterial {
    public JiaCountyFeatureRawMaterial() {
        this.name = "郏县特色原料:羊油辣椒,味精";
    }
}

有了原料工厂,那我们稍微修改下Hele的putMutton和putSeasoning方法:

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 饸饹面
 */
public abstract class Hele {
    protected String name;

    /**
     * 和面
     */
    public void huoMian() {
        System.out.println("和面");
    }

    /**
     * 下面
     */
    public void xiaMian() {
        System.out.println("下面");
    }

    /**
     * 放羊肉
     */
    public void putMutton(HeleRawMaterialFactory rawMaterialFactory) {
        Meat meat = rawMaterialFactory.createMeat();
        System.out.println("放肉:" + meat.name);
    }

    /**
     * 放调味料
     */
    public void putSeasoning(HeleRawMaterialFactory rawMaterialFactory) {
        RawMaterial rawMaterial = rawMaterialFactory.createRawMaterial();
        System.out.println("放原料:" + rawMaterial.name);
    }

    /**
     * 浇汤
     */
    public void putSoup() {
        System.out.println("浇汤");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.d;


/**
 * 郏县饸饹面
 */
public class JiaCountyHele extends Hele {
    public JiaCountyHele() {
        this.name = "郏县饸饹面";
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.d;


/**
 * 陕西饸饹面
 */
public class ShaanxiHele extends Hele {
    public ShaanxiHele() {
        this.name = "陕西饸饹面";
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.d;


/**
 * 山西饸饹面
 */
public class ShanxiHele extends Hele {
    public ShanxiHele() {
        this.name = "山西饸饹面";
    }
}

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 饸饹店
 */
public abstract class HeleStore {
    public abstract Hele createHele(String type);

    public Hele sellHele(String type, HeleRawMaterialFactory rawMaterialFactory) {
        Hele hele = createHele(type);
        hele.huoMian();
        hele.xiaMian();
        hele.putMutton(rawMaterialFactory);
        hele.putSeasoning(rawMaterialFactory);
        hele.putSoup();
        return hele;
    }

}

package cn.wangzengqiang.study.designpattern.factory.own.d;

/**
 * 郏县饸饹店
 */
public class JiaCountyHeleStore extends HeleStore {
    @Override
    public Hele createHele(String type) {
        Hele hele = null;
        if (type.equals("Jiaxian")) {
            hele = new JiaCountyHele();
        } else if (type.equals("Shanxi")) {
            hele = new ShanxiHele();
        } else if (type.equals("Shaanxi")) {
            hele = new ShaanxiHele();
        }
        return hele;
    }
}

好了,现在必须用我们官方原料做为原材料了。
现在对比定义:

1、提供一个接口:public interface HeleRawMaterialFactory

2、用于创建相关的或依赖对象的家族Meat createMeat(); RawMaterial createRawMaterial();

我们接口用于创建一系列的原材料。

测试:

package cn.wangzengqiang.study.designpattern.factory.own.d;


import org.junit.Test;

public class HeleStoreTest {
    @Test
    public void test1() {
        //1.选择饸饹店
        HeleStore heleStore = new JiaCountyHeleStore();
        //2.卖饸饹
        HeleRawMaterialFactory rawMaterialFactory = new JiaCountyHeleRawMaterialFactory();
        Hele jiaxianHele = heleStore.sellHele("Jiaxian", rawMaterialFactory);
        //3.打印饸饹相关信息
        System.out.println("您吃的是:" + jiaxianHele.getName());

    }

}

测试结果:

和面
下面
放肉:羊肉
放原料:郏县特色原料:羊油辣椒,味精
浇汤
您吃的是:郏县饸饹面

类图:

image-1673091509941

为什么用(why)

  • 1、一个调用者想创建一个对象,只要知道其名称就可以了。

  • 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。

  • 3、屏蔽产品的具体实现,调用者只关心产品的接口。

  • 4、当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。