学习设计模式——工厂模式

747 阅读3分钟

前言

工厂模式(Factory Design Pattern)也是一种比较常用的创建型模式,它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,而是通过使用一个共同的接口来指定新创建的对象。

工厂模式可以细分为三种类型:简单工厂工厂方法抽象工厂,简单工厂、工厂方法在实际的项目中比较常用,而工厂方法在实际的项目中相对不常用。

简单工厂(Simple Factory)

什么是简单工厂模式,我们通过一个例子来解释一下。

1. 创建一个动物接口(Animal)

/**
 * @Author:
 * @Description: 动物接口
 */
public interface Animal {
    void eat();
}

2. 狗狗类(Dog)实现动物接口

/**
 * @Author:
 * @Description: 狗类
 */
public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("狗狗吃骨头");
    }
}

3. 猫咪类(Cat)实现动物接口

/**
 * @Author: 
 * @Description: 猫咪类
 */
public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("猫咪吃鱼干");
    }
}

4. 动物工厂类(AnimalFactory)

/**
 * @Author:
 * @Description: 动物工厂类(第一种实现)
 */
public class AnimalFactory {
    public Animal createAnimal(String animalType) {
        Animal animal = null;
        if ("dog".equals(animalType)) {
            animal =  new Dog();
        } else if ("cat".equals(animalType)) {
            animal =  new Cat();
        }
        return animal;
    }
}

5. 测试

/**
 * @Author: 
 * @Description: 测试
 */
public class Test {
    public static void main(String[] args) {
        AnimalFactory animalFactory = new AnimalFactory();
        Animal dog = animalFactory.createAnimal("dog");
        Animal cat = animalFactory.createAnimal("cat");
        dog.eat();
        cat.eat();
    }
}

结果如下:

狗狗吃骨头
猫咪吃鱼干


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

这有点类似于单例模式和简单工厂模式的结合,具体代码实现如下所示:

/**
 * @Author:
 * @Description: 动物工厂类(第二种实现)
 */
public class AnimalFactory {

    private static final Map<String, Animal> cachedAnimals = new HashMap<>();
    static {
        cachedAnimals.put("dog", new Dog());
        cachedAnimals.put("cat", new Cat());
    }
    public Animal createAnimal(String animalType) {
        return cachedAnimals.get(animalType);
    }
}

对于上面两种简单工厂模式的实现方法,如果我们要添加新的 animal,那势必要改动到 AnimalFactory 的代码,是否违反开闭原则呐?实际上,如果不是频繁的添加新的 animal,只是偶尔修改一下 AnimalFactory 的代码,稍微不符合开闭原则也是可以接受的。

在第一种实现例子中, AnimalFactory 类中 if 只有两个分支,可以直接这样写,但如果有很多个 if 分支判断逻辑,就要考虑使用多态或设计模式来代替 if 分支判断逻辑了。

工厂方法(Factory Method)

如果我们非要将 AnimalFactory 类中的 if 分支逻辑去掉,比较经典的处理方法就是利用多态。按照多态的实现思路,对上面的代码进行重构。

/**
 * @Author:
 * @Description: 动物工厂接口
 */
public interface IAnimalFactory {
    Animal createAnimal();
}

/**
 * @Author:
 * @Description: 狗狗工厂
 */
public class DogFactory implements IAnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Dog();
    }
}

/**
 * @Author:
 * @Description: 猫咪工厂
 */
public class CatFactory implements IAnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Cat();
    }
}

实际上,这就是工厂方法模式的典型代码实现。当我们新增一种 animal 的时候,只需要新增一个实现了 IAnimalFactory 接口的 Factory 类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则

从上面的实现来看,一切看起来都挺完美,但是实际上依旧存在问题,问题在于这些工厂类的使用上。

/**
 * @Author:
 * @Description:
 */
public class Test {
    public static void main(String[] args) {
        Animal animal = null;
        if ("dog".equals(getAnimalType())) {
            DogFactory dogFactory = new DogFactory();
            animal = dogFactory.createAnimal();
        } else if ("cat".equals(getAnimalType())) {
            CatFactory catFactory = new CatFactory();
            animal = catFactory.createAnimal();
        }
        animal.eat();
    }

    public static String getAnimalType() {
        return "dog";
    }
}

看上面的代码,使用工厂类的时候依旧会出现 if 分支语句,感觉设计好像变得更复杂了,要解决这个问题,我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。

其中,AnimalFactoryMap 类是创建工厂对象的工厂类,getAnimalFactory() 方法返回的是缓存好的单例工厂对象。

/**
 * @Author:
 * @Description: 工厂的工厂,不需要每次都创建新的工厂类对象
 */
public class AnimalFactoryMap {
    private static final Map<String, IAnimalFactory> cachedFactories = new HashMap<>();

    static {
        cachedFactories.put("dog", new DogFactory());
        cachedFactories.put("cat", new CatFactory());
    }

    public static IAnimalFactory getAnimalFactory(String animalType) {
        return cachedFactories.get(animalType.toLowerCase());
    }
}

使用工厂类:

/**
 * @Author:
 * @Description:
 */
public class Test {
    public static void main(String[] args) {
        IAnimalFactory animalFactory = AnimalFactoryMap.getAnimalFactory(getAnimalType());
        Animal animal = animalFactory.createAnimal();
        animal.eat();
    }

    public static String getAnimalType() {
        return "cat";
    }
}

这样,当我们需要添加新的动物比如鸟的时候,我们只需要创建新的 Bird 类和 BirdFactory 类,并且在 AnimalFactoryMap 类中,将新的 BirdFactory 对象添加到 cachedFactories 中即可。代码改动不多,基本符合开闭原则。

实际上,如果每个 Factory 类只是做简单的 new 操作,功能非常单薄,也没必要设计成独立的类,所以在这样的应用场景中,简单工厂模式比工厂方法模式更加合适。也就是当对象的创建逻辑比较复杂时候,我们推荐使用工厂方法模式。

抽象工厂

抽象工模式的应用场景比较特殊,没有前两种常用。上面的例子已经不再合适了,可能需要再重新写一篇抽象工厂模式的了。

注意

工厂模式作为一种创建型模式,在任何需要生成复杂对象的地方,都可以使用。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。