之前讲的东西都偏理论,今天我们来开发一个需求。
需求很简单,提供一个服务,给手机充电。我们尽量简化这个需求。如果手机直接带有充电功能的话,那我们要做的事儿就简单了,拿到手机对象,直接执行充电方法就行了。然而手机自身并不能连接电源(不考虑无线充电技术),参考现实生活,我们需要充电器来充电。手机类型有苹果和华为。手机充电器对应的是苹果手机充电器和华为手机充电器
我们先看一个比较差的实现:
1、定义手机接口:
![](https://img.haomeiwen.com/i22466533/2b417c38d7dabe61.png)
这个没什么好说的,毕竟有两种类型的手机,需要多态。
2、然后两个手机类实现接口:
![](https://img.haomeiwen.com/i22466533/8b7a1f6183f89865.png)
![](https://img.haomeiwen.com/i22466533/682de5024065b5f8.png)
3、充电器接口:
![](https://img.haomeiwen.com/i22466533/1d0ce92ead1dd222.png)
4、具体两个充电器类:
![](https://img.haomeiwen.com/i22466533/56bf72f0ff099a29.png)
![](https://img.haomeiwen.com/i22466533/79a63c996d3fc68a.png)
主方法及调用:
![](https://img.haomeiwen.com/i22466533/a92d25a6d951a637.png)
忽略报错,因为本地没有这么去实现,所以就写点代码表示一下。
我们来分析一下这么实现的缺点,最主要有两个不可接受的点:
1、用if/else判断手机类型,当增加手机类型时,必须要修改主方法来增加逻辑判断。
2、在逻辑分支里有太多的重复代码,而且充电的步骤都是固定的,如果缺少或者打乱就会出错。
我们来优化一下:
1、手机的接口和实现不变
2、定义充电器抽象类:
![](https://img.haomeiwen.com/i22466533/32d1aeac8cc71b53.png)
3、两个充电器的实现类:
![](https://img.haomeiwen.com/i22466533/f29533d7877f88a6.png)
![](https://img.haomeiwen.com/i22466533/b157afd48327efb7.png)
4、主方法
![](https://img.haomeiwen.com/i22466533/a3c928d13a30b584.png)
5、加载类的工具类:
![](https://img.haomeiwen.com/i22466533/d43070423c623e33.png)
![](https://img.haomeiwen.com/i22466533/b968fad972d7289c.png)
我们来解释一下为什么这么优化。还是那两个点:
1、如果增加小米手机和充电器,只需要增加两个类,小米手机实现类和小米充电器实现类。不需要改代码,只需要增加两个类。扩展开放、修改关闭,符合开闭原则。
2、充电方法都是调用父类的方法,而且不允许重写。这样调用时不用重复代码,而且步骤和顺序都得到了保证。
那怎么做到的呢?
1、充电器增加判断支持什么手机的功能,需要持有手机对象,通过参数传递即可。
2、充电器的抽象类的具体方法都有子类实现,但主充电方法是父类定义步骤和顺序且不允许重写。
3、获取了所有充电器实例,循环判断哪个支持,支持的执行充电,并以参数的形式持有手机对象,在方法内部执行接受电量的方法。
其中,获取所有充电器实例的行为,在spring框架下更容易实现,本例是通过扫包查找的方式,如果是Spring下,可以直接使用ApplicationContext来从容器内直接获取,省去了自己加载和实例化的步骤。
我并不喜欢去强行的套用模式,而是希望在解决具体问题的时候来借鉴模式的好的思路。比如说模板模式,抽象类内的Charge方法使用了模板模式,它的必要性也显而易见。
所以当遇见业务需要固定的步骤实现,只不过具体每个步骤实现不同的场景时,可以考虑使用模板模式。
第二个是适配器模式,场景是,手机需要充电功能,但是它没有连接电源的功能,又不能在手机上加一插头(对应到代码里就是,修改Phone接口,增加连接电源的功能,这显然不好)。
需要一个新的功能、又不能改变原来类的行为,那只能创造一个新的类,来连接原有类和新功能。具体的做法是持有原来类的引用,实现新功能的接口。即持有具体的手机对象,实现连接电源的接口。充电器类应运而生。
当然,如果说做框架开发,场景不会这么简单,不过我们的手段就那么多,反射、多态、重载、持有引用、继承、实现接口、注解、动态代理等等。灵活的设计,并不是非要套用某个模式,而是排列组合这些手段、当设计不符合预期时,从设计模式中找相近的场景,看是否可以找到可借鉴的排列组合。
网友评论