设计模式之工厂模式

103 阅读10分钟

软件设计模式一个老生常谈的话题了,但是在阅读一些优秀的开源框架中是逃脱不了对设计模式理解与应用。了解设计模式可以在阅读框架的源码的时候不会有那么多的困惑。

软件设计模式的概念 软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。

需求: 设计一个饭店点餐系统

饭店中有各色各样的菜系,下单的时候可以点各色各样的菜,粤菜,鲁菜,川菜、湘菜等等。

按照普通的编程方式,一般会设计一个食物类Food,并定义两个子类(粤菜【GuangdongFood】),(川菜【SichuanFood】);在设计一个饭店类(FoodStore),饭店具有点菜的功能。定义好了需要的类然后我们会使用new 关键字去创建类,FoodStore 会去依赖 Food 类或者其子类。当Food 子类发生变化时,对应的FoodStore类的源码也要去更改。类图:

image.png

为什么要使用工厂模式

在java中,万物皆对象。在使用对象的时候都是要创建的,如果使用new关键字创建对象,则对象和对象之间的关联关系是非常紧密。某个模块或者对象依赖的另一个模块或者对象时如果对应的模块发现改变会影响其运行,即耦合程度严重。显然违背了软件设计的开闭原则。这时如果使用一个专门生产不同类型对象的工厂,要创建对象就从工厂中获取,模块依赖的对象有改变或者替换只需要更改工厂中的代码,不需要更新模块中的代码从而达到解耦的目的。工厂模式最大的优点就是:解耦

工厂模式的三种实现方式

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工行模式

编程习惯-简单工厂

简单工厂属于创建型模式但不属于23种GOF之一,反而更像一种编程习惯。该工厂模式只是通过传入不同的type简易分支来创建不同的对象。这种模式只要后面需求改动添加新的type那么就需要改动工厂中的代码。违背了六大设计原则开闭原则

1. 模式结构

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品:实现或者继承了抽象产品的子类
  • 具体工厂:提供了创建方法,调用者通过该方法来获取产品

2. 模式实现

类图: 简单工厂

在类图中,工厂(factory) 处理了创建类的细节。商家SimpleFoodOrder类不必去关心他需要菜肴的生产方式,只需要传入对应菜系(口味),后期如果还需要菜肴直接从工厂获取即可。这种处理方式解除了OderFood实现类的耦合,但是同时又产生了新的耦合,FoodSimpleFactorySimpleFoodOrder 的耦合。

3. 实现模式代码

根据2中类图类图码出以下代码

Food

package com.zhou.bean.simple;

/**
 * @author Axel
 * @version 1.0
 * @className Food
 * @description 食物,菜肴基类
 */

public class Food {

    Food() {
    }

    Food(String name) {
        this.name = name;
    }

    private String name;


    public String getName() {
        return name;
    }

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

    public void wash() {
        System.out.println("开始清洗" + name + ",准备烹饪...");
    }

    public void cook() {
        System.out.println("烹饪" + name + "...");
    }

    public void addFlavor() {
        System.out.println("往" + name + "加入调料...");
    }

    public void finish() {
        System.out.println("烹饪" + name + "完成,装盘上菜。");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Food food = (Food) o;

        return name != null ? name.equals(food.name) : food.name == null;
    }

    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }

    @Override
    public String toString() {
        return "Food{" +
                "name='" + name + ''' +
                '}';
    }
}

GuangdongFood类、SichuanFood

public class GuangdongFood extends Food{
}
public class SichuanFood extends Food{
}

工厂类FoodSimpleFactory

public class FoodSimpleFactory {

    private Food food;

    public FoodSimpleFactory() {
    }

    public FoodSimpleFactory(Food food) {
        this.food = food;
    }

    public Food createFood(String type) {
        if (null == type) {
            throw new RuntimeException("需要的食物类型不能为null");
        }
        if (type.equals("Guangdong")) {
            food = new GuangdongFood();
            food.setName("广东菜");
        } else if (type.equals("Sichuan")) {
            food = new SichuanFood();
            food.setName("四川菜");
        } else {
            throw new RuntimeException("还没有这种菜系.....");
        }
        return food;
    }
}

点餐类SimpleFoodOrder

public class SimpleFoodOrder {

    // 工厂用于生产菜肴
    private FoodSimpleFactory factory;

    // 客户需要的菜系
    private String foodType;


    public SimpleFoodOrder() {
    }

    public SimpleFoodOrder(FoodSimpleFactory factory) {
        this.factory = factory;
    }

    public FoodSimpleFactory getFactory() {
        return factory;
    }

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

    public String getFoodType() {
        return foodType;
    }

    public void setFoodType(String foodType) {
        this.foodType = foodType;
    }

    public Food orderFood() {
        factory = new FoodSimpleFactory();
        // 生产菜肴的方法
        Food food = factory.createFood(getType());
        // 不同的菜肴制作过程不一样调用改菜肴的制造方法
        food.wash();
        food.cook();
        food.addFlavor();
        food.finish();
        return food;
    }

    /**
     * 获取用户选择的菜系
     *
     * @return
     */
    public String getType() {
        String foodType = null;
        System.out.println("请输出你需要的菜肴口味:");
        BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
        try {
            foodType = buffer.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return foodType;
    }
}

4.简单工厂缺陷

缺点很明显如果商家需要其他的菜系则需要改动工厂中的代码,违背了开闭原则(对软件实体的扩展开放,对修改关闭)。

工厂方法模式

针对简单工厂产生的问题,在工厂方法中得到了完美的解决,完全遵循了开闭原则

1. 概念

类图

工厂方法模式

该模式是在简单工厂模式的基础上对工厂对象进一步的抽象,有其他菜系只需实现或者继承这个工厂实现对应的生产方法。order对象与factory对象也解耦了,后继饭店想要加入其它菜系只需要实现AbStractFoodFacatory中的抽象方法。让子类来决定实例化那个产品类对象。工厂方法使用产品类的实例化延迟到其工厂的子类。

2. 模式结构

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

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

3. 实现代码

抽象工厂类AbstractFoodFactory

public abstract class AbstractFoodFactory {

    /**
     * 提供一个抽象的创建菜肴方法
     * @return
     */
    public abstract Food createFood();

}

具体工厂类 GuangdongFoodFactorySichuanFoodFactory

public class GuangdongFoodFactory extends AbstractFoodFactory {
    @Override
    public Food createFood() {
        GuangdongFood food = new GuangdongFood();
        food.setName("广东菜");
        return food;
    }
}
public class SichuanFoodFactory extends AbstractFoodFactory{
    @Override
    public Food createFood() {
        SichuanFood food = new SichuanFood();
        food.setName("四川菜");
        return food;
    }
}

点餐类 FunctionFoodOrder

public class FunctionFoodOrder {

    private AbstractFoodFactory foodFactory;

    public FunctionFoodOrder(AbstractFoodFactory foodFactory) {
        this.foodFactory = foodFactory;
    }

    public FunctionFoodOrder() {
    }

    public AbstractFoodFactory getFoodFactory() {
        return foodFactory;
    }

    public void setFoodFactory(AbstractFoodFactory foodFactory) {
        this.foodFactory = foodFactory;
    }
   

    /**
     *
     * @return
     */
    public Food orderFood(){
        String type = getType();
        if(null == type){
            throw new RuntimeException("错误的类型");
        }
        if("Guangdong".equals(type)){
            this.foodFactory = new GuangdongFoodFactory();
            Food food = foodFactory.createFood();
            food.wash();
            food.cook();
            food.addFlavor();
            food.finish();
            return food;
        }else if("Sichuan".equals(type)){
            this.foodFactory = new SichuanFoodFactory();
            Food food = foodFactory.createFood();
            food.wash();
            food.cook();
            food.addFlavor();
            food.finish();
            return food;
        }else {
            throw new RuntimeException("无效的type");
        }
    }
    
    /**
     * 获取用户选择的菜系
     *
     * @return
     */
    public String getType() {
        String foodType = null;
        System.out.println("请输出你需要的菜肴口味:");
        BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
        try {
            foodType = buffer.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return foodType;
    }
}

4. 工厂方法模式的缺陷

当饭店扩展店内菜肴的的口味时,需要不断的去增加具体的产品类和一个对应具体工厂类,这样增加了系统的复杂度

抽象工厂模式

前面说的工厂方法模式中考虑的是一类产品的生产,如玩具厂商只生产玩具,手机厂商只生产手机,电视机厂只生产电视机。这些工厂只生产同类产品,同种类产品称之为同级产品。换句话说,工厂方法模式只考虑了生产同等级的产品,但是在现实生活中,许多工厂是综合型的,可以生产多等级(种类)的产品,如手机厂不仅生产手机还生产耳机,有的还生产汽车。

抽象工厂就是一个综合型的工厂可以生产多等级(种类)的产品工厂,显然这个综合工厂是一个抽象类或者一个接口,而具体生产的位于不同等级的同一组产品(例如和手机一组的包含 手机、耳机、手机壳、手机吊绳 等等)需要具体工厂去实现。不同等级的同一组产品 一般称之为产品族。可以使用一个坐标系来加深一下理解

image.png

1. 概念

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

在抽象工厂中添加生产不同等级产品的方法,每个方法对应不同的具体工厂

2. 模式结构

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。

3. 模式实现

现在饭店业务发生改变,在茶余饭后提供甜点例如双皮奶、米花糖。如果按照工厂方法模式来实现,则需要定义双皮奶类、米花糖类、双皮奶工厂、米花糖工厂、甜点工厂,如果后继饭店继续扩展业务比如奶茶等等。又要建各种各样类很同意发生类爆炸。在粤菜中老火靓汤、川菜中的东波肘子是一个产品等级,都是下饭菜。双皮奶、米花糖属于一个产品等级,都是饭后甜点;老火靓汤和双皮奶属于粤菜风味的(同一产品族),东波肘子和米花糖属于川菜风味的(同一产品族)。可以得到一下

类图:

image.png

代码:

两个产品族基类DishDessert

public class Dish{

    private String name;

    Dish() {
    }

    Dish(String name) {
        this.name = name;
    }


    public String getName() {
        return name;
    }

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

    public void wash() {
        System.out.println("开始清洗" + name + ",准备烹饪...");
    }

    public void cook() {
        System.out.println("烹饪" + name + "...");
    }

    public void addFlavor() {
        System.out.println("往" + name + "加入调料...");
    }

    public void finish() {
        System.out.println("烹饪" + name + "完成,装盘上菜。");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Dish dish = (Dish) o;

        return name != null ? name.equals(dish.name) : dish.name == null;
    }

    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }

    @Override
    public String toString() {
        return "Food{" +
                "name='" + name + ''' +
                '}';
    }
}
public class Dessert {
    private String name;

    public Dessert(){}

    public Dessert(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public void mix(){
        System.out.println("开始制作"+name+"混合原材料...");
    }

    public void show(){
        System.out.println(name+"制作完成,展示甜点...");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Dessert dessert = (Dessert) o;

        return name != null ? name.equals(dessert.name) : dessert.name == null;
    }

    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }

    @Override
    public String toString() {
        return "Dessert{" +
                "name='" + name + ''' +
                '}';
    }
}

具体产品

具体产品代码和简单工厂一样,不一一列举。

食物工厂接口FoodFactory

public interface FoodFactory {

    Dish createDish();

    Dessert createDessert();
}

食物工厂两个产品实现 GuangdongFoodFactorySichuanFoodFactory

public class GuangdongFoodFactory implements FoodFactory {
    @Override
    public Dish createDish() {
        LaohuoLiangtangDish liangtangDish = new LaohuoLiangtangDish();
        liangtangDish.setName("老火靓汤");
        return liangtangDish;
    }

    @Override
    public Dessert createDessert() {
        ShuangpinaiDessert shuangpinaiDessert = new ShuangpinaiDessert();
        shuangpinaiDessert.setName("双皮奶");
        return shuangpinaiDessert;
    }
}
public class SichuanFoodFactory implements FoodFactory {
    @Override
    public Dish createDish() {
        DongbozhouziDish dongbozhouziDish = new DongbozhouziDish();
        dongbozhouziDish.setName("东波肘子");
        return dongbozhouziDish;
    }

    @Override
    public Dessert createDessert() {
        MiHuatangDessert miHuatangDessert = new MiHuatangDessert();
        miHuatangDessert.setName("米花糖");
        return miHuatangDessert;
    }
}

缺陷

当产品族中需要增加新的产品时,所有的工厂类需要进行修改。

使用场景

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。