抽象工厂模式是一种比工厂模式抽象程度更高的模式。简而言之,抽象工厂类和工厂类的原理相同,只不过工厂类返回的是普通类的实例;而抽象工厂类返回的是一个工厂类的实例。
抽象工厂类最经典的应用支持多个GUI界面,例如Java的程序同时支持Windows、Motif和Macintosh的界面(这种技术被称为界面类型,look-and-feel)。开发人员可以通过抽象工厂获得某种界面对应的GUI工厂类,通过GUI工厂类开发人员可以对界面上的组件(例如按钮、文本框等)进行操作。从Java 1.2 开始,Java在系统层提供了实现了抽象工厂模式的界面类型。当开发人员在程序中确定界面类型后,就可以通过界面类型获得界面上组件的实例。例如在下面的这段代码中,程序获得操作系统的类型,并根据操作系统设定程序的界面类型:
例子
让我们通过一个简单的例子来看一看抽象工厂模式是如何在应用层被使用的。假设你才买了一套住宅,屋顶有一个花园,而你需要设计一下花园的布局。花园右侧有一个花台,边上需要种一排植物,中心需要种主体植物。根据风格的不同,花园可以被设计为典雅型、使用型和懒人型。
首先我们需要一个Garden类作为基类,通过该类中可以获得花园中种植的植物:
然后我们需要实现Plant类,在该类中保存植物的名称。
在实际的应用中,每种类型的花园对应的植物应该从数据库中查找。在这里为了方便,将每种植物的类型花园对应的植物类型直接写在程序中:
现在我们有了一组Garden的子类,每个子类都实现了同样的方法:getShade(),getCenter()和getBorder()。现在可以通过给抽象工厂类传递特殊的参数获得某一中类型的花园的实例:
运行程序后显示下面的窗口:
在界面中包含了两个部分:左边选择花园的类型。当用户选择了其中一种类型后,程序通过抽象工厂类MakeGarden获得实际的Garden类的实例。
当用户按下"中间"、"墙边"或"花台"的按钮时,程序返回Plant对象,然后通过GardenPanel类将Plant对象的名称显示在界面上:
程序中其他类的软代码如下所示:
小结
使用抽象工厂模式的一个主要原因是由于它能够将实现类和它们的生成过程完全分割开来。实现类被隐藏在工厂类中,调用者不必知道实现类的任何信息。由于这种特性,开发人员也很容易在一组实现类中进行替换。虽然抽象工厂中的子类继承了同一个基类,但是它们也可以拥有与其他子类不同的方法。例如在ElegantGarden类中,我们可以加上花架和假山的属性以及获得这两个属性的方法;而在其他的花园类中则不需要包含这两个属性。当然在程序中需要判断当前的实例是否支持这些特殊的方法,解决的方案有两种:一种是将包含特殊方法在内的所有的方法都定义在基类中,只有支持特殊方法的类才实现这些特殊方法;另一种方案是在抽象类中判断实现类的类型:
抽象工厂类最经典的应用支持多个GUI界面,例如Java的程序同时支持Windows、Motif和Macintosh的界面(这种技术被称为界面类型,look-and-feel)。开发人员可以通过抽象工厂获得某种界面对应的GUI工厂类,通过GUI工厂类开发人员可以对界面上的组件(例如按钮、文本框等)进行操作。从Java 1.2 开始,Java在系统层提供了实现了抽象工厂模式的界面类型。当开发人员在程序中确定界面类型后,就可以通过界面类型获得界面上组件的实例。例如在下面的这段代码中,程序获得操作系统的类型,并根据操作系统设定程序的界面类型:
String laf = UIManager.getSystemLookAndFeelClassName(); try { UIManager.setLookAndFeel(laf); } catch (UnsupportedLookAndFeelException exc) { System.err.println("UnsupportedL&F: " + laf); } catch (Exception exc) { System.err.println("Error loading " + laf); } |
让我们通过一个简单的例子来看一看抽象工厂模式是如何在应用层被使用的。假设你才买了一套住宅,屋顶有一个花园,而你需要设计一下花园的布局。花园右侧有一个花台,边上需要种一排植物,中心需要种主体植物。根据风格的不同,花园可以被设计为典雅型、使用型和懒人型。
首先我们需要一个Garden类作为基类,通过该类中可以获得花园中种植的植物:
// Garden.java public abstract class Garden { public abstract Plant getShade(); // 花台中的植物 public abstract Plant getCenter(); // 中间的植物 public abstract Plant getBorder(); // 边上的植物 } |
然后我们需要实现Plant类,在该类中保存植物的名称。
// Plant.java public class Plant { String name; public Plant(String pname) { name = pname; } public String getName() { return name; } } |
在实际的应用中,每种类型的花园对应的植物应该从数据库中查找。在这里为了方便,将每种植物的类型花园对应的植物类型直接写在程序中:
// ElegantGarden.java (典雅型) public class ElegantGarden extends Garden { public Plant getShade() { return new Plant("郁金香"); } public Plant getCenter() { return new Plant("榕树"); } public Plant getBorder() { return new Plant("兰草"); } } // PracticalGarden.java (实用型) public class PracticalGarden extends Garden { public Plant getShade() { return new Plant("葡萄"); } public Plant getCenter() { return new Plant("石榴"); } public Plant getBorder() { return new Plant("丝瓜"); } } // LasyGarden.java(懒人型) public class LasyGarden extends Garden { public Plant getShade() { return new Plant("月季"); } public Plant getCenter() { return new Plant("茶花"); } public Plant getBorder() { return new Plant("竹"); } } |
现在我们有了一组Garden的子类,每个子类都实现了同样的方法:getShade(),getCenter()和getBorder()。现在可以通过给抽象工厂类传递特殊的参数获得某一中类型的花园的实例:
// GardenMaker.java public class GardenMaker { // 抽象工厂类 private Garden gd; public Garden getGarden(String gtype) { gd = new ElegantGarden(); //缺省情况 if(gtype.equals("实用型")) gd = new PracticalGarden(); if(gtype.equals("懒人型")) gd = new LasyGarden(); return gd; } } |
运行程序后显示下面的窗口:
![](http://developer.ccidnet.com/pub/attachment/2003/3/120923.jpg)
在界面中包含了两个部分:左边选择花园的类型。当用户选择了其中一种类型后,程序通过抽象工厂类MakeGarden获得实际的Garden类的实例。
public void itemStateChanged(ItemEvent e) { Checkbox ck = (Checkbox)e.getSource(); garden = new GardenMaker().getGarden(ck.getLabel()); shadePlant=""; centerPlant=""; borderPlant = ""; gardenPlot.repaint(); } |
当用户按下"中间"、"墙边"或"花台"的按钮时,程序返回Plant对象,然后通过GardenPanel类将Plant对象的名称显示在界面上:
//---------------------------------- public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); if(obj == Center) setCenter(); if(obj == Border) setBorder(); if(obj == Shade) setShade(); if(obj == Quit) System.exit(0); } //---------------------------------- private void setCenter() { if(garden != null) centerPlant = garden.getCenter().getName(); gardenPlot.repaint(); } private void setBorder() { if(garden != null) borderPlant = garden.getBorder().getName(); gardenPlot.repaint(); } private void setShade() { if(garden != null) shadePlant = garden.getShade().getName(); gardenPlot.repaint(); } |
程序中其他类的软代码如下所示:
// Gardener.java import java.awt.*; import java.awt.event.*; public class Gardener extends Frame implements ActionListener, ItemListener { private Checkbox Elegant, Practical, LasyBoy; private Button Center, Border, Shade, Quit; private Garden garden = null; private GardenPanel gardenPlot; private String borderPlant = "", centerPlant = "", shadePlant = ""; public Gardener() { super("屋顶花园规划"); setGUI(); } //---------------------------------- private void setGUI() { setBackground(Color.lightGray); setLayout(new GridLayout(1,2)); Panel left = new Panel(); add(left); Panel right= new Panel(); add(right); left.setLayout(new GridLayout(4, 1)); left.add(new Label("花园类型")); CheckboxGroup grp= new CheckboxGroup(); Elegant = new Checkbox("典雅型", grp, false); Practical = new Checkbox("实用型", grp, false); LasyBoy = new Checkbox("懒人型", grp, false); left.add(Elegant); left.add(Practical); left.add(LasyBoy); Elegant.addItemListener(this); LasyBoy.addItemListener(this); Practical.addItemListener(this); right.setLayout(new GridLayout(2,1)); gardenPlot = new GardenPanel(); gardenPlot.setBackground(Color.white); Panel botRight = new Panel(); right.add(gardenPlot); right.add(botRight); Center = new Button("中间"); Border = new Button("墙边"); Shade = new Button("花台"); Quit = new Button("退出"); botRight.add(Center); Center.addActionListener(this); botRight.add(Border); Border.addActionListener(this); botRight.add(Shade); Shade.addActionListener(this); botRight.add(Quit); Quit.addActionListener(this); setBounds(200,200, 400,300); setVisible(true); } //---------------------------------- public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); if(obj == Center) setCenter(); if(obj == Border) setBorder(); if(obj == Shade) setShade(); if(obj == Quit) System.exit(0); } //---------------------------------- private void setCenter() { if(garden != null) centerPlant = garden.getCenter().getName(); gardenPlot.repaint(); } private void setBorder() { if(garden != null) borderPlant = garden.getBorder().getName(); gardenPlot.repaint(); } private void setShade() { if(garden != null) shadePlant = garden.getShade().getName(); gardenPlot.repaint(); } //---------------------------------- public void itemStateChanged(ItemEvent e) { Checkbox ck = (Checkbox)e.getSource(); garden = new GardenMaker().getGarden(ck.getLabel()); shadePlant=""; centerPlant=""; borderPlant = ""; gardenPlot.repaint(); } //---------------------------------- static public void main(String argv[]) { new Gardener(); } //-------------------------------- class GardenPanel extends Panel { public void paint (Graphics g) { Dimension sz=getSize(); g.setColor(Color.lightGray); g.fillArc( 0, 0, 80, 80,0, 360); g.setColor(Color.black); g.drawRect(0,0, sz.width-1, sz.height-1); g.drawString(centerPlant, 100, 50); g.drawString( borderPlant, 75, 120); g.drawString(shadePlant, 10, 40); } } } // Gardener.java |
使用抽象工厂模式的一个主要原因是由于它能够将实现类和它们的生成过程完全分割开来。实现类被隐藏在工厂类中,调用者不必知道实现类的任何信息。由于这种特性,开发人员也很容易在一组实现类中进行替换。虽然抽象工厂中的子类继承了同一个基类,但是它们也可以拥有与其他子类不同的方法。例如在ElegantGarden类中,我们可以加上花架和假山的属性以及获得这两个属性的方法;而在其他的花园类中则不需要包含这两个属性。当然在程序中需要判断当前的实例是否支持这些特殊的方法,解决的方案有两种:一种是将包含特殊方法在内的所有的方法都定义在基类中,只有支持特殊方法的类才实现这些特殊方法;另一种方案是在抽象类中判断实现类的类型:
if (gard instanceof ElegantGarden) String rock = gard.getRockery(); // 获得假山名称 |