W
wind_jackyer
Unregistered / Unconfirmed
GUEST, unregistred user!
用Adapter模式取代部分实现的接口
你的类实现一个接口,但是只为接口中的一部分方法提供代码。
将实现的方法移交给该接口的适配器,并让一个工厂方法可以访问该适配器。
动机
具体类中的空方法总是让我很烦心。我经常看见一些空方法,因为具体类经常需要
通过实现某个接口来满足某种要求,但又不是真的需要实现接口中所有的方法。不需要的
方法得到了声明,但是保留为空:添加它们仅仅是为了满足编译器的规则。这可能不会让
你烦心,但的确让我很烦心。我发现,类的接口(即public方法集合)中如果出现空方法
,不但会使类的接口变得臃肿,还会对类的行为做出错误的宣传(我是一个类,我可以做
X()、Y()和Z()——但我其实只提供X()的代码),而且强迫我做一些不想做的工
作(比如声明一些空方法)。
Adapter模式提供了一种重构这种代码的好方法。Adapter类将给接口中所有的方
法一个空的实现,而你则可以从Adapter类派生出需要的子类,同时只需要提供需要的代
码。在Java中,我甚至不需要正式声明一个Adapter子类:我只需要在Adapter类中创建一
个匿名子类,然后直接将它的引用提供给Factory Method就行了。
通信
重复
简化
不管是有人忘了删掉空方法还是因为接口强迫你保留空方法,类中的空方法都不会有太多
的通信。最好是只在真正实现的时候才进行通信,而Adapter可以帮助你实现这一点。
如果你有不止一个类部分实现了同一个接口,那么你可能拥有相当多的空方法了。只要
让这些类都与一个处理空方法声明的Adapter类协同工作,你就可以去掉这些重复。
提供少量代码总比提供大量代码要容易。这个重构方法给你一种方法以减少类中声明的
方法数量。另外,当你用这种方法来适配多个接口时,它可以提供一条非常好的途径,让
你可以实现每个接口中的一部分方法。
技巧
1. 如果还没有要使用的接口(我把它叫做A)的适配器,就用“适配接口”的方法
来创建一个。然后创建一个Factory Method模式,让它返回你的适配器的一个实例(我把
它叫做AdapterInstance)。
2. 把你的类中那些完全因为实现A接口才存在的空方法删掉。
3. 把你实现了的那些由A指定的方法转移到AdapterInstance中。
4. 删除你的类中“implements A”的声明。
5. 把AdapterInstance提供给需要的客户。
示例
我将用具体的代码来说明上面的步骤。在这里我们有一个名叫CardComponent的类
,它派生自JDK中的Component类,并实现JDK中的MouseMotionListener接口。但是,它只
实现MouseMotionListener接口的两个方法中的一个。让我们看看Adapter模式怎样改善这
段代码。
第一步,为我们的AdapterInstance创建一个Factory Method。如果我们还没有
AdapterInstance,我们就需要用“适配接口”的重构方法来创建一个。在这里,JDK已
经为MouseMotionListener接口提供了一个适配器,名叫MouseMotionAdapter。所以我们
在CardComponet类中创建下列新方法,其中使用了Java便利的内部类能力:
private MouseMotionAdapter createMouseMotionAdapter() {
return new MouseMotionAdapter() {
};
}
接着,我们删掉CardComponent中声明的空方法,因为它在MouseMotionListener
已经被实现了。在这里,MouseMotionListener实现了mouseDragged方法,但没有实现
mouseMoved方法。
public void mouseMoved(MouseEvent e) {}
现在我们已经将mouseDragged方法从CardComponent方法中移到了
MouseMotionAdapter的实例中。
private MouseMotionAdapter createMouseMotionAdapter() {
return new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
e.consume();
dragPos.x = e.getX();
dragPos.y = e.getY();
setLocation(getLocation().x+e.getX()-currPos.x,
getLocation().y+e.getY()-currPos.y);
repaint();
}
};
}
现在我们可以从CardComponent中删掉MouseMotionListener的实现了。
public class CardComponent extends Container implements MouseMotionListener {
最后,我们必须将新的适配器实例提供给需要的客户。在这里,我们必须观察构
造子,它的代码是这样:
public CardComponent() {
//……
addMouseMotionListener(this);
}
需要将它改为调用新的私有工厂方法:
public CardComponent() {
//…
addMouseMotionListener(createMouseMotionAdapter());
}
现在我们进行测试。很不幸,因为这段代码与鼠标相关,我无法进行自动化单元测
试。因此我进行了一些简单的手工测试,并确认:我们的重构很成功。
附录:源代码
l 重构前:
public class CardComponent extends Container implements MouseMotionListener ...
public CardComponent(Card card,Explanations explanations) {
//...
addMouseMotionListener(this);
}
public void mouseDragged(MouseEvent e) {
e.consume();
dragPos.x = e.getX();
dragPos.y = e.getY();
setLocation(getLocation().x+e.getX()-currPos.x, getLocation().y+e.getY()-currPos.y);
repaint();
}
public void mouseMoved(MouseEvent e) {
}
l 重构后:
public class CardComponent extends Container ...
public CardComponent(Card card,Explanations explanations) {
//...
addMouseMotionListener(createMouseMotionAdapter());
}
private MouseMotionAdapter createMouseMotionAdapter() {
return new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
e.consume();
dragPos.x = e.getX();
dragPos.y = e.getY();
setLocation(getLocation().x+e.getX()-currPos.x, getLocation().y+e.getY()-currPos.y);
repaint();
}
};
}
你的类实现一个接口,但是只为接口中的一部分方法提供代码。
将实现的方法移交给该接口的适配器,并让一个工厂方法可以访问该适配器。
动机
具体类中的空方法总是让我很烦心。我经常看见一些空方法,因为具体类经常需要
通过实现某个接口来满足某种要求,但又不是真的需要实现接口中所有的方法。不需要的
方法得到了声明,但是保留为空:添加它们仅仅是为了满足编译器的规则。这可能不会让
你烦心,但的确让我很烦心。我发现,类的接口(即public方法集合)中如果出现空方法
,不但会使类的接口变得臃肿,还会对类的行为做出错误的宣传(我是一个类,我可以做
X()、Y()和Z()——但我其实只提供X()的代码),而且强迫我做一些不想做的工
作(比如声明一些空方法)。
Adapter模式提供了一种重构这种代码的好方法。Adapter类将给接口中所有的方
法一个空的实现,而你则可以从Adapter类派生出需要的子类,同时只需要提供需要的代
码。在Java中,我甚至不需要正式声明一个Adapter子类:我只需要在Adapter类中创建一
个匿名子类,然后直接将它的引用提供给Factory Method就行了。
通信
重复
简化
不管是有人忘了删掉空方法还是因为接口强迫你保留空方法,类中的空方法都不会有太多
的通信。最好是只在真正实现的时候才进行通信,而Adapter可以帮助你实现这一点。
如果你有不止一个类部分实现了同一个接口,那么你可能拥有相当多的空方法了。只要
让这些类都与一个处理空方法声明的Adapter类协同工作,你就可以去掉这些重复。
提供少量代码总比提供大量代码要容易。这个重构方法给你一种方法以减少类中声明的
方法数量。另外,当你用这种方法来适配多个接口时,它可以提供一条非常好的途径,让
你可以实现每个接口中的一部分方法。
技巧
1. 如果还没有要使用的接口(我把它叫做A)的适配器,就用“适配接口”的方法
来创建一个。然后创建一个Factory Method模式,让它返回你的适配器的一个实例(我把
它叫做AdapterInstance)。
2. 把你的类中那些完全因为实现A接口才存在的空方法删掉。
3. 把你实现了的那些由A指定的方法转移到AdapterInstance中。
4. 删除你的类中“implements A”的声明。
5. 把AdapterInstance提供给需要的客户。
示例
我将用具体的代码来说明上面的步骤。在这里我们有一个名叫CardComponent的类
,它派生自JDK中的Component类,并实现JDK中的MouseMotionListener接口。但是,它只
实现MouseMotionListener接口的两个方法中的一个。让我们看看Adapter模式怎样改善这
段代码。
第一步,为我们的AdapterInstance创建一个Factory Method。如果我们还没有
AdapterInstance,我们就需要用“适配接口”的重构方法来创建一个。在这里,JDK已
经为MouseMotionListener接口提供了一个适配器,名叫MouseMotionAdapter。所以我们
在CardComponet类中创建下列新方法,其中使用了Java便利的内部类能力:
private MouseMotionAdapter createMouseMotionAdapter() {
return new MouseMotionAdapter() {
};
}
接着,我们删掉CardComponent中声明的空方法,因为它在MouseMotionListener
已经被实现了。在这里,MouseMotionListener实现了mouseDragged方法,但没有实现
mouseMoved方法。
public void mouseMoved(MouseEvent e) {}
现在我们已经将mouseDragged方法从CardComponent方法中移到了
MouseMotionAdapter的实例中。
private MouseMotionAdapter createMouseMotionAdapter() {
return new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
e.consume();
dragPos.x = e.getX();
dragPos.y = e.getY();
setLocation(getLocation().x+e.getX()-currPos.x,
getLocation().y+e.getY()-currPos.y);
repaint();
}
};
}
现在我们可以从CardComponent中删掉MouseMotionListener的实现了。
public class CardComponent extends Container implements MouseMotionListener {
最后,我们必须将新的适配器实例提供给需要的客户。在这里,我们必须观察构
造子,它的代码是这样:
public CardComponent() {
//……
addMouseMotionListener(this);
}
需要将它改为调用新的私有工厂方法:
public CardComponent() {
//…
addMouseMotionListener(createMouseMotionAdapter());
}
现在我们进行测试。很不幸,因为这段代码与鼠标相关,我无法进行自动化单元测
试。因此我进行了一些简单的手工测试,并确认:我们的重构很成功。
附录:源代码
l 重构前:
public class CardComponent extends Container implements MouseMotionListener ...
public CardComponent(Card card,Explanations explanations) {
//...
addMouseMotionListener(this);
}
public void mouseDragged(MouseEvent e) {
e.consume();
dragPos.x = e.getX();
dragPos.y = e.getY();
setLocation(getLocation().x+e.getX()-currPos.x, getLocation().y+e.getY()-currPos.y);
repaint();
}
public void mouseMoved(MouseEvent e) {
}
l 重构后:
public class CardComponent extends Container ...
public CardComponent(Card card,Explanations explanations) {
//...
addMouseMotionListener(createMouseMotionAdapter());
}
private MouseMotionAdapter createMouseMotionAdapter() {
return new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
e.consume();
dragPos.x = e.getX();
dragPos.y = e.getY();
setLocation(getLocation().x+e.getX()-currPos.x, getLocation().y+e.getY()-currPos.y);
repaint();
}
};
}