在我们没有接触到工厂模式(简单工厂、工厂方法模式、抽象工厂模式)之前,我们实例化对象唯一的方法就是通过“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”。
简单工厂的UML图:
5.短板
简单工厂虽然有一些优点:将对象创建这件事进行封装,促使上层隔离变化,实现了对扩展开放,对修改关闭,并且可以达到对象创建功能的复用。然而它还存在一定程度的不足之处:简单工厂中创建的对象类型种类繁多,这不符合单一职责原则。另外,上层模块和下层模块之间并没有通过抽象建立依赖,这意味着它不符合依赖倒置原则,如果要改变使用其他工厂创建对象,此时的简单工厂是不具备适应变化的能力。
还是以汽车销售作为背景来说,假如你依赖的这个A工厂因为疫情出现停产,又或者因为你要扩张你的车型使用新的工厂,那么此时你依赖的A工厂(具体的类)就很难做出扩展,而只能因为你换了工厂导致上层模块都要跟着调整。所以,后续诞生出的“工厂方法模式”和“抽象工厂模式”,就是在不断解决因为变化而带来各种的问题。
知识改变命运 【文章原创作者:韩国机房 http://www.558idc.com/kt.html欢迎留下您的宝贵建议】