前言
在软件设计的时候,我们要充分的复用我们的代码,这样可以给我们节省很多时间,可以少打很多重复的代码。下面就是节省6种设计模式,这6种设计模式主要基于继承和委派两种机制,给出的高效复用的思想。
适配器模式(Adapter)
有的时候,我们在以前就实现某些功能或写过某些类,或某些类的某些方法,这些方法的功能我想复用,但是由于不同的项目场景不同,方法对应的参数不同了,怎么办呢?如果这些参数是可以转化成以前的参数类型,那还可以接着复用,这就是适配器模式。
举一个例子,比如我的有一个U盘,是正常的USB接口,但我想在手机上也可以用这个U盘,但是接口不对,我加上一个USB转type-c的转换器,就可以用了。适配器模式也是这么个意思,我实现转换器的功能,这里面的核心实现就是委派。具体代码例子如下:
client中display方法想调用上面的LegacyRectangle中的display方法,但是明显参数不对,我没有w和h,但是w和h是可以算出来。
于是我们就写了个转换器,把四个参数转换成6个参数,当然可能有人会觉得这样有点蠢啊,明明可以直接减为什么还要单独写个ADT来转换,其实只是因为这个例子比较简单,如果比较难的,比如x1,x2,y1,y2要进行一系列的转换才能得到w和h呢,那适配器就很有必要了,且使用这种方式可以简化客户端的输入要求,相当于更弱的前置条件,客户端用起来更轻松,不需要它自己转换。还有就是隐藏了具体的实现体,实现体怎么实现的,有几个参数我们都隐藏了。
Décorator装饰器模式
装饰器模式很大的特点就是套娃,层层迭代,它是严格满足LSP原则,结合委派的思想,达到了不同维度上的方法个性化的组合。这里我们以路边摊小吃来举例子。
选定义一个接口Snack,里面有两个方法(相当于两种小吃),一个是烤冷面,一个是煎饼
public interface Snack {
//烤冷面
public String KLM();
//煎饼
public String JB();
}
然后编写一个实现类,最基础的套餐
继承了Snack接口,比如烤冷面的基础套餐是加一个鸡蛋,煎饼的也是加一个鸡蛋
这样肯定有人是不够吃的(比如我hhhhh),我想再加点额外的套餐,而且我希望可以任意组合,那好办,使用装饰器模式。
先创建一个装饰器类
public class SnackDecorator implements Snack{
protected Snack snack;
//关键就在构造函数这里,我们通过建立委派关系,相当于同类的委派
public SnackDecorator(Snack snack) {
this.snack = snack;
}
@Override
public String KLM() {
return snack.KLM();
}
@Override
public String JB() {
return snack.JB();
}
}
比如我想要加火腿肠的,那就创建一个类Snack1(唉,复制代码块老是会导致代码块卡bug,就上截图算了)
觉得火腿肠不够,我还想要别的,再写一个类Snack2
好了,我们看看客户端怎么点菜
我们看看运行结果
可以看到,其实已经达到了我们想要的组合效果。
总结一下:装饰器模式的主要思想就是委派,我们对同类的委派,再结合继承关系,这样我们就可以重写方法,委派关系确保了对不同子类的个性的自由组合,而继承关系则是要满足LSP原则,我们只能对共同的方法进行个性化扩展,但并不意味着不能添加新的方法,而是如果添加个性化的方法那么根据LSP原则就无法将个性化的方法装饰到其他的子类上面,所以在实现装饰器模式的时候需要严格满足LSP原则,当然也可以直接创建子类而不是接口,其中的扩展延伸有很多种形式,不一定要按固定模板来套,而是将其思想理解,并运用到其他地方
Facade 外观模式
外观模式主要是为客户端设计的,外观模式的意思就是在具体实现类和客户端调用之间加上一个中间站,这个中间站的作用就是为了简化客户端的调用操作,比如还是以我上面那个小吃的例子,我已经写好了客户端了,但是客户端还得自己创建,自己选择组合,太麻烦了,我可以直接给组合的结果给客户端。
比如下面这种样子,我把创建组合的过程都放到Façade里面去实现了,然后客户端这边就只用调用Façade中的方法就行了,等于是内部的实现客户端什么都不知道,它的操作就简单了很多。所以facade模式还有一个好处就是它隐藏了我们的具体实现类,既简化了操作,又隐藏了内部实现,安全便捷,用途还是很广泛的。
Strategy 策略模式
策略模式也很好理解,简单来讲就是如果同样的功能有不同的实现类,还是上面的那个例子,比如我想要先鸡柳再加火腿肠,另一个同学想先加火腿肠再加鸡柳,套餐是一样的,只不过做的顺序不一样了而已。策略模式就是给出选择给用户,但最后达到的最终目的是一样的。
具体的实现也很简单,就是先创建一个接口,策略接口,然后有几种策略就写几种实现类,客户端通过传入实现类达到目的。还是这个小吃例子,我想吃加了鸡蛋鸡柳火腿肠的豪华版烤冷面
先创建策略接口
创建两个子类,有不同的实现
然后在façade里添加一个套餐(方法)
在客户端里调用,我们传具体的实现类给Snack4方法,相当于我规定好了加材料次序。
打印输出结果,都是烤冷面,也都加了同样多的东西,但是加的顺序不一样了,这就是我们自己选择的策略。
总结一下:策略模式其实就是给出不同的策略给客户端选择,这样使得我们代码实现有多样性,不是单一的实现,在更复杂的应用场景中,策略模式用途更广发,我这里都是比较简单的例子,只能大概体现这种设计思想。
Template 模板模式
模板模式主要用在白盒框架中,它的主要思想就是将共性的步骤在抽象类中实现,将差异化的步骤在各个子类中实现,简单来说就是提取出共性的操作放在父类里面,具体的个性化操作放在子类里面实现。
比如下面这个UML图,step1和step2还有step3,他们这三个方法的顺序是固定的,这就是共性,所以抽象出来,把调用顺序放在templateMethod中,然后这三个方法的具体实现再两个实现类A和B中具体实现。
Iterator 迭代器模式
大家看这个是不是觉得特别熟悉,就是集合类中都有的迭代器Iterator,它的功能就是遍历,集合类中都实现了迭代器的功能,所以可以通过Iterator在对集合中的元素进行遍历。其实我们可以在任何一个类中都加上Iterator功能,方法也很简单,对需要遍历功能的类中让其继承Iterable接口,然后重写iterator方法,最后再创建一个实现类实现Iterator方法。
然后实现接口中的方法就行,实现后Facade这个类就拥有了对Snack对象的迭代遍历功能。
总结
上述的六种设计模式本质上基本实现都是继承或者委派,通过不同形式的继承和委派组合使用,诞生出不同的设计模式和设计思想,我们也可以在平时代码中多进行尝试,看看能不能尽可能的提高的代码的复用。我觉得真正的复用其实并不是拘泥于这六种设计模式,他们相互之间也可以互相组合,达到一加一大于二的效果,在lab3中其实也可以感受出来,我并没有具体的严格的使用某种模式,比如我想用装饰器,但是我发现航班高铁课程之间虽然有共同的地方,但差异也很明显,所以就没用装饰器,因为装饰器严格遵循LSP原则,且就算是共有的方法里面的具体实现也不一样,还是一样得写很多代码,那不如直接把各个模块功能都抽象出来形成一个个类,然后委派出去。在可复用性编程的路上我们还有很多的地方需要是思考实践,而我上述写的都是我自己的理解,可能有理解不到位的地方,请多多指教。