抽象类WindowAdapter是变压器模式的一个例子
抽象类WindowAdapter是为接受视窗的事件而准备的。此抽象类内所有的方法都是空的。 使用此类可以很方便地创立listener对象。置换(Override)你所感兴趣的那个事件所对应的方法。 如果你不使用此抽象类,那么你必然规律要实现WindowsListener接口,而那样你就不得不实现所有接口中的方法, 即便是你不需要的事件所对应的方法,你也要给出一个空的方法,而这显然不方便。
显然,抽象类WindowAdapter的目标接口可以选得与源接口一样,而不影响效果。 这就解释了为什么目标接口不出现在WindowAdapter类图(见下面)里。
图3. 本例子SwingUI类与WindowAdapter实例变压器模式的类图定义
SwingUI类的代码如下。
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
class SwingUI extends JFrame implements ActionListener
{
JLabel text, clicked;
JButton button, clickButton;
JPanel panel;
private boolean m_clickMeMode = true;
Public SwingUI()
{
text = new JLabel("我很高兴!");
button = new JButton("理我");
button.addActionListener(this);
panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.setBackground(Color.white);
getContentPane().add(panel);
panel.add(BorderLayout.CENTER, text);
panel.add(BorderLayout.SOUTH, button);
}
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
if (m_clickMeMode)
{
text.setText("我很烦!");
button.setText("别理我");
m_clickMeMode = false;
}
else
{
text.setText("我很高兴!");
button.setText("理我");
m_clickMeMode = true;
}
}
public static void main(String[] args)
{
SwingUI frame = new SwingUI();
frame.setTitle("我");
WindowListener listener = new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
};
frame.addWindowListener(listener);
frame.pack();
frame.setVisible(true);
}
}
代码清单7. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
显然,由于无名内部类是继承自WindowAdapter抽象类,因此只需置换(override)掉我们需要的方法, 即windowClosing()而不必操心WindowListener的其它方法。
本例子在运行时的样子:
图4. SwingUI类在运行时的样子。单击命令键“理我”就变成下图的样子。
图5. 再单击命令键“别理我”就会回到前图的样子
利用变压器模式指方为圆
中国古代有赵高指鹿为马的故事。鹿与马有很多相似之处,没见过的人本就分辨不清,指一指可能没什么大不了的。 指方为圆是否太过?非也。本例就是要指方为圆,需要的只是变压器模式这个魔术手指(Magic Finger)。
变压器模式在本例子的类图如下。
图6. 指方为圆的变压器模式类图
package com.javapatterns.adapter.cube2ball;
public class Cube
{
public Cube(double width)
{
this.width = width;
}
public double calculateVolume()
{
return width * width * width;
}
public double calculateFaceArea()
{
return width * width;
}
public double getWidth()
{
return this.width;
}
public void setWidth(double width)
{
this.width = width;
}
private double width;
}
代码清单8. Cube类的源代码。。
package com.javapatterns.adapter.cube2ball;
public interface BallIF
{
double calculateArea();
double calculateVolume();
double getRadius();
void setRadius(double radius);
}
代码清单9. BallIF接口的源代码。
package com.javapatterns.adapter.cube2ball;
public class MagicFinger implements BallIF
{
public MagicFinger(Cube adaptee)
{
super();
this.adaptee = adaptee;
radius = adaptee.getWidth();
}
public double calculateArea()
{
return PI * 4.0D * ( radius * radius );
}
public double calculateVolume()
{
return PI * 4.0D/3.0D * ( radius * radius * radius );
}
public double getRadius()
{
return radius;
}
public void setRadius(double radius)
{
this.radius = radius;
}
private double radius = 0;
private static final double PI = 3.14D;
private Cube adaptee;
}
代码清单10. MagicFinger类的源代码。
如果读者还记得中学的数学的话,应该可以看出,我们的指方为圆系统其实还是有道理的。它接受一个正方体, 返还此正方体的内切球,也就是能放进此正方体的最大的球。
显然,本例子里,我们使用的是实例形式的变压器模式。这样做的好处是,如果一旦我们决定不仅要支持正方体, 而且要支持四面体等多面体,我们可以使用同一个MagicFinger类,而不必针对每一个多面体都建立一个MagicFinger类。 这样也比较符合“魔术手指”这个名字。
关于模式实现的讨论
本模式在实现时有以下这些值得注意的地方:
第一、目标接口可以省略。此时,目标接口和源接口实际上是相同的。 由于源是一个接口,而变压器类是一个类(或抽象类),因此这种做法看似平庸而并不平庸, 它可以使客户端不必实现不需要的方法。这一点已经在WindowAdapter的例子里做了详尽的分析。
第二、变压器类可以是抽象类。这已经在WindowAdapter的例子里看到了。实际上,WindowAdapter的例子过于简单。 实际的情形里,你可能想给出一些实方法。
第三、带参数的变压器模式。使用这种办法,变压器类就不必,有时可能不能是源类的子类。 变压器类根据参数返还一个合适的实例给客户端。
问答题
第1题、请做一个小猫(kittie)的实类,并实现miao(),catchRat(),run(),sleep()等方法。 再做一个小狗(doggie)的接口,要求有wao(),fetchBall(),run(),sleep()等方法。
现在你的女朋友想要一只小狗,可是你只找到的一只小猫。请用变压器模式把小猫“适配成”小狗, 让你的女朋友满意。(提示:量力而为。)
第2题、请指出第一题的解答所使用的是那一种形式的变压器模式。
第3题、笔者在许多场合给各种不同水准的专业人士作过各种编程模式的介绍,发现参加OOP开发工作的不同时间长短的人, 对不同的模式理解接受的速度有所不同。唯独在讲过这个男朋友与小狗小猫的例子后,大家对变压器模式的理解都很准确。 让笔者百思不得其解。你知道这是怎样回事吗?
第4题、请讲一讲使用实例形式的变压器模式和使用类形式的变压器模式在第一题的解决上有何影响。
问答题答案
第1题、根据提示,我们可以量力而为。因此,我们将把miao()“适配成”wao(),catchRat()“适配成”fetchBall(), run(),sleep()不变。源代码如下:
图7. 男朋友小狗适配器的类图。
package com.javapatterns.adapter.kittie2doggie;
public interface Doggie
{
void wao();
void fetchBall();
void run();
void sleep();
void setName(String name);
String getName();
}
代码清单11. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
package com.javapatterns.adapter.kittie2doggie;
public class Kittie {
public void miao(){}
public void catchRat() {
}
public void run() {
}
public void sleep() {
}
public String getName(){ return name; }
public void setName(String name){ this.name = name; }
}
代码清单12. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
package com.javapatterns.adapter.kittie2doggie;
public class Boyfriend extends Kittie implements Doggie
{
public void wao()
{
this.miao();
}
public void fetchBall()
{
this.catchRat();
}
public void run()
{
super.run();
}
public void sleep()
{
super.sleep();
}
public String getName()
{
return super.getName();
}
public void setName(String name)
{
super.setName(name);
}
}
代码清单13. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
怎么,她不满意呀?那也有办法:把wao(),fatchBall()当作新的方法,在变压器类中实现。由于你扮演变压器角色, 当她调用wao(),fatchBall()方法是,你就叫一声,或把球捡回来就可以了。
你不满意呀?那就再去找一只真正的小狗吧。变压器模式的威力就到此为止了。
第2题、这里使用的是类形式的变压器模式。
第3题、我的一个学生告诉我,理解这个问题的关键,即男朋友必须装小狗。
第4题、使用类形式的结果是,她一旦想要另一个宠物,她就得换一个男朋友。 使用实例形式的变压器模式的结果是,她如果想要另一个宠物,原来的男朋友就得身兼几种身份。
抽象类WindowAdapter是为接受视窗的事件而准备的。此抽象类内所有的方法都是空的。 使用此类可以很方便地创立listener对象。置换(Override)你所感兴趣的那个事件所对应的方法。 如果你不使用此抽象类,那么你必然规律要实现WindowsListener接口,而那样你就不得不实现所有接口中的方法, 即便是你不需要的事件所对应的方法,你也要给出一个空的方法,而这显然不方便。
显然,抽象类WindowAdapter的目标接口可以选得与源接口一样,而不影响效果。 这就解释了为什么目标接口不出现在WindowAdapter类图(见下面)里。
图3. 本例子SwingUI类与WindowAdapter实例变压器模式的类图定义
SwingUI类的代码如下。
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
class SwingUI extends JFrame implements ActionListener
{
JLabel text, clicked;
JButton button, clickButton;
JPanel panel;
private boolean m_clickMeMode = true;
Public SwingUI()
{
text = new JLabel("我很高兴!");
button = new JButton("理我");
button.addActionListener(this);
panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.setBackground(Color.white);
getContentPane().add(panel);
panel.add(BorderLayout.CENTER, text);
panel.add(BorderLayout.SOUTH, button);
}
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
if (m_clickMeMode)
{
text.setText("我很烦!");
button.setText("别理我");
m_clickMeMode = false;
}
else
{
text.setText("我很高兴!");
button.setText("理我");
m_clickMeMode = true;
}
}
public static void main(String[] args)
{
SwingUI frame = new SwingUI();
frame.setTitle("我");
WindowListener listener = new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
};
frame.addWindowListener(listener);
frame.pack();
frame.setVisible(true);
}
}
代码清单7. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
显然,由于无名内部类是继承自WindowAdapter抽象类,因此只需置换(override)掉我们需要的方法, 即windowClosing()而不必操心WindowListener的其它方法。
本例子在运行时的样子:
图4. SwingUI类在运行时的样子。单击命令键“理我”就变成下图的样子。
图5. 再单击命令键“别理我”就会回到前图的样子
利用变压器模式指方为圆
中国古代有赵高指鹿为马的故事。鹿与马有很多相似之处,没见过的人本就分辨不清,指一指可能没什么大不了的。 指方为圆是否太过?非也。本例就是要指方为圆,需要的只是变压器模式这个魔术手指(Magic Finger)。
变压器模式在本例子的类图如下。
图6. 指方为圆的变压器模式类图
package com.javapatterns.adapter.cube2ball;
public class Cube
{
public Cube(double width)
{
this.width = width;
}
public double calculateVolume()
{
return width * width * width;
}
public double calculateFaceArea()
{
return width * width;
}
public double getWidth()
{
return this.width;
}
public void setWidth(double width)
{
this.width = width;
}
private double width;
}
代码清单8. Cube类的源代码。。
package com.javapatterns.adapter.cube2ball;
public interface BallIF
{
double calculateArea();
double calculateVolume();
double getRadius();
void setRadius(double radius);
}
代码清单9. BallIF接口的源代码。
package com.javapatterns.adapter.cube2ball;
public class MagicFinger implements BallIF
{
public MagicFinger(Cube adaptee)
{
super();
this.adaptee = adaptee;
radius = adaptee.getWidth();
}
public double calculateArea()
{
return PI * 4.0D * ( radius * radius );
}
public double calculateVolume()
{
return PI * 4.0D/3.0D * ( radius * radius * radius );
}
public double getRadius()
{
return radius;
}
public void setRadius(double radius)
{
this.radius = radius;
}
private double radius = 0;
private static final double PI = 3.14D;
private Cube adaptee;
}
代码清单10. MagicFinger类的源代码。
如果读者还记得中学的数学的话,应该可以看出,我们的指方为圆系统其实还是有道理的。它接受一个正方体, 返还此正方体的内切球,也就是能放进此正方体的最大的球。
显然,本例子里,我们使用的是实例形式的变压器模式。这样做的好处是,如果一旦我们决定不仅要支持正方体, 而且要支持四面体等多面体,我们可以使用同一个MagicFinger类,而不必针对每一个多面体都建立一个MagicFinger类。 这样也比较符合“魔术手指”这个名字。
关于模式实现的讨论
本模式在实现时有以下这些值得注意的地方:
第一、目标接口可以省略。此时,目标接口和源接口实际上是相同的。 由于源是一个接口,而变压器类是一个类(或抽象类),因此这种做法看似平庸而并不平庸, 它可以使客户端不必实现不需要的方法。这一点已经在WindowAdapter的例子里做了详尽的分析。
第二、变压器类可以是抽象类。这已经在WindowAdapter的例子里看到了。实际上,WindowAdapter的例子过于简单。 实际的情形里,你可能想给出一些实方法。
第三、带参数的变压器模式。使用这种办法,变压器类就不必,有时可能不能是源类的子类。 变压器类根据参数返还一个合适的实例给客户端。
问答题
第1题、请做一个小猫(kittie)的实类,并实现miao(),catchRat(),run(),sleep()等方法。 再做一个小狗(doggie)的接口,要求有wao(),fetchBall(),run(),sleep()等方法。
现在你的女朋友想要一只小狗,可是你只找到的一只小猫。请用变压器模式把小猫“适配成”小狗, 让你的女朋友满意。(提示:量力而为。)
第2题、请指出第一题的解答所使用的是那一种形式的变压器模式。
第3题、笔者在许多场合给各种不同水准的专业人士作过各种编程模式的介绍,发现参加OOP开发工作的不同时间长短的人, 对不同的模式理解接受的速度有所不同。唯独在讲过这个男朋友与小狗小猫的例子后,大家对变压器模式的理解都很准确。 让笔者百思不得其解。你知道这是怎样回事吗?
第4题、请讲一讲使用实例形式的变压器模式和使用类形式的变压器模式在第一题的解决上有何影响。
问答题答案
第1题、根据提示,我们可以量力而为。因此,我们将把miao()“适配成”wao(),catchRat()“适配成”fetchBall(), run(),sleep()不变。源代码如下:
图7. 男朋友小狗适配器的类图。
package com.javapatterns.adapter.kittie2doggie;
public interface Doggie
{
void wao();
void fetchBall();
void run();
void sleep();
void setName(String name);
String getName();
}
代码清单11. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
package com.javapatterns.adapter.kittie2doggie;
public class Kittie {
public void miao(){}
public void catchRat() {
}
public void run() {
}
public void sleep() {
}
public String getName(){ return name; }
public void setName(String name){ this.name = name; }
}
代码清单12. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
package com.javapatterns.adapter.kittie2doggie;
public class Boyfriend extends Kittie implements Doggie
{
public void wao()
{
this.miao();
}
public void fetchBall()
{
this.catchRat();
}
public void run()
{
super.run();
}
public void sleep()
{
super.sleep();
}
public String getName()
{
return super.getName();
}
public void setName(String name)
{
super.setName(name);
}
}
代码清单13. SwingUI类的源代码。红色的代码就是使用WindowAdapter的无名内部类。
怎么,她不满意呀?那也有办法:把wao(),fatchBall()当作新的方法,在变压器类中实现。由于你扮演变压器角色, 当她调用wao(),fatchBall()方法是,你就叫一声,或把球捡回来就可以了。
你不满意呀?那就再去找一只真正的小狗吧。变压器模式的威力就到此为止了。
第2题、这里使用的是类形式的变压器模式。
第3题、我的一个学生告诉我,理解这个问题的关键,即男朋友必须装小狗。
第4题、使用类形式的结果是,她一旦想要另一个宠物,她就得换一个男朋友。 使用实例形式的变压器模式的结果是,她如果想要另一个宠物,原来的男朋友就得身兼几种身份。