从易维护、高可靠、低耦合的角度来看,当需要将一种类(types)添加到系统中时,比较合理的方式是使用多态特性为这些类创建一个通用接口。这将使得可以在尽可能不改变现有类的情况下向系统添加新的types。因为是一种类,所以理想情况下这些类能够被抽象出通用接口。
注意,是尽可能。
假设已经抽象出接口或者基类,当创建新types时,实现接口或者继承基类并扩展它们是第一个要修改的地方,这不会改变现有类,除此之外,你仍然要在使用的地方创建新类型的对象,这极有可能会改变现有类型。当创建对象的过程相对复杂并且创建对象的代码分布在整个应用框架中时,那么,维护这样一个系统将会是一个噩梦。
解决方案是,强制对象的创建都通过某一个类进行,而不允许创建对象的代码在整个应用中传播。这个类被称为工厂。
到这里,工厂模式(Factory Pattern)的雏形就出来了。因为诸如Java这样的面向对象的语言,都逃不过创建对象或通过添加新的types来扩展程序,所以工厂模式是比较常用的一种创建型设计模式。
简单来说,它有几个优点:
注意前文的types这个字眼,它从一定程度上决定了用工厂模式的使用场景。type,“一种”东西,这种东西的共性就是提炼出来的接口或者基类,它势必存在多个种属。用吃穿住行的例子来说的话,吃饭的时候桌上的猪肉牛肉就是“一种”肉,身上穿的夹克衬衫就是“一种”衣服,单身公寓/loft都是“一种”房子,公交车私家车都是“一种”车。当系统中存在这些种属且存在不同条件下需要创建不同的对象的情况(如桌上有牛肉猪肉鸡肉,假设吃饭的过程就是一个系统运行的过程,第一筷子夹牛肉,第二筷子夹鸡肉……整个吃饭的过程中,鸡肉猪肉牛肉对象会被一次又一次的创建并使用),这种情况下,使用工厂模式是一种很好的选择。
工厂模式可以分为:
用前文的桌上的肉的例子来说明。
package factorypattern;
public class Meat {
private static int counter = 0;
private int id = counter++;
@Override
public String toString(){
return getClass().getSimpleName() + "[" + id + "]";
}
public void taste() {
System.out.println(this + " tastes good");
}
}
package factorypattern;
public class Chicken extends Meat {}
package factorypattern;
public class Beef extends Meat {}
package factorypattern;
public class Chicken extends Meat {}
package factorypattern;
public class MeatFactory {
// 方法可考虑使用静态
public Meat createMeatToEat(String type) {
switch(type) {
case "chicken": return new Chicken();
case "beef": return new Beef();
case "pork": return new Pork();
default: return null;
}
}
}
package factorypattern;
import java.util.stream.Stream;
public class Dinner {
public Dinner(MeatFactory fac) {
Stream.of("chicken", "beef", "pork",
"beef", "chicken", "beef", "pork")
.map(fac::createMeatToEat)
.peek(Meat::taste)
.count();
}
public static void main(String[] args) {
new Dinner(new MeatFactory());
}
}
输出如下:
Chicken[0] tastes good
Beef[1] tastes good
Pork[2] tastes good
Beef[3] tastes good
Chicken[4] tastes good
Beef[5] tastes good
Pork[6] tastes good
从输出看,每块肉都不一样,这足以区分我夹起来的每一块肉。工厂好像厨师,一把做了一盘肉给你端上来,你不需要知道他是怎么做的,也不需要自己一块一块的去煮熟,只需要一块一块的夹起来吃就行了。当在餐桌上吃的远比例子中列举的数量多得多,或者煮熟(即创建对象)本身很难时,厨师一把做了(工厂模式)会省事很多。
尽管看起来已经比较方便,但事实上隐藏着问题:每添加一种肉,除了要继承基类或者实现接口构造新类外,还必须在工厂类里添加对应的创建过程,这个过程从一定程度上违背了设计模式的原则之一“开闭原则”,所以有了下面的工厂方法模式。
工厂方法模式(Factory Method Pattern),也叫多态工厂(Polymorphic Factory),其实就是把工厂方法抽象化,使得整体结构设计更加符合“开闭原则”。
package factorypattern;
public interface MeatFactoryI {
public Meat createMeatToEat() ;//不含参
}
package factorypattern;
public class ChickenFactory implements MeatFactoryI{
public Meat createMeatToEat() {
return new Chicken();
}
}
//其余几种肉亦是如此
package factorypattern;
public class RoastDuck extends Meat {
}
(2)添加抽象工厂,添加创建过程
package factorypattern;
public class RoastDuckFactory implements MeatFactoryI{
public Meat createMeatToEat() {
case "roastduck": return new RoastDuck();
}
}
当然,在使用的时候,因为工厂有了区分,所以不能像简单工厂里面一样使用流式编程处理了。
当考虑随机访问的时候,可以参考下面《on java8》的方法
//Shape为基类,Circle、Square、Triangle分别继承Shape,略
interface PolymorphicFactory {
Shape create();
}
class RandomShapes implements Supplier<Shape> {
private final PolymorphicFactory[] factories;
private Random rand = new Random(42);
RandomShapes(PolymorphicFactory... factories){
this.factories = factories;
}
//随机抽取
public Shape get() {
return factories[ rand.nextInt(factories.length)].create();
}
}
public class ShapeFactory3 {
public static void main(String[] args) {
RandomShapes rs = new RandomShapes(//函数式
Circle::new,
Square::new,
Triangle::new);
Stream.generate(rs)//用rs提供的元素(get()方法)生成无限长流
.limit(6)//取前6
.peek(Shape::draw)//每个调用方法
.peek(Shape::erase)//每个调用方法
.count();//终止流
}
}
抽象工厂模式(Abstract Factory Pattern)乍一看起来,与工厂方法模式并没有不同,细心一看的时候,会发现其实在抽象工厂模式里,工厂对象拥有的不再是一个工厂方法而是多个,每一个工厂方法都创建不同种类的对象。
比较官方的定义是,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
还是以吃饭为例,这会儿以上菜为主题,两种相互依赖的接口分别是肉类以及装它的盘子。
//盘子接口
package abstractfactory;
public interface Plate {
void called();
}
//肉类接口
package abstractfactory;
public interface Meat {
void servedWith(Plate p);//两者联系
}
//猪肉
package abstractfactory;
public class Pork implements Meat{
@Override
public void servedWith(Plate p) {
System.out.print("Pork is served with ");
p.called();
}
}
//牛肉
package abstractfactory;
public class Beef implements Meat {
@Override
public void servedWith(Plate p) {
System.out.print("Beef is served with ");
p.called();
}
}
//圆盘子
package abstractfactory;
public class CirclePlate implements Plate {
public void called() {
System.out.println("CirclePlate");
}
}
//方盘子
package abstractfactory;
public class SqurePlate implements Plate {
@Override
public void called() {
System.out.println("SqurePlate");
}
}
package abstractfactory;
import java.util.function.Supplier;
public class DishesFactory {
Supplier<Plate> plate;
Supplier<Meat> meat;
}
//圆盘装猪肉
package abstractfactory;
public class PorkAndCirclePlate extends DishesFactory {
PorkAndCirclePlate(){
plate = CirclePlate::new;
meat = Pork::new;
}
}
//方盘装牛肉
package abstractfactory;
public class BeefAndSqurePlate extends DishesFactory {
BeefAndSqurePlate(){
plate = SqurePlate::new;
meat = Beef::new;
}
}
package abstractfactory;
public class Dishes {
private Meat meat;
private Plate plate;
Dishes(DishesFactory fac){
meat = fac.meat.get();
plate = fac.plate.get();
}
public void serve() {
meat.servedWith(plate);
}
public static void main(String[] args) {
DishesFactory pc = new PorkAndCirclePlate(), bq = new BeefAndSqurePlate();
Dishes d1 = new Dishes(pc), d2 = new Dishes(bq);
d1.serve();
d2.serve();
}
}
Pork is served withCirclePlate
Beef is served withSqurePlate
实际用起来的时候,环境可能远比例子要复杂很多,比方说如果是一碗汤,里面有肉有菜,你得拿盘子装,你得放个勺子等等。
举一反三,理解其中奥妙比例子的简易程度重要。准确来说,抽象工厂模式处理的是“一族”产品,一盘菜该用什么原料用什么盘子装全都归在这一族里,它保证用户只能这么消费这一整个族、这一整个过程,而不是单点什么原料什么盘子。
抽象工厂模式隔离了具体类的生成,用户不会也不必知道什么被创建,将多个对象绑定在一起,改变具体工厂即可改变系统的行为。增加一个产品族也很方便,实现虚拟工厂就行了。
但是,当业务不再只是原来的业务,例如我需要规定除了用什么原料用什么盘子装之外,我还要规定用什么佐料哪个厨师来做哪个服务员上菜等等,就需要对虚拟工厂下手,此外还要对各个实现类进行修改,这使得必须对原有系统进行较大规模的修改,带来维护难度,同时也违反了“开闭原则”。
工厂模式是一种常用的创建型模式,分为简单工厂模式,工厂方法模式和抽象工厂模式,三种模式依次增加抽象程度。抽象工厂模式在虚拟工厂只有一个工厂方法的情况下就会降维成为工厂方法模式,工厂方法模式在剥离抽象层的情况下也会降维为简单工厂模式。
三种模式都有其各自的优缺点和使用场景,需要辩证的看待这种模式所能给我们系统带来的效益,也需要合理的使用以避免给系统平添复杂度。
【1】BruceEckel《On Java8》
【2】https://github.com/BruceEckel/OnJava8-Examples
【3】https://www.runoob.com/design-pattern/abstract-factory-pattern.html