Adapter模式取代部分实现的接口

Adapt Interface

Joshua Kerievsky               透明

 

你的类实现一个接口,但是只为接口中的一部分方法提供代码。

将实现的方法移交给该接口的适配器,并让一个工厂方法可以访问该适配器。

 

关键字

      Adapter,设计模式,重构,Java

 

动机

       具体类中的空方法总是让我很烦心。我经常看见一些空方法,因为具体类经常需要通过实现某个接口来满足某种要求,但又不是真的需要实现接口中所有的方法。不需要的方法得到了声明,但是保留为空:添加它们仅仅是为了满足编译器的规则。这可能不会让你烦心,但的确让我很烦心。我发现,类的接口(即 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();

}

};

}