在面向对对象设计中,我们会通过构造各种各样的对象并组织对象之间的关系,但是在很多时候我们并不是很关心这个对象是怎么来的,或者是这个对象的创建是十分的复杂,而希望直接有个现成的方式直接去获取,客户可以直接拿来就可以使用也不用去维护这个对象的生命周期。
工厂模式也分三种比较常见的模式,简单工厂方法,工厂方法模式,抽象工厂模式
简单工厂模式
简单工厂模式的角色有三个:
- 抽象产品角色
- 具体产品,实现了抽象产品定义
核心角色工厂类(完成产品的创建逻辑)
案例:CPU的创建,全世界能制造设计CPU的公司屈指可数。Intel与Amd算是其中比较优秀的两家,很多时候我们需要使用CPU,客户商希望有个工厂直接去拿就好了,希望将对象的创建过程交给工厂,这个时候我们采用工厂模式。
// 工厂类,完成产品的创建
class CPUFactory {
// 创建产品的接口
public Cpu getCpu(String name) {
if ("Intel".equals(name)) {
// 制造工艺十分的复杂
return new Intel();
}
if ("Amd".equals(name)) {
return new Amd();
}
return null;
}
}
// 产品接口,具体产品需要实现的功能定义在这里面
interface Cpu {
void compute();
}
// 具体的产品
class Intel implements Cpu {
public void compute() {
System.out.println("intel finished compute");
}
}
class Amd implements Cpu {
public void compute() {
System.out.println("amd finished compute");
}
}
// 客户调用如下
CPUFactory factory = new CPUFactory();
Cpu cpu = factory.getCpu("Intel");
cpu.compute();
cpu = factory.getCpu("Amd");
cpu.compute();
解决的问题:简单工厂模式的方法优点是结构比较简单,让用户可以不关心对象的创建,创建工作交给了工厂,如果是对象的种类变化不是很剧烈,简单工厂模式可能是一种较好的让业务和对象创建解耦的方式。
优点:结构简单,让对象的创建逻辑与使用逻辑分开。
缺点:关键都集中在工厂类,如果对象的种类很多,工厂类里可能有大量的if-else语句,每添加一个具体的产品都要去添加判断,修改原有的代码,这样是不符合开闭原则的,需要知道具体的产品都有哪些,如果添加新的产品,需要修改工厂类。所以如果产品的具体实现种类比较多的情况下就不太适合用简单工厂模式了
改进:这样的设计依赖传入的参数,这样增加了出错的可能性,因此我们可以改进一下,将每一个对象的创建拆分成多个函数,这样就可以避免传入参数的错误。
// 修改工厂类CPUFactory如下:
class CPUFactory {
public Cpu getIntelCpu() {
return Intel();
}
public Cpu getAmdCpu() {
return Amd();
}
}
这样修改返回的对象可以是具体的产品对象,这样看来定义产品CPU接口的意义就不大了。一个工厂应该是创建一类产品,所以产品接口的约束可以让工厂的职责更加的单一,不同的产品通过参数去控制会更加的灵活。
使用总结:简单工厂方法的应用场景就是屏蔽了创建一类复杂对象的细节,在种类不多的情况结构十分的简单清晰,方便了产品的使用,工厂需要知道具体产品的种类,不太符合单一职责原则。如果种类变化比较多比较灵活,会导致频繁的修改原有的代码。各种产品创建逻辑交织在一起,非常不利于系统的维护,这个时候简单工厂可能就不太适合。对于产品类别比较稳定,但是具体产品变化比较多的情况下可以采用工厂模式解决。
工厂模式
根据简单工厂模式的表述,我们将使用对象和对象的创建已经分离开来了。但是工厂类还是依赖于具体的实现类,如果实现类需要添加,依旧会改变工厂类,需要强调的是简单工厂与工厂模式在字面上的含义虽然是简单与“高级”的区别,但是实际上并没高级与低级区分,简单工厂只是在客观上来说设计的结构比较简单,在很多应该使用简单工厂的情况下盲目的引入工厂模式可能反而导致设计的复杂性,如果创建的对象的种类有会有不可预期的增长,这样对于简单工厂模式来说可能就比较难以应付这样的问题我们可以在工厂模式中得到解决。
工厂模式的角色如下:
- 1).抽象产品
- 2).具体产品
- 3).抽象工厂
- 4).具体工厂
说明:抽象产品为产品的抽象,具体产品去实现即可。抽象工厂依赖于抽象的产品,这里是工厂模式的键,将工厂从具体的产品中脱离出来,具体工厂去实现抽象工厂,具体产品的创建延迟到了抽象工厂的实现类具体工厂里。抽象依赖于抽象,使整个创建的架构变得稳定,添加新的产品只需要再添加一个新的具体工厂实现类。
案例:生产汽车也是一个比较复杂的工作,用户每次出行可不想都去创建汽车,希望直接拿来就用,根据场景,汽车的种类肯定比CPU要多很多,所以我们不能再采用简单工厂模式去设计了,这次我们采用工厂模式去完成各种汽车的创建,并且以后还可以方便的添加新的汽车。
// 定义抽象产品 汽车
interface Car {
// 汽车能跑
void run();
}
// 具体的产品 宝马汽车
class BMW implements Car {
public void run() {
System.out.println("bmw is running");
}
}
class Benz implements Car {
public void run() {
System.out.println("benz is running");
}
}
// 抽象工厂对象 汽车工厂
interface CarFactory {
Car produce();
}
// 具体的汽车工厂
class BMWFactory implements CarFactory {
public Car produce() {
// 假装制造汽车比较复杂
return new BMW();
}
}
class BenzFactory implements CarFactory {
public Car produce() {
return new Benz();
}
}
class User {
public void useCar() {
// 用户使用汽车
// 想要宝马车
Car car = getCarFromFactory(new BMWFactory());
car.run();
// 需要奔驰车
car = getCarFromFactory(new BenzFactory());
car.run();
}
public Car getCarFromFactory(CarFactory carFactory) {
return carFactory.produce();
}
}
解决的问题:工厂模式解决了产品种类扩充困难的问题,如果某一天我需要新的汽车,直接定义一个新的汽车去实现原有的汽车接口,再去新建一个生产该汽车的具体工厂即可。这样就不用去修改原有的代码,灵活的扩充了产品的种类。
优点:分离了对象的创建和对象的使用,对于产品灵活的扩展性。
缺点:增加了系统的复杂性,增加了类的数量。
总结:简单工厂模式和工厂模式都解决的是一类产品的创建,简单工厂适用于产品种类不多,迫切的需要分离产品的创建和产品的使用的场景。工厂模式不仅继承了工厂模式的优点,还可以灵活的扩充具体的产品,但是代价是增加了更多类的数量和系统的复杂性。
抽象工厂模式
简单工厂和工厂模式都是解决了一类产品的创建问题,但是我们可能很多时候是需要一系列的产品族,而且每一个产品族都是具有相关性的,因此叫做产品族。
举个简单的例子,我们使用的手机和平板电脑和PC是大的产品系列,某个客户是个苹果产品爱好者,他只用苹果产品,另一个客户是微软的支持者,只用windows产品。这个时候我们可以模仿简单工厂定义如下:
// 定义抽象产品
interface SmartPhone {}
interface Pad {}
interface Pc {}
// 具体的产品
class Nokia implements SmartPhone{}
class Surface implements Pad{}
class Lenovo implements Pc{}
class Iphone implements SmartPhone{}
class Ipad implements Pad{}
class Macbook implements Pc{}
// 核心工厂
class ElecFactory {
public SmartPhone getSmartPhone(int type) {
// 假装生产很复杂
if (type == 1) return new Iphone();
if (type == 2) return new Nokia();
return null;
}
public Pad getPad(int type) {
if (type == 1) return new Ipad();
if (type == 2) return new Surface();
return null;
}
public Pc getPc(int type){
if (type == 1) return new Macbook();
if (type == 2) return new Lenovo();
return null;
}
}
这样ElecFactory为核心的类承担了过多的职责,而且添加新的产品也会出现跟简单工厂模式一样的弊端,即很难扩充新的具体产品。而且各种类型的产品都混合在一起,比较混乱,如果种类比较多,该工厂类会变得难以维护。而抽象工厂模式就是解决这样的问题的。
于是我们采用以下抽象工厂模式:
// 抽象产品工厂,生产一系列产品(产品定义不变)
interface ElecFactory {
SmartPhone getSmartPhone();
Pad getPad();
Pc getPc();
}
// 微软的工厂,只生产微软产品
class MicrosoftFactory implements ElecFactory {
public SmartPhone getSmartPhone() {
return new Nokia();
}
public Pad getPad() {
return new Surface();
}
public Pc getPc() {
return new Lenovo();
}
}
// 苹果的工厂,只生产苹果产品
class AppleFactory implements ElecFactory {
public SmartPhone getSmartPhone() {
return new Iphone();
}
public Pad getPad() {
return new Ipad();
}
public Pc getPc() {
return new Macbook();
}
}
class User {
public void useElec() {
// 苹果产品
ElecFactory factory = new AppleFactory();
SmartPhone phone = factory.getSmartPhone();
Pad pad = factory.getPad();
Pc pc = factory.getMacbook();
// 微软产品
factory = new MicrosoftFactory();
SmartPhone phone = factory.getSmartPhone();
Pad pad = factory.getPad();
Pc pc = factory.getMacbook();
}
}
这样我们用户想使用某一系列产品的时候,直接去哪个工厂拿就行,值得一提的是具体的工厂可以根据业务的需要灵活的组织需要生产的产品族,这个具体的案例我们的业务需要就是根据苹果和微软阵营来划分了产品。某天来了一个新的产品族,例如Google的产品,我们先去添加一些Google的新的具体产品,然后再创建一个具体的Google的工厂类将这些产品组织在一起即可,不用改变原有的代码。当然如果添加了一个产品系列,还是需要改动ElecFactory,因此应当注意接口的稳定性,划分好合适的接口粒度。
总结:工厂模式属于创建型模式,重点在于分离对象的创建和对象的使用,简单工厂适用于产品族不多,具体产品类型稳定的情况下使用,优点是结构简单完成目标,缺点是扩展性较差,对于工厂的职责过多。工厂模式弥补了简单工厂模式难以扩充具体产品类型的弊端,但是增加了系统的复杂性和类的数量。抽象工厂模式可以同时创建多个产品族,可以根据需要创建具体工厂,有良好的扩展性和稳定性,缺点也是设计复杂。