1.“new”有什么不对劲?
在我们没有接触到工厂模式(简单工厂、工厂方法模式、抽象工厂模式)之前,我们实例化对象唯一的方法就是通过“new”关键字来完成。但是,大量的使用“new”关键字来实例化对象会违背一些设计原则,因为代码与具体的类型绑在一起,从而导致过多的依赖于细节而非抽象,这样代码就很难适应需求变化。
在面向对象编程中我们大量的使用到了继承、多态的特性,但是在使用这些特性时往往会延申出一些新的问题。例如在一个有继承模块的代码当中,如果子类发生新增或删除,这就不得不去使用类的“调用层”做出相应的修改,因为你new的都是具体的类型,当下层发生变动就不得不去上层进行修改。对于这样的场景来说,实际上它也违背了设计模式原则之一的“开闭原则”,即对扩展开放,对修改关闭,这会不利于程序的稳定和扩展。
2.举例反映问题
接下来将通过代码案例来反映出,在一个使用继承模块的代码当中,大量使用“new”关键字,在设计层面会带来什么样的缺陷。我们以一个汽车销售系统作为背景,其中汽车类型UML图如下:
其中有一个卖车的方法使用到了“new”来实例化车型对象,相应的代码如下:
1 //卖车方法 2 public void SellCar(string carType) 3 { 4 Car car; 5 if(carType==”宝马 X3”) 6 { 7 car=new Bmwx3(); 8 } 9 else if(carType==”吉普牧马人”) 10 { 11 car=new Jeeper(); 12 } 13 else if(carType==”奔驰G63”) 14 { 15 car=new BenzG63(); 16 } 17 18 TestDrive(car);//试驾 19 Collection(car);//收款 20 License(car);//上牌 21 }
在实际的生活当中我们应该都知道,4s店卖的车型不可能是一层不变的,而是经常性的会发生产品的更新换代,所以会不断对车型做出新增或删减。如果汽车销售系统中使用了以上的代码,这就意味这每次车型模块的调整都要同步到“卖车方法”中,并且随着公司规模的增大所卖的车型会增多,在新增车型后代码中出现大量的else if,这会显得非常臃肿,在删除时还可能误删。
即便你说你的“汽车销售系统只卖一种车”(也就是只new一种对象类型),你可能还是会在使用构造函数创建对象时要加入参数的情形,这些变化都会促使你因为下层模块的调整而影响到上层模块。
3.改善方法
1.编程当中任何需求或代码都是无法避免的,而我们能做到就是运用设计模式的思想,能够将程序更好的去适应变化。其中的一个思想就是:“找出变化的部分,把它们从不变的部分分离出来”。以上面的“汽车销售系统”中的卖车方法为例,分析变化如下:
2.然后,我们将变化的部分单独的封装到一个类的静态方法中,该类的静态方法将专门用于创建具体的汽车对象,代码如下:
1 public class CarFactory 2 { 3 public static Car CreateCarInstance(string type) 4 { 5 Car car=null; 6 if(carType==”宝马 X3”) 7 { 8 car=new Bmwx3(); 9 } 10 else if(carType==”吉普牧马人”) 11 { 12 car=new Jeeper(); 13 } 14 else if(carType==”奔驰G63”) 15 { 16 car=new BenzG63(); 17 } 18 return car; 19 } 20 }
3.在抽离并封装变化后,我们将对“卖车方法”中的实例化车对象部分进行改造。
1 public void SellCar(string carType) 2 { 3 Car car=CarFactory.CreateCarInstance(carType); 4 5 TestDrive(car);//试驾 6 Collection(car);//收款 7 License(car);//上牌 8 }
根据以上的三个步骤,其实一个简单工厂创建对象的方式就已经形成了,其中专门创建对象的Factory类我们称之为工厂,它实例化的对象我们称之为产品。此时我们创建对象的方式不在是”new”,而是通过一个静态方法。
对于上层模块(卖车方法),在也不用担心下层模块(汽车车型)的变化而牵连到自身,它只有根据指定的类型创建对应的车型对象即可,并且上层模块(卖车方法)也不用在承担对象创建的细节,减少大量代码和复杂度。
另外简单工厂也解决了对象创建复用的问题,例如4s店的汽车保险系统、汽车维修系统等等都会用到实例化车型对象。这样的话,如果车型发生修改我们只用调整简单工厂类即可而其他(调用层)都不必受到改动的牵连。
4.定义
简单工厂又称之为静态工厂方法,它属于创建型模式中的一种简单运用。
严格来讲它其实并不属于一种设计模式,反而比较像我们在遵循设计模式原则时的一种编程习惯。你要你能够在编程中遵循并灵活运用:迪米特法则(最少知道原则)、依赖倒置(依赖抽象,不依赖细节)、里氏替换(子类替代父类)这些原则,其实就会无形当中使用简单工厂来实现对象的实例化,而并非只使用“new”。
5.短板
使用简单工厂实例化对象虽然解决了一些问题,但是有同时存在一些新的问题。因为我们的工厂本身就是一个具体的类型,这违反了面向接口(抽象)编程的思想。
还是以汽车销售作为背景来说,假如你依赖的这个A工厂因为疫情出现停产,又或者因为你要扩张你的车型使用新的工厂,那么此时你依赖的A工厂(具体的类)就很难做出扩展,而只能因为你换了工厂导致上层模块都要跟着调整。所以,后续诞生出的“工厂方法模式”和“抽象工厂模式”,就是在不断解决因为变化而带来各种的问题。