Java中的设计模式之adapter,decorator,facade,strategy,template,iterator
设计模式(一)
很多设计模式,在学习的过程中不好理解,并且许多设计模式在一起讲,难免会有混淆,所以今天来逐个解释一下,这篇的内容包括:adapter,decorator,facade,strategy,template,iterator
1. Adapter(适配器模式)
1.1 目的
适配器模式的目的是:有些接口并不能满足客户的需求,这是需要另一个新的接口将原接口和客户端连接起来,举一个比较形象的例子,就好像你的插座是三项的,但是你的插销是两项的,这时候你需要一个转接器,才能保证你可以正常使用 (当然在现实生活中尽量不要这样使用,安全才是最重要的)
1.2 使用方法
我们一般使用继承或者委托机制,一般是采用委托机制,因为继承过多的话会导致结构过于复杂,下面来看一个例子。
就用插座的例子:现在墙上有一个三项的插座,冰箱是三项插头,但是我的电视机插头是两项的,我的手机插头是USB的,现在需要一个转换器,可以将电视和手机都可以用这个插座。
三项插座接口:
public interface ThreePlug {
public void contact(String type, String equipment);
}
其他插座接口:
public interface OtherPlug {
public void Two(String equipment);
public void USB(String equipment);
}
两项插座:
public class TwoPlug implements OtherPlug{
@Override
public void Two(String equipment) {
System.out.println(equipment + "已接通");
}
@Override
public void USB(String equipment) {
}
}
USB插座:
public class USBPlug implements OtherPlug {
@Override
public void Two(String equipment) {
}
@Override
public void USB(String equipment) {
System.out.println(equipment + "已接通");
}
}
转换器(适配器)
public class Adapter implements ThreePlug {
OtherPlug OP;
public Adapter(String type) {
if(type.toLowerCase().equals("two")) {
OP = new TwoPlug();
}else if(type.toLowerCase().equals("usb")) {
OP = new USBPlug();
}
}
@Override
public void contact(String type, String equipment) {
if(type.toLowerCase().equals("two")) {
OP.Two(equipment);
}else if(type.toLowerCase().equals("usb")) {
OP.USB(equipment);
}
}
}
三项插座的实例类(经过转换器后,可以接三种插头):
public class Plug implements ThreePlug {
@Override
public void contact(String type, String equipment) {
if(type.toLowerCase().equals("three")) {
System.out.println(equipment + "已接通");
}else if(type.toLowerCase().equals("two") || type.toLowerCase().equals("usb")) {
Adapter a = new Adapter(type);
a.contact(type, equipment);
}else {
System.out.println("暂不支持此类型插头");
}
}
}
测试代码:
public class tryness {
public static void main(String[] args) throws Exception {
ThreePlug tp = new Plug();
tp.contact("three", "冰箱");
tp.contact("two", "电视");
tp.contact("usb", "手机");
}
}
运行结果:
冰箱已接通
电视已接通
手机已接通
相信很多人看到这里,像我一样并没有有一个清晰的思路,现在来总体讲述一下:
生活中:
首先我们开始只有一个三项插座(ThreePlug),三个设备,冰箱(三项),电视(两项),手机(USB);
在没有转接口之前,我们只能为冰箱通电,所以我们需要构建一个适配器(Adapter),通过适配器我们可以将两项和USB也插到三项插座上。
程序层面:
我们有一个类ThreePlug,我们通过ThreePlug调用TwoPlug和USBPlug的方法,但是ThreePlug的方法只适用于type为“three”的情况,很明显TwoPlug和USBPlug的方法public void Two(String equipment) 和 public void USB(String equipment) 是与 public void contact(String type, String equipment) 不兼容的,所以需要Adapter来解决这个问题
Adapter(关键):首先是ThreePlug 的一个实例类,然后必须要通过委托机制来调用TwoPlug 和 USBPlug 中的方法,所以Adapter中有一个OtherPlug OP ,用此来委托。在Adapter中的public void contact(String type, String equipment) 是最主要的部分,“形式化”就靠这个了,先判定类型,如果是two或者USB,那么通过委托个对应的OP 来完成。
对客户端只体现Plug这个类的使用方式,它是ThreePlug的一个实例类,其中的调用关系的结构客户端不需要知道,客户端只给出插头类型和设备名称,这也就是适配器模式的体现。
1.3 适配器模式的优缺点
优点:
算是对代码的一个复用,使用起来更加灵活
缺点:
过多的使用适配器会让结构十分混乱,不易于对代码整体结构有一个清晰的理解
总结:
适配器模式是在迫不得已的时候才使用的,不可以在构建初期就为了使用适配器而故意复杂化整体结构
2. Decorator(装饰者模式)
2.1 目的
通常为了给一个类增加一些功能,我们通常会建立一个子类(继承机制),在子类中新添加一些方法,但是这样可能会导致子类因为扩展的功能过多,而导致的膨胀,而装饰器模式恰好解决了这个问题,而且还可以动态的增加或取消新的功能
2.2 使用方法
举一个例子:我们为animal类建了一个run的方法,但是现在我们想对run外加一点修饰,比如快慢,这时候就需要用到装饰者模式了
Animal接口:
public interface Animal {
public void run();
}
Dog类:
public class Dog implements Animal {
@Override
public void run() {
System.out.println("Dog is running");
}
}
Cat类:
public class Cat implements Animal {
@Override
public void run() {
System.out.println("Cat is running");
}
}
Decorator类:
public class Decorator implements Animal {
protected Animal a;
public Decorator(Animal animal) {
this.a = animal;
}
@Override
public void run() {
a.run();
}
}
FastAnimal类:
public class FastAnimal extends Decorator {
public FastAnimal(Animal animal) {
super(animal);
}
@Override
public void run() {
a.run();
fast();
}
private void fast() {
System.out.println("And it is running fast");
}
}
测试代码:
public class tryness {
public static void main(String[] args) {
Animal dog1 = new Dog();
dog1.run();
System.out.println();
Decorator dog = new FastAnimal(new Dog());
dog.run();
System.out.println();
Decorator cat = new FastAnimal(new Cat());
cat.run();
}
}
运行结果:
Dog is running
Dog is running
And it is running fast
Cat is running
And it is running fast
我们原来建Animal类时只是为了表示run这一行为,但是现在想要对run有一个描述,而又不想继承,所以就建立一个装饰者Decorator,对于run() 为了提高复用度,还是用之前Animal中的方法,所以涉及到委托,那么Decorator中就需要有一个Animal的变量,而且要考虑到装饰者的子类也可调用到这个Animal,所以应该是protected的变量,变量通过构建器赋值。然后就是我们要新加的修饰了,比如我们要加入跑得快这一属性,那么就建立一个Decorator的子类FastAnimal,然后在这个类中加入新的方法fast() 然后重写run() 方法,在run方法里利用其父类Decorator中的Animal变量调用原来的run方法,再在其后面调用新加入的fast方法。
如果还想有其他的修饰,可以继续建Decorator的子类,然后加入新的方法即可,如果有与fast同时应该加入的修饰,比如“肌肉发达”,那么只需要在FastAnimal中再加入这个新的方法即可,所以装饰者模式对于后续的修饰是很方便的。
2.3 装饰者模式的优缺点
优点:
装饰器模式可以动态扩展一个实现类的功能
缺点:
多层装饰容易使整体结构过于复杂
3. Facade(外观模式)
3.1 目的
为了客户端使用更加简洁,在客户端和复杂系统之间加一层,这一层会让复杂的系统变得易于使用。
3.2 使用方法
比如:午餐有:汉堡,面条等,但是这些餐厅不在一起,而且我对路线也不熟悉,那么如果有一个代购店,我进入这个店里,告诉店员我想要的午餐,他就会帮我去买回来,这样对于我来说就会方便很多
Restaurant接口:
public interface Restaurant {
public void buy();
}
KFC类:
public class KFC implements Restaurant {
@Override
public void buy() {
System.out.println("Hamburger");
}
}
MrLee类:
public class MrLee implements Restaurant {
@Override
public void buy() {
System.out.println("Noodles");
}
}
Facade类:
public class Facade {
private Restaurant kfc;
private Restaurant mrlee;
public Facade() {
kfc = new KFC();
mrlee = new MrLee();
}
public void buyHamburger() {
kfc.buy();
}
public void buyNoodles() {
mrlee.buy();
}
}
测试代码:
public class tryness {
public static void main(String[] args) {
Facade f = new Facade();
f.buyHamburger();
f.buyNoodles();
}
}
运行结果:
Hamburger
Noodles
Restaurant就是所有餐厅,KFC 和 MrLee分别是卖汉堡和面条的餐厅,Facade 类相当于代购店,只需要在Facade中通过简单的方法调用就可以帮我得到想要的结果,这就是外观模式,还是比较好理解的。
3.3 外观模式的优缺点
优点:
提高了调用的便捷性,不需要考虑深层结构,底层方法不暴露给客户端,更具安全性
缺点:
如果想要修改很麻烦,既要改底层又要改Facade,比如KFC店倒闭了,那么既要把KFC类删除掉,又要把Facade中的KFC变量和buyHamburger进行相应的修改
4. Strategy(策略模式)
4.1 目的
设计一系列的算法,并且统一封装,可以根据需要选择合适的算法来执行
4.2 使用方法
就用布尔运算来举例,有“与”和“或”两种,类似这种属于统一代数系统中的运算,将他们封装起来,统一调用会更加方便,这时候就需要用到策略模式
Strategy接口:
public interface Strategy {
public boolean booleanCompute(boolean a, boolean b);
}
Or类:
public class Or implements Strategy {
@Override
public boolean booleanCompute(boolean a, boolean b) {
return a | b;
}
}
And类:
public class And implements Strategy {
@Override
public boolean booleanCompute(boolean a, boolean b) {
return a & b;
}
}
Context类:
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public boolean compute(boolean a, boolean b) {
return strategy.booleanCompute(a, b);
}
}
测试代码:
public class tryness {
public static void main(String[] args) {
Context c1 = new Context(new Or()); //选择"或"策略
boolean b1 = c1.compute(true, false);
System.out.println(b1);
Context c2 = new Context(new And()); //选择"与"策略
boolean b2 = c2.compute(true, true);
System.out.println(b2);
}
}
运行结果:
true
true
首先对于布尔运算有一个统一的接口Strategy,然后 Or 和 And 类就是两种算法,需要有一个 Context 类来这些算法封装起来,改变策略的方式就是构建Context类时传入的Strategy
4.3 策略模式的优缺点
优点:
算法可以根据需要自由切换,具有良好的扩展性
缺点:
所有策略类都需要暴露,否则无法使用 Context 选择策略
5. Template(模板模式)
5.1 目的
为了避免通用的方法在每个类中都写一遍,将共同的部分抽象出来写成一个类
5.2 使用方法
Game类:
public abstract class Game {
public abstract void init();
public abstract void start();
public abstract void end();
public void play() {
init();
start();
//可以加其他共性操作
end();
}
}
goClass类:
public class goClass extends Game {
@Override
public void init() {
System.out.println("围棋棋盘已经初始化");
}
@Override
public void start() {
System.out.println("围棋已经开始");
}
@Override
public void end() {
System.out.println("围棋已经结束");
}
}
chessClass类:
public class chessClass extends Game {
@Override
public void init() {
System.out.println("国际象棋棋盘已经初始化");
}
@Override
public void start() {
System.out.println("国际象棋已经开始");
}
@Override
public void end() {
System.out.println("国际象棋已经结束");
}
}
测试代码:
public class tryness {
public static void main(String[] args) {
Game g1 = new goClass();
g1.play();
System.out.println("\n");
Game g2 = new chessClass();
g2.play();
}
}
运行结果:
围棋棋盘已经初始化
围棋已经开始
围棋已经结束
国际象棋棋盘已经初始化
国际象棋已经开始
国际象棋已经结束
Game 是一个抽象类,用来实现共享方法,比如围棋和国际象棋的过程都是由初始化、开始、结束三个步骤构成,只是这三个步骤不一样,所以将三个步骤先不实现,把共性的步骤执行顺序,即 play() 实现了,再分别建立 goClass 和 chessClass 类,继承了 Game 类,并且实现自己的特殊的 init()、 start()、 end() 方法,这就是模板模式
5.3 模板模式的优缺点
优点:
封装了共性部分,然后扩展特有的部分,提高了代码复用度,公共部分便于维护
缺点:
每个不同的实现都要有一个子类,整体结构比较复杂
6. Iterator(迭代器模式)
6.1 目的
为了以自己想要的方式遍历整个集合
6.2 使用方法
用一个按照字符串长度从大到小的顺序遍历字符串数组的例子来说明:
StringLength类:
private String[] str;
public StringLength(String[] str) {
this.str = str;
}
public Iterator<String> getIterator() {
return new LengthIterator();
}
private class LengthIterator implements Iterator<String> {
int index = 0;
@Override
public boolean hasNext() {
if(index < str.length) {
return true;
}else {
return false;
}
}
@Override
public String next() {
for(int i = index; i < str.length; i ++) {
if(str[i].length() >= str[index].length()) {
String temp = str[i];
str[i] = str[index];
str[index] = temp;
}
}
index ++;
return str[index - 1];
}
}
}
测试代码:
public class tryness {
public static void main(String[] args) {
StringLength sl = new StringLength(new String[] {"Alice", "Bob", "Jack"});
Iterator<String> it = sl.getIterator();
for(; it.hasNext();) {
System.out.println(it.next());
}
}
}
运行结果:
Alice
Jack
Bob
对于StringLength类,我们要构建一个相应的迭代器,在类里面实现一个Iterator的实例类 LengthIterator 首先要重写 hasNext() 和 next() 方法,对于hasNext就是检验当前的index小于数组长度即可,对于next,需要按照我们想要的顺序来选取下一个元素,就是每次选出剩余元素中最长的那个,算法很简单不做过多解释。然后在 StringLength 中的 getIterator() 方法中直接返回刚才构建好的 LengthIterator 即可,这样迭代器模式就完成了。
6.3 迭代器模式的优缺点
优点:
可以根据我们自己的需要来遍历一个容器
缺点:
迭代器在一定程度上增加了系统的复杂性
此篇文章只是我个人理解,如果有错误欢迎指出,感谢阅读!