建造者模式
建造者模式是我之前有些不理解的模式,直到比着网上的demo抄的时候,也是不太理解的状态。但是当时就这么得过且过下去了,现在回顾过去,我这种学习方式是不行的,不理解,写过demo了就算作学会了?这样显然是不对的。不可以这么凑活下去了。学习这种东西不是做做样子而已。
那么我之前不理解的是什么呢?我不理解他的使用场景, 我当时认为直接new一个也挺好的,反正都能完成功能,就行了呗。另外一点是,我感觉还要写一个builder真是麻烦。原因现在也大概清楚了, 就是我之所以这么想,是因为我的工作中暂且没有碰到要创建一个复杂对象的情况,亦或是我当时并没有注意这种情况。
我想学完这个之后,我得看看我们目前的项目中还有没有我留下来的“烂代码”。有时间的话,就优化优化吧。
问题的提出
假设现在让你造一辆车,用一个对象表示一辆车。那么我们首先会拆分,车包括,轮子,轮胎,方向盘,引擎,离合,外壳, 车灯,座位, 等等等等特别多的部分。 假设里里外外100个零件。实际上更多哈。
我可能在这个对象里面有100个成员变量, 有的成员变量还可能是更高级的一层抽象–数组, 比如车灯四个, 车轮四个, 轮胎4个!
public class Car{
private 轮子 a;
private 轮胎 b;
...(此处省略98个属性...)
---构造方法省略,是个思考题---
public 轮子 get轮子(){
rehturn 轮子
}
...(此处省略99个属性的get方法)....
public void set轮子(轮子 a) {
this.a = a;
}
...(此处省略99个属性的set方法)....
}
- 问? 你如何初始化这个对象?
- 我想但凡有点基础,也不会傻乎乎的写出来一个含有100多个参数的构造器吧。
- 好, 我又想到了第二个方法, 就是我不把参数的设置写在构造器里面, 而是所有的参数都是调用set方法,就这样的方式,将成员变量的值设置进去。 勇气可嘉, 但漏洞百出!为什么呢?原因是,我们无法对参数进行检验。这种100个属性的对象,直接set属性的方式是相当危险的!比如,有个脑残将车轮设置成了三个, 没有设置轮胎, 给了两个车灯,连个引擎都没有。 我想如果在这种情况下,如果你的代码还允许这种奇葩情况, 那么请立即重构吧。 因为别人给你的物料根本就不足以建造一个车子,你却还接受,出了问题,责任全在你!
- 综上所述第二种方法也有一点脑残,那么再进行优化, 我进行合适的参数判断不就得了?
- 首先有了这种想法,是挺好的,但是你要如何做判断?尤其是针对这种缺斤少两的参数情况。 每次set的时候判断?不可能, 因为假设你现在set的是第一个成员变量,那么其余的99和成员变量百分之百是空的,判断个毛?代码怎么写,这种又是正常情况!(而且更为变态的是,有时候有种要求就是,在没有A属性的的情况下,B属性不能被设置!或者在有C属性的情况下,D属性就无效!就问你变不变态?我们平常情况下极有可能遇到欧!)但是这里不考虑这种情况,咱们先想简单的,把简单的推论清楚就可以了!
- 好, 这样就给我们带来了另外一个想法, 判断要加,只不过,判断的时机不能是set成员变量的时候。 我们把时机调整一下,比如,当第100个参数设置完的时候? 或者是当外部代码告诉你"物料购买完成请核对"!
- 好, 于是我可能会在car类加一个方法,叫做"校验"。
public class Car{ ....省略已有代码... public boolean checkParame(){ .... } }
- 但是,你有没有发现,调用"校验"的前提,是car有一个对象,这样才能调用这个方法!但是,校验之前,我连这100个参数是否合法都不晓得!凭什么让我创建一个car对象,这是个鸡生蛋蛋生鸡问题啊。
- 好,目前又遇到一个问题了哈,就是
- 1 物料校验不合格,我绝对不生成car对象。
- 2 想要执行校验逻辑,就必须生成一个car对象,这样才能调用!
- 如果不做改变这问题基本无解了。
- 所以再改进!引入一个新类,这个"校验"写到这个新类里面, 我们创建这个新类的对象来调用这个方法。 但是我们要保证,校验不通过,连真正的car对象都不让你生成!因为本来应该如此啊!重点快来了,我们此时已经发现要借助一个外部类了,不久你就会发现这个类其实就是建造者模式中的builder
我想如果你仔细点的话,可能一眼就看出了,checkParame(Car car) 的参数,是个Car! 但是规定好的,校验属性不合格,就不应该生成Car。 所以吧,这么写又是不对的!public class CheckCarUtil{//推演是完全按照未知的情况下进行的,所以这里类的名字可以是任意的名字,不一定非得叫建造者模式的某个固定的名词,如 XXXBuilder!这里之所以这么叫,是为了防止我死记硬背。 public boolean checkParame(Car car){ .... } }
- 那么我到底应该要怎么做啊!!!!有方案,就是让CheckCarUtil这个类里面,有100个和Car一模一样的属性,设置完之后,执行一下checkParame不就得了!这样又没有生成car对象,又能确保Car对象创建出来之后,所有参数是合法的!
public class CheckCarUtil{//推演是完全按照未知的情况下进行的,所以这里类的名字可以是任意的名字,不一定非得叫建造者模式的某个固定的名词,如 XXXBuilder!这里之所以这么叫,是为了防止我死记硬背。 private 轮子 a; private 轮胎 b; ...(此处省略98个属性...) ---构造方法省略,是个思考题--- public 轮子 get轮子(){ rehturn 轮子 } ...(此处省略99个属性的get方法).... public void set轮子(轮子 a) { this.a = a; } ...(此处省略99个属性的set方法).... public boolean checkParame(){ .... } public Car buildCar(){ Car car = new Car(); car.something = this.someting; ...... return car; } }
- 这样不就解决了么,但是CheckCarUtil被写成了这个鬼样子,显然功能和名字不太符合了!在我看在,util不应与业务相关的有关。他面向的功能很单一,处处可以用。但是这里明显不符合我对Util这种东西的定义了。
- 改名字! 改为 CarBuilder (看起来有那么点专业范儿了)
- 还有进化的空间,checkParame 这个方法实际上是给内部用就可以了,外部调用的话,又增加了别人的学习成本,一不小心人家就能忘记调用。再改,改为内部调用,对外部屏蔽!
public class CarBuilder{//推演是完全按照未知的情况下进行的,所以这里类的名字可以是任意的名字,不一定非得叫建造者模式的某个固定的名词,如 XXXBuilder!这里之所以这么叫,是为了防止我死记硬背。 private 轮子 a; private 轮胎 b; ...(此处省略98个属性...) ---构造方法省略,是个思考题--- public 轮子 get轮子(){ rehturn 轮子 } ...(此处省略99个属性的get方法).... public void set轮子(轮子 a) { this.a = a; } ...(此处省略99个属性的set方法).... private boolean checkParame(){ 如果没有引擎,或者,外壳,或者轮子{ 返回false } 如果轮子不是4个......{ 返回false } ..省略一坨判断包括但不限于.. 在没有A属性的的情况下,B属性不能被设置!或者在有C属性的情况下,D属性就无效!这种复杂情况的检举 返回 true } public Car buildCar(){ if (checkParame()) { Car car = new Car(); car.a = this.a; ...... return car; } else{ 报错 } } }
- 上面的问题就这么被一步步的优化,代码越来越有建造者模式的样子了。(正式的代码请随便百度一下)但是我目前有一个疑问: CarBuilder 的属性照搬的Car,其实我不介意去生成一个Car对象然后去判断所有属性的。尽管原则上不应该这样搞。之后我又进行了思考。当前我们是些一个Car,但是如果Car是一个非常复杂的对象,并且有很庞大的逻辑呢?这种情况下CarBuilder显然比Car清爽的多了。那么它的存在便会更加具有意义和必要性了!
- 真正的建造者模式不讲解了,代码到现在,已经和建造者模式八九不离十了。
- 至于建造者模式更进一步的变种,也挺好理解的。比如,有时候会把Car拆分生N个模块,按照指定的顺序进行建造,等等。不过我感觉懂得了这个变化过程以及为什么引入新类那么费劲的创建对象,其他的了解基本成浮云了。百度一下就可以解决。