工厂模式
<工厂模式大致分为 3 类: 简单工厂,工厂方法 , 抽象工厂。工厂是个生产东西的地方 ,比如我们java中new一个对象。我们都知道,当我们代码中存在 new 时 我们就 依赖于new的这个具体类 , 而工厂模式的作用就在于,把new一个对象的任务都交给他来做,而其他类只需要有他的引用就可以了,这样的好处在于该类(其他类)与具体类进行分离(解耦),该类(其他类)无需关心具体是哪个类,他只知道,某个工厂能生产出他要的产品>
1,简单工厂模式
举一个简单的例子来说明简单工厂模式,考虑一个买车店,有如下产品。
//先定义一个汽车二接口各类车型将实现他
interface Car{
public void run(); //汽车行驶
}
// 宝马车型
class BWM implements Car{
public void run(){
System.out.println(" i am BWM . i can run " );
}
}
// 奔驰车型
class Benz implements Car{
public void run(){
System.out.println(" i am Benz . i can run " );
}
}
// 法拉利车型
class FARRAR implements Car{
public void run(){
System.out.println(" i am FARRAR. i can run " );
}
}
//..........还有一系列的车
上面利用到了接口,是便于下面客服端代码使用接口多态的性质。
好有了车我们就买车喽,以下为买车店的代码。
class CarStore{
//购买并且测试该车
Car buyAndRun(Car car){
car.run();
return car;
}
//显然我们汽车有很多车型我没要根据车型来进行选择
public Car buy(String type){
switch(type)
{
case "Benz" : return buyAndRun(new Benz()) ;break;
case "BWM" : return buyAndRun(new BWM()) ;break;
case "FARRAR" : return buyAndRun(new FARRAR());break;
//.....其他车型
}
reuturn null;
}
}
可以看到这段代码的有很明显的缺点,这里我们是根据传入车型来进行选购的,现在我们这家卖车店要新进一种类型的车比如说大众,或者这个车店不买奔驰了,我们是不是要在这段代码上进行修改,增加或者删除,为什么会这样呢,因为我们这段卖车店代码是依赖于具体产品类(车类)。(你会问什么叫做依赖,就是当我们一个类直接通过new 的方式去创建一个类的时候我们就对他产生了依赖。当依赖的这个具体类被改变的时候,这个通过new 创建该对象的类也将受到影响。)显然我不想看到这样的情况发生,因为这违反了开闭原则(对扩展开发,对修改关闭)。
说到这我们不得不提简单工厂模式了,先看下面的代码。
//我们首先创建一个工厂类用来专门用来生产汽车
class CarFactory{
//购买并且测试该车
Car buyAndRun(Car car){
car.run();
return car;
}
//显然我们汽车有很多车型我没要根据车型来进行选择
public static Car buy(String type){
switch(type)
{
case "Benz" : return buyAndRun(new Benz()) ;break;
case "BWM" : return buyAndRun(new BWM()) ;break;
case "FARRAR" : return buyAndRun(new FARRAR());break;
//.....其他车型
}
reuturn null;
}
}
//修改后的汽车店
class CarStore{
CarFactory carFactory;
//将购买汽车的重任交给工厂类
public Car buy(String type){
reuturn carFactory.buy(type);
}
}
在这里我们新创建了一个工厂类,然后基本是将汽车店的代码复制到该类中去了(唯一改变是buy使用的是静态方法),之后汽车店拥有一个该工厂的引用,简单来说就是汽车店将选择汽车这件是委托给了汽车工厂来做,这里的好处就在于他实现了与具体类(汽车类)的分离(解耦),而他依赖的对象也就一个工厂类而已,不在像以前一样依赖这么多车产产品,汽车产品的改变对他也无任何的影响。
有人就会说了,这不就是将问题从一个类转到了另外一个类吗?汽车产品的变更不还是要更改工厂类中的代码。确实这些人想的没错。但是这里将那部分易变的代码从汽车店中抽离出来进行进一步的封装还是有他的好处的(什么好处?)。有没有想过汽车店不只有一家,不如老板的业务做大了,他在全国各地开分店,倒时候我们将可能需要创建更多的汽车店的类,而这部代码就是共工的部分,这样也是由利于代码的复用。这样也可以做到分工明确,汽车店只需要卖汽车,而具体汽车生产与他半毛钱关系没有。
注意: 前面将工厂类的的buy方法改为了静态的(所以也可以将静态工厂)。这样好处在于汽车店类无需实例化这个工厂类,但是还是有缺点的,该方法无法被继承重写。个人建议不要使用静态方法,但还是看具体情况而定。
2,工厂方法模式
[图片来源百度百科]
我紧接着上面汽车店的例子更进一步的来讲工厂模式的另外一种工厂方法。
有一天老板的生意做大了,他想扩大自己的生意,于是开启了连锁店,而且连锁店实行了专一话就是说某家店只卖某种类型的车比如 店A只卖宝马(宝马有很多系列 如 1、2、3、4、5、6、7、i、M、X、Z 系列)。
我们如何去设计这样的类呢?将简单工厂中的CarFactory写成接口,然后将各种工厂类来实现他,比如BwmFactory , BenzFactory等工厂,然后在汽车店类中加上一个setCarFactory的方法,根据不同的工厂就可以得到不同的车品牌以及型号。
abstract class CarFactory{
Car buyAndRun(Car car){
car.run();
return car;
}
public static Car buy(String type);
}
class BwmFactory implements CarFactory{
//根据车的型号选择
public static Car buy(String type){
switch(type)
{
case "type1" : return buyAndRun(new Bwm1()) ; break;
case "type2" : return buyAndRun(new Bwm2()) ; break;
case "type3" : return buyAndRun(new Bwm3()) ; break;
//.....其他车型
case "typeX" : return buyAndRun(new BwmX()) ; break;
}
reuturn null;
}
}
//还有其他的工厂类型不如生产奔驰的此处省略
//......
class CarStore{
CarFactory carFactory;
public void setCarFactory(CarFactory carFactory){
this.carFactory = carFactory;
}
//将购买汽车的重任交给工厂类
public Car buy(String type){
reuturn carFactory.buy(type);
}
}
注意我们前面在将简单工厂的时候没有讨论汽车的型号之说了汽车的品牌,这里将会讨论汽车的型号,而且同一品牌的车可能有多种不同的型号,每种不同型号的车将设计成一个类,比如宝马 Bwm1~X 。但每种汽车型号还是实现自Car接口。这里我们将汽车的类省略了。
好我们来看下客服端代码怎么写:
class Client{
public static void main(String[] args){
//我要买一辆宝马X系列的车
//创建一个店子
CarStore carStore = new CarStore();
//设置这个店所买的车都来自哪个 工厂
carStore.setCarFactory(new BwmFactory());
//给出车的型号
carStore.buy("typeX");
}
}
汽车店和汽车的具体类实现了解耦,汽车店所依赖的仅仅是一个抽象类 CarFactory ,具体是哪个工厂他也无需关心。
这也将引出我们将要讲到的一个原则
依赖倒置
高层模块不应该依赖于底层的具体类,而应当依赖于抽象的接口。
底层模块也应该依赖抽象接口。
要针对接口编程。
汽车店最开始的设计是直接通过new 具体的汽车来实现的 因此对底层具体汽车类依赖十分强,哪怕一点改动对他造成影响可能都很大。现在汽车店类只是与工厂接口挂上了钩,工厂接口依赖于汽车接口Car(只是工厂抽象类的实现类与具体类有依赖关系),汽车的具体实现类确是依赖于Car的(遵循他的规则并实现他)。看下图
回到我们要说的工厂方法工厂接口 CarFactory 中buy其实就是工厂方法(factoryMethod),而具体的工厂实现类实现了该方法,没错就是工厂方法模式。
有人就会说这和简单工厂模式有啥区别啊,不都是一样的吗?确实当工厂类只有一个的时候,这两者之间确实区别不大。但是他们之间确实是有差异的(其中的含义不同),简单工厂他是将创建具体类的全部任务封装在自己的类中并去执行,而工厂方法却不一样,他是为创建具体类提供了一个框架,工厂实现类只需要实现他的方法就可以了,当创建具体类的代码发生变化时,工厂方法弹性更好,它可以再次创建一个工厂实现类即可,而简单工厂可能需要修改原有的代码或者好点的方法是通过继承的方式覆盖掉原来的方法(前面说的建议不要用静态方法因的原是),但是这违背了里氏替换原则(子类扩展父类时尽量不要将原来的方法覆盖,如果覆盖说明该子类不适合继承父类,这里说的比较笼统可以自行查看资料了解里氏替换原则)。
也没有绝对的好坏,只是具体情况具体分析。
3,抽象工厂模式
[ 图片来源百度百科 ]
最后我们来将抽象工厂模式。任然我们使用上面卖车的例子来将,我们前面讲的时老板的每家连锁店只能卖一种品牌的车,比如该店只能卖宝马,另外一家店只能卖奔驰。现在老板有新的点子就是,每家连锁店可以卖所有品牌的车但是,某种品牌的车只能卖一种型号 比如一家连锁店能卖 宝马X系列 奔驰A级车等。这个时候我们的工厂应该重新设计了。话不说我们先上代码。
//我们先创建一个工厂接口
interface CarFactory{
Car createBWM() ;
Car createBenz() ;
Car createFerrari();
//还有其他各种品牌的车
//......
}
这里我们定义了一个工厂接口,具体的工厂类实现这个规范即可。以下为具体的实现类。
//为北京连锁店提供产品
class BeijingFactory implements CarFactory{
Car createBWM(){
return new BwmX(); //宝马X系列
}
Car createBenz(){
return new BenzA(); //奔驰A级
}
Car createFerrari(){
return new Ferrari458(); //法拉利458
}
}
//为上海连锁店提供产品
class ShanghaiFactory implements CarFactory{
Car createBWM(){
return new Bwm1(); //宝马1系列
}
Car createBenz(){
return new BenzB(); //奔驰B级
}
Car createFerrari(){
return new Ferrari488(); //法拉利488
}
}
//.........
最后我们来看看连锁店的代码。
我们先定义一个连锁店的类。
class CarStore{
CarFactory carFactory;
Car buyAndRun(Car car){
car.run();
return car;
}
public void setCarFactory(CarFactory carFactory){
this.carFactory = carFactory;
}
//根据车的型号选择
public static Car buy(String brand){
switch(brand)
{
case "Bwm" : return carFactory.createBWM(); break;
case "Benz" : return carFactory.createBenz(); break;
case "Ferrari" : return carFactory.createFerrari(); break;
//.....其他车的品牌
}
reuturn null;
}
}
最后我们来看看来客服端的代码
class Client{
public static void main(String[] args){
//创建一个连锁店的类
CarStore carStore = new CarStore();
//我们逛的是上海的连锁店于是我们将工厂设置为上海的
carStore.setCarFactory(new ShanghaiFactory());
//现在我们要买一辆奔驰车 这里买到的奔驰车是奔驰B级因为上海店买的奔驰车只有B级
carStore.buy("Benz");
}
}
到这里设计已经完成,我们开始分析代码。先不要纠结为什么一家连锁店一种品牌的车只卖一种型号的,虽然这有些不现实但是我这里只是为了说明抽象工厂的模式,是怎么样的结构,或许其他的例子可能更加贴近现实,我们先不要管。
抽象工厂是定义了生产一个产品族的接口,具体用哪个产品由具体实现类来选择。在这里所说的产品族是所有品牌的车,而不同的工厂实现类是将同一产品族(即所有品牌的车)的不同车的型号进行组合,就比如说上海工厂他生产的产品族是宝马,奔驰,法拉利等,他生产的型号分别是宝马1系列,奔驰B级,法拉利488。北京工厂的产品族和上海工厂的相同(他们是实现同一个工厂接口),而北京生产的车型号组合却是不相同的。
连锁店类关心只是工厂接口,具体是哪个工厂实现类与他无关。通过客服端对工厂实现类的不同设置,可以得到不同的车的型号。
说了这么多到底什么是抽象工厂模式,有啥用啊!
抽象工厂模式他适用的场景是,对于某类确定的产品族,对于产品族中各类产品有不同的实现。就比如说我一辆汽车,他各个部件的来源是不同的,轮胎来自美国,发动机是德国的,同一部件也也可能来源是不同的,比如轮胎还可能来自日本。对于一辆汽车的的产品族(构成汽车的所有部件)是确定,将来自不同国家的部件进行组合就可以构成不同型号的车了。
抽象工厂的一个缺点是当产品族发生变更时,需要修改原有的代码,所以对抽象工厂选择时产品族应当明确。
抽象工厂和工厂方法区别在哪?抽象工厂是对于某类产品族产品的生产,他给出了该产品族的生产各个产品的所有方法,其他类只需要通过他给的这些方法创建所需要的产品即可。工厂方法则不同,子类工厂实现父类中某一工厂方法,通过该工厂方法来生产产品。
这几个工厂模式之间的差别是明显的却也是细微的,通过实际的开发我们能从中体会。我们也应当根据实际开发中的需求进行选择。