创建型模式——工厂方法模式

在学习工厂方法模式之前,我们先来看一个需求,设计一个咖啡店点餐系统。下面我们就来分析一下该需求。

咖啡店点餐系统点的肯定是咖啡,所以我们需要设计一个咖啡类(即Coffee),而咖啡它又有不同的品种:美式咖啡、拿铁咖啡等。这样,我们又得设计两个类,即美式咖啡类(即AmericanCoffee)和拿铁咖啡类(即LatteCoffee),很显然,我们得让这俩类去继承咖啡类,因为它俩可以向上抽取出共性的东西,而这些共性的东西可以放在咖啡类里面,用以提高代码的一个复用性。关于咖啡的这三个类设计完了之后,咱们还得设计一个咖啡店类,该类里面就具有点咖啡的功能,客户来了之后可以进行咖啡的一个点餐。

简单工厂模式 + 配置文件

简单工厂模式里面包含如下角色:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。上面点咖啡的案例中就用到了抽象产品这个角色,就是咖啡类(即Coffee),它里面定义了一套规范
  • 具体产品:实现或者继承抽象产品的子类。在上面点咖啡的案例中,具体产品指的就是拿铁咖啡、美式咖啡
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品

类图设计:

代码实现:

咖啡类:


//咖啡类
public abstract class Coffee {
    public  abstract String getName();

    //加调料
    public void addSpices(){
        System.out.println("加糖");
    }
}

美式咖啡类:


//美式咖啡
public class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }

    @Override
    public void addSpices() {
        System.out.println("加少糖");
    }
}

拿铁咖啡类:


public class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }

    @Override
    public void addSpices() {
        System.out.println("加奶");
    }
}

接着,在maven工程目录下创建一个配置文件,为了演示方便,这里我们不妨使用properties文件来作为配置文件,文件名字就起为bean.properties

american=pattern.factory.config_factory.AmericanCoffee
latte=pattern.factory.config_factory.LatteCoffee

紧接着,创建工厂类,这里我们创建的是静态工厂模式的工厂类。


public class CoffeeFactory {
    /**
     * 我们所要所做的事情,就是加载配置文件,然后去获取配置文件中配置的全类名,并创建该类的对象进行存储。
     */

    // 1. 定义容器对象存储咖啡对象
    /*
     * 容器选择双列集合
     *      键:(咖啡)名称
     *      值:咖啡对象
     */
    private static HashMap<String,Coffee> map = new HashMap<String,Coffee>();
    // 2. 加载配置文件中的全类名,并通过反射创建对象进行存储,注意,配置文件只需要加载一次。
    // 由于配置文件只需要加载一次,所以我们最好将代码写在静态代码块里面
    static {
        //2.1 创建Properties对象
        Properties p = new Properties();
        //2.2 调用p对象的load方法进行配置文件的加载,注意,配置文件(即bean.properties)是在类路径下面哟!
        InputStream in = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            p.load(in);
            //从p集合中获取全类名并创建对象
            Set<Object> keys = p.keySet();
            for(Object key : keys){
                //获取到全类名
                String className = p.getProperty((String) key);
                // 通过反射技术创建对象
                Class clazz = Class.forName(className);
                Coffee coffee = (Coffee) clazz.newInstance();
                // 将名称和对象存储到容器中
                map.put((String) key,coffee);
            }
        } catch (IOException | InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    /**
     * 根据名称获取Coffee对象
     * @param name:咖啡的名称
     * @return
     */
    public static Coffee createCoffee(String name){
        return map.get(name);
    }
}

创建一个测试类来测试一下:


public class Client {
    public static void main(String[] args) {
        Coffee coffee = CoffeeFactory.createCoffee("american");
        coffee.addSpices();
        System.out.println(coffee.getName());

        Coffee coffee1 = CoffeeFactory.createCoffee("latte");
        coffee1.addSpices();
        System.out.println(coffee1.getName());
    }
}

使用工厂模式+配置文件这种方式,扩展性更好,而且,它也符合开闭原则,这是因为如果我们此时再定义一个Coffee的子类的话,那么工厂类的代码是不需要改变的,我们只需要在配置文件里面再配置一行内容(即新咖啡类的全类名)就行了。

工厂方法模式

定义一个用于创建对象的接口(该接口就是工厂,里面并没有具体的去创建某一个产品对象),让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化(也即对象的创建)延迟到其工厂的子类中。

工厂方法模式的主要角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它里面并没有创建任何产品对象。调用者通过它访问具体工厂(即抽象工厂的子类)的工厂方法来创建产品
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应

使用工厂方法模式对以上案例进行改进之后设计出来的类图:

 

以上类图中可以看到,右边是咖啡类、美式咖啡类、拿铁咖啡类这三个类,而且它们也是父子类的关系,这和我们之前是一样的,所以我就不再过多的去强调了。中间是咖啡店类,它里面具有一个点咖啡的功能,而且它依赖于抽象(即Coffee),而没有依赖于具体,这就符合我们之前讲过的依赖倒转原则了。左边是一个接口(即CoffeeFactory),也就是抽象工厂,而且它里面有一个抽象的方法(即createCoffee)用于创建咖啡对象,这也就形成一套规范了,那么就要求子类必须去重写了。此外,CoffeeFactory接口下面还有两个子实现类:

  • 美式咖啡生产工厂类(即AmericanCoffeeFactory),它是得要去重写父接口中的抽象方法的,很明显它是用来生产具体的美式咖啡的,所以它还得依赖于美式咖啡类
  • 拿铁咖啡工厂类(即LatteCoffeeFactory),它也需要重写父接口中创建咖啡的抽象方法,很明显它是用来生产具体的拿铁咖啡的,所以它还得依赖于拿铁咖啡类

代码实现:

咖啡类、美式咖啡类、拿铁咖啡类这三个类不需要进行修改

根据以上类图去创建一个接口,即CoffeeFactory,它属于抽象工厂角色。


//CoffeeFactory:抽象工厂
public interface CoffeeFactory {
    // 创建咖啡对象的方法
    Coffee createCoffee();
}

接着,创建以上CoffeeFactory接口的子实现类


/**
 * 美式咖啡工厂类,专门用来生产美式咖啡
 */
public class AmericanCoffeeFactory implements CoffeeFactory {
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}
/**
 * 拿铁咖啡工厂类,专门用来生产拿铁咖啡
 */
public class LatteCoffeeFactory implements CoffeeFactory {
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

创建一个咖啡店类,从以上类图中可以看到,它里面有一个点咖啡的功能,即orderCoffee,它并不需要参数,不需要参数的话,那么它就得根据具体的工厂去获取咖啡对象了。注意,在该方法里面,我们不再需要去创建具体的工厂对象了,而是会从客户端传递进来。


/**
 * 咖啡店
 */
public class CoffeeStore {

    private CoffeeFactory factory; // 依赖于抽象(即CoffeeFactory),而没有依赖于具体

    public void setFactory(CoffeeFactory factory) {
        this.factory = factory;
    }

    // 点咖啡功能。注意,这儿不再需要创建具体工厂对象了,而是会从客户端传递进来
    public Coffee orderCoffee() {
        Coffee coffee = factory.createCoffee();
        // 加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }

}

测试类:


public class Client {
    public static void main(String[] args) {
        // 创建咖啡店对象
        CoffeeStore store = new CoffeeStore();
        // 创建具体工厂对象
        // CoffeeFactory factory = new AmericanCoffeeFactory();
        CoffeeFactory factory = new LatteCoffeeFactory();
        store.setFactory(factory);
        // 点咖啡
        Coffee coffee = store.orderCoffee();
        System.out.println(coffee.getName());
    }
}

我们在客户端传递的是拿铁咖啡工厂对象,所以应该生产的是拿铁咖啡

如果我们想要添加一种新的咖啡的品种的话,那么再定义一个Coffee类的子类即可,而且此时工厂类中的方法都不需要修改,现在只需要再创建一个新的咖啡工厂的子实现类就行了,这样就解决了简单工厂模式的缺点。

工厂方法模式是简单工厂模式的进一步抽象,由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点

工厂方法模式

优点:

  • 用户只需要知道具体工厂的名称就可以得到所要的产品,无须知道产品的具体创建过程,产品的具体创建过程被封装到了具体的工厂里面
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足了开闭原则,因为我们只是新添加了类,并没有去修改之前类中的代码

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。如果产品类特别特别多,那么就会产生类爆炸现象,也即系统里面的类特别特别多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值