java设计模式之工厂方法探究
简单工厂 + 工厂方法 + 抽象工厂
看了十几篇博客,每篇基本上都能有个自己的解释,我汇总这些内容,重新梳理整理了一番,以形成自己的理解。
简单工厂模式其实不算23种设计模式之一,它是一个非常简化版本的工厂。
这里只有一个工厂对象SimpleFactory,负责创建多个AbstractProduct类型具体产品实例。
public class SimpleFactory { public static void main(String[] args) { Car car = CarFactory.createCar("Audi"); car.setBrand("Audi"); //生产 car.produce(); } } abstract class Car{ private String brand; private String series;//暂时用不到 public abstract void produce(); public void setBrand(String brand) { this.brand = brand; } public void setSeries(String series) { this.series = series; } public String getBrand() { return brand; } public String getSeries() { return series; } } //具体产品 class Audi extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } class Bmw extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } //简单工厂 class CarFactory { public static Car createCar(String car){ Car c = null; if("Audi".equalsIgnoreCase(car)) c = new Audi(); else if("Bmw".equalsIgnoreCase(car)) c = new Bmw(); return c; } }
在这个案例中,我要生产一辆车,不再需要自己new一个车,而只需要传入一个类型(品牌),根据类型来创建车。的确隐藏了实现细节。但是问题在于:
使用者首先必须确定要create哪种车,然后传入类型,然后在工厂里又if-else判断了到底是哪种车,违背了开闭原则,我需要增加对其他子类的支持,必须修改代码,增加else判断分支。毫无疑问是有问题的,看起来这个if-else很多余,我既然已经告诉你我要奥迪车,你为什么内部自己还判断了呢。当然,反射是可以避免if-else 的,但是要使用Class.forName() 必须要传入类的完全限定名,这点在使用中是非常麻烦的。
如何解决?
既然我在工厂里面也要判断哪种品牌,那为什么我不将判断拿出来呢?对于奥迪车,我建立一个小工厂专门生产奥迪车,而奔驰、宝马我也各自建一个工厂去生产。这样,在客户调用的时候,你只需要告诉我你准备用那个公司的生产车间,我就能生产这个品牌的汽车,这也就是工厂方法模式。
工厂方法模式,为了避免if-else判断,干脆再封装一层工厂
以此图为例
client持有AbstractFactory的引用,AbstractFactory可创建具体工厂对象ConcreteFactory1,ConcreteFactory1。
每个具体工厂可生产具体的产品 ConcreteProduct1,ConcreteProduct2。这两个具体产品可抽象为AbstractProduct。因此用抽象父类接受具体子类对象。
public class FactoryMod { @Test public void test() { CarFactory factory = new AudiFactory(); factory.createCar().produce(); } abstract class Car{ private String brand; private String series;//暂时用不到 public abstract void produce(); public void setBrand(String brand) { this.brand = brand; } public void setSeries(String series) { this.series = series; } public String getBrand() { return brand; } public String getSeries() { return series; } } class Audi extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } class Bmw extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } class AudiFactory implements CarFactory{ public Car createCar(){ Car audi = new Audi(); audi.setBrand("audi"); return audi; } } class BmwFactory implements CarFactory{ public Car createCar(){ Car bmw = new Bmw(); bmw.setBrand("bmw"); return bmw; } } //工厂 interface CarFactory { Car createCar(); } }
此处我建了两个工厂,分别生产不同品牌的汽车,这样客户端调用时,只要指明了是哪个工厂,就不必再有if-else判断。如果我们增加奔驰生产的功能,只需要在Car下面增加奔驰类,并增加生产奔驰类的车间,无需改动代码,就可以生成奔驰车。
而且我还可以指定生产什么型号的汽车:
class AudiFactory implements CarFactory{ public Car createCar(String series){ Car audi = new Audi(); audi.setBrand("audi"); audi.setSeries(series); return audi; } } class BmwFactory implements CarFactory{ public Car createCar(String series){ Car bmw = new Bmw(); bmw.setBrand("bmw"); bmw.setSeries(series); return bmw; } } //我指定汽车型号,客户端想要生产汽车的时候必须告诉我型号,然后我就可以给你相应型号的汽车。 interface CarFactory { Car createCar(String series); }
上面的代码就可以实现生产多个车型
但是这种写法其实是有问题的,或者说适用性不强。因为这里我是吧车型当成了Car的一个内部属性,但是实际上,车型可能是一个单独的属于奥迪车的子类。
那么如果我不仅要生产奥迪,我还要指定车型,比如奥迪A4,奥迪A6呢?工厂方法如何实现?
public class FactoryMod2 { @Test public void test() { CarFactory factory = new AudiFactory(); factory.createCar().produce();//生产奥迪汽车 factory.createCar("A4").produce();//生产奥都A4 } //汽车都有自己的序列号 abstract class Car { private String id = String.valueOf(Math.random()); public String getId() { return id; } public void setId(String id) { this.id = id; } public abstract void produce(); } //奥迪汽车还有自己的品牌名 class Audi extends Car { private String brand = "Audi"; public void produce() {
System.out.println("produce : " + this.getBrand() + "," + this.getId());
} public String getBrand() { return brand; } } //奥迪A4汽车除了品牌还有型号 class AudiA4 extends Audi { private String series = "A4"; public void produce() { System.out.println("produce : " + this.getBrand() + "," + this.getSeries() + "," + this.getId()); } public String getSeries() { return series; } } class AudiA6 extends Audi { private String series = "A6"; public void produce() { System.out.println("produce : " + this.getBrand() + "," + this.getSeries() + "," + this.getId()); } public String getSeries() { return series; } } class AudiFactory implements CarFactory { //要判断客户要什么系列 public Car createCar(String series) { Audi car = null; if (series.equals("A4")) return new AudiA4(); else if (series.equals("A6")) return new AudiA4(); return null; } public Car createCar(){ return new Audi(); } } //简单工厂 interface CarFactory { Car createCar(String series); Car createCar(); } }
代码稍微复杂了一点,这里我省略了宝马工厂的创建,都是相似的,并且调整了继承体系。
我们很容易在Audi下在派生子类AudiA4 和 AudiA6 没问题,在用户调用的时候我们要把用户要求的型号告诉车间,车间里判断用户到底是要哪个系列,我们就给他哪个系列。很好,我们实现了功能:
produce :Audi,0.5005191840367693
produce :Audi,A4,0.1326089233983656
但是if-else的问题又来了,我们在奥迪生产车间里,又一次遇到了if判断,判断到底是哪种系列。就像一开始我们用简单工厂模式一样。
随着需求的复杂度提高,单一的奥迪工厂已经无法轻松写意的生产各种型号的奥迪汽车,它的produce方法开始负责A4和A6汽车的生产,这违背了单一职责原则。进而违背了开闭原则。
怎么办?
我们还是从刚才简单工厂到工厂方法的转变上,试着寻求消除if-else的方式,前面在简单工厂中,调用者已经指明我要的是奥迪汽车,但是工厂里还是判断了一次,因为工厂意图承担多个创建责任。我们分析之后觉得应该把这个判断拿出来。也就是说让客户直接选择要创建哪种汽车,但是又不能直接new,所以我们就在原先工厂的地方再用一次工厂,将原先的职责划分开来,形成奥迪工厂和宝马工厂,然后调用者直接拿奥迪工厂生产奥迪汽车,现在因为需求变更,奥迪工厂不得不承担多个生产责任,产生多个if-else。我们要干掉他,所以我们选择在奥迪工厂上,仿照上面的做法,再用工厂划分出奥迪A4产品线,奥迪A6产品线。
我初步试了一下。
class AudiA4Factory implements CarFactory{ public Car createCar(){ return new AudiA4(); } } class AudiA6Factory implements CarFactory{ public Car createCar(){ return new AudiA6(); } }
发现这种基于垂直的继承体系,最好的办法就是直接使用父类工厂,直接创建子类,也就是说无论是奥迪还是奥迪A4,还是宝马,都直接用对应的AudiA4Factory,AudiA6Factory来生产。既然客户端知道确定的类型,就直接创建确定类型的工厂。当然这里看起来好像不太科学,如果垂直继承体系很深,那么不同各层级的工厂有些不太清晰。关于这种垂直继承体系,使用什么来创建比较好,此处不展开讨论,或许直接创建子类的工厂,子类的子类的工厂更好,如果要实现单一职责开闭原则的话,容易变化的就不能作为方法存在,比如生产各种型号的汽车,如果做成createAudiAt() createAudiA6()方法,就会出现之前的问题。
我们还有个问题没有解决:抽象工厂怎么用?
抽象工厂的应用场景,不是类似于这种垂直的产品体系,因为这种垂直的体系就只有一个产品等级结构。
产品族与产品等级结构如图所示。
产品等级结构就是一系列具有垂直继承体系的产品。产品族就是一系列没有继承关系的产品集合,比如手枪和子弹,鼠标和键盘。
通过工厂方法与抽象工厂方法的区别来理解抽象工厂模式:
对于工厂方法:此处抽象产品类就是Car,派生的具体产品类就是Audi,Bmw。此处抽象工厂类就是CarFactory。具体工厂类就是AudiFactory.
根据前面的分析,抽象产品类可以派生多个具体产品类。抽象工厂类可以派生多个具体工厂类,而具体工厂类只能创建一个具体产品实例。多了就要违背开闭。
对于抽象工厂:是有多个抽象产品类的,也就是多个产品族,例子有:汽车、飞机、航母;枪与子弹,鼠标与键盘等。是不同继承体系的产品。
多个抽象产品类可以抽象出多个具体产品类,比如雷蛇鼠标雷柏鼠标,键盘可能是雷蛇键盘雷柏键盘等。每个抽象工厂,可以派生多个具体工厂类,而每个具体工厂类可以创建多个产品实例,意思就是每个工厂都能生产鼠标和键盘,但是却有不同的工厂去生产不同的实例。
类图如下:
现在有抽象工厂类AbstractFactory,它可以创建几个具体的工厂 ConcreteFactory1,ConcreteFactory2,具体的每个工厂都能生产具体的A产品和B产品,但是A,B产品并没有继承关系,它们是不同的产品等级体系,现在要增加一个产品族,只需要增加一个相应产品族的工厂和具体的产品,比如A3,B3。大师要增加一个新产品比如C,那么3个工厂都需要修改内容,以生产新的产品。
代码:
//抽象产品(Bmw和Audi同理) abstract class BenzCar{ private String name; public abstract void drive(); public String getName() { return name; } public void setName(String name) { this.name = name; } } //具体产品(Bmw和Audi同理) class BenzSportCar extends BenzCar{ public void drive(){ System.out.println(this.getName()+"----BenzSportCar-----------------------"); } } class BenzBusinessCar extends BenzCar{ public void drive(){ System.out.println(this.getName()+"----BenzBusinessCar-----------------------"); } } abstract class BmwCar{ private String name; public abstract void drive(); public String getName() { return name; } public void setName(String name) { this.name = name; } } class BmwSportCar extends BmwCar{ public void drive(){ System.out.println(this.getName()+"----BmwSportCar-----------------------"); } } class BmwBusinessCar extends BmwCar{ public void drive(){ System.out.println(this.getName()+"----BmwBusinessCar-----------------------"); } } abstract class AudiCar{ private String name; public abstract void drive(); public String getName() { return name; } public void setName(String name) { this.name = name; } } class AudiSportCar extends AudiCar{ public void drive(){ System.out.println(this.getName()+"----AudiSportCar-----------------------"); } } class AudiBusinessCar extends AudiCar{ public void drive(){ System.out.println(this.getName()+"----AudiBusinessCar-----------------------"); } } //抽象工厂 abstract class Driver3{ public abstract BenzCar createBenzCar(String car) throws Exception; public abstract BmwCar createBmwCar(String car) throws Exception; public abstract AudiCar createAudiCar(String car) throws Exception; } //具体工厂 class SportDriver extends Driver3{ public BenzCar createBenzCar(String car) throws Exception { return new BenzSportCar(); } public BmwCar createBmwCar(String car) throws Exception { return new BmwSportCar(); } public AudiCar createAudiCar(String car) throws Exception { return new AudiSportCar(); } } class BusinessDriver extends Driver3{ public BenzCar createBenzCar(String car) throws Exception { return new BenzBusinessCar(); } public BmwCar createBmwCar(String car) throws Exception { return new BmwBusinessCar(); } public AudiCar createAudiCar(String car) throws Exception { return new AudiBusinessCar(); } } //老板 public class BossAbstractFactory { public static void main(String[] args) throws Exception { Driver3 d = new BusinessDriver(); AudiCar car = d.createAudiCar(""); car.drive(); } }
上面的代码比较清楚的展示了抽象方法的工作原理。
此处的抽象工厂是Driver3,具体工厂是BussinessDriver,SportDriver等。抽象产品是AudiCar,具体产品是AudiSportCar和AudiBussinessCar。
因此对于抽象工厂,支持多个产品族,要增加一个产品族很容易,只需要增加一个具体工厂,比如生产SUV型轿车。只需要实现一个SUVDriver,但是要增加一种汽车产品线,比如大众。就必须在所有的产品族工厂里增加createVolkswagenCar,如果用鼠标键盘举例子的话,就是每个厂商(雷柏雷蛇赛睿)就是具体的工厂,是产品族。而每中产品,鼠标键盘就是产品等级体系。
有不清楚的可以继续参考:
总结:
1.为什么用工厂方法?
概括起来大致有以下的说法:
将对象的实例化归集起来,避免一个类到处实例化对象,一旦需求变更导致霰弹式修改,如果以后要改,直接在工厂里改就行了。
类的某些实例化需要有比较多的初始化设置,放到工厂里可以封装代码,而不至于到处都是重复的初始化代码。
从解耦的角度考虑,不应该硬编码,其实Spring才是最大的工厂,管理所有的代码,实现所有代码的解耦。springIOC有什么好处,工厂差不多也有这些好处。
将对象本身的职责,对象的创建创建逻辑,使用逻辑隔离开来。
-------------------------------------------------
也有认为工厂模式华而不实的。
也有认为工厂是C++时代设计模式遗留到java中的产物,其实并不一定需要,java通过反射可以方便的拿到所需要的实例。
不过普通程序员在java开发中广泛运用spring框架,多数时候不太需要,而且对于javaWeb这种CRUD的项目来说,也不需要太多的设计模式。最后就是,许多人都没有找到适合工厂模式的应用场景,不能为了模式而套模式,导致过度设计。工厂方法到底有没有用,我还不能下定论,或许5年之后,我能理解这个模式更深层次的内涵。
2.何时使用工厂方法。
个人浅薄地认为,只要有new的地方,都可以考虑一下是否使用工厂,对于简单很少有变动的情况,可以考虑用简单的工厂,毕竟结构要清洗一点,也没有特别大的变动。对于产品等级体系发达的情况,优先用工厂模式。对于产品族发达,而产品等级体系固定的情况,用抽象工厂。有些时候这几种工厂可以组合使用,目的其实都是一样,遵循6个设计原则,实现高内聚低耦合。