设计模式 学习 3()



七个结构型模式之3(适配器 /桥接模式/组合模式)
适配器模式

Sunny软件公司在很久以前曾开发了一个算法库,里面包含了一些常用的算法,例如排序算法和查找算法,在进行各类软件开发时经常需要重用该算法库中的算法。在为某学校开发教务管理系统时,开发人员发现需要对学生成绩进行排序和查找,该系统的设计人员已经开发了一个成绩操作接口ScoreOperation,在该接口中声明了排序方法sort(int[]) 和查找方法search(int[], int),为了提高排序和查找的效率,开发人员决定重用算法库中的快速排序算法类QuickSort和二分查找算法类BinarySearch,其中QuickSort的quickSort(int[])方法实现了快速排序,BinarySearch 的binarySearch (int[], int)方法实现了二分查找。
由于某些原因,现在Sunny公司开发人员已经找不到该算法库的源代码,无法直接通过复制和粘贴操作来重用其中的代码;部分开发人员已经针对ScoreOperation接口编程,如果再要求对该接口进行修改或要求大家直接使用QuickSort类和BinarySearch类将导致大量代码需要修改。
【设计模式 学习 3()】Sunny软件公司开发人员面对这个没有源码的算法库,遇到一个幸福而又烦恼的问题:如何在既不修改现有接口又不需要任何算法库代码的基础上能够实现算法库的重用?


//抽象成绩操作类:目标接口
interface ScoreOperation {
public int[] sort(int array[]); //成绩排序
public int search(int array[],int key); //成绩查找
}


//快速排序类:适配者
class QuickSort {
public int[] quickSort(int array[]) {
sort(array,0,array.length-1);
return array;
}


public void sort(int array[],int p, int r) {
int q=0;
if(p q=partition(array,p,r);
sort(array,p,q-1);
sort(array,q+1,r);
}
}


public int partition(int[] a, int p, int r) {
int x=a[r];
int j=p-1;
for (int i=p; i<=r-1; i++) {
if (a[i]<=x) {
j++;
swap(a,j,i);
}
}
swap(a,j+1,r);
return j+1;
}


public void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}


//二分查找类:适配者
class BinarySearch {
public int binarySearch(int array[],int key) {
int low = 0;
int high = array.length -1;
while(low <= high) {
int mid = (low + high) / 2;
int midVal = array[mid];
if(midVal < key) {
low = mid +1;
}
else if (midVal > key) {
high = mid -1;
}
else {
return 1; //找到元素返回1
}
}
return -1; //未找到元素返回-1
}
}


//操作适配器:适配器
class OperationAdapter implements ScoreOperation {
private QuickSort sortObj; //定义适配者QuickSort对象
private BinarySearch searchObj; //定义适配者BinarySearch对象


public OperationAdapter() {
sortObj = new QuickSort();
searchObj = new BinarySearch();
}


public int[] sort(int array[]) {
return sortObj.quickSort(array); //调用适配者类QuickSort的排序方法
}


public int search(int array[],int key) {
return searchObj.binarySearch(array,key); //调用适配者类BinarySearch的查找方法
}
}



缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。






桥接模式


Sunny软件公司欲开发一个跨平台图像浏览系统,要求该系统能够显示BMP、JPG、GIF、PNG等多种格式的文件,并且能够在Windows、Linux、Unix等多个操作系统上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。系统需具有较好的扩展性以支持新的文件格式和操作系统。


答案:
为了减少所需生成的子类数目,实现将操作系统和图像文件格式两个维度分离,使它们可以独立改变,Sunny公司开发人员使用桥接模式来重构跨平台图像浏览系统的设计,其基本结构如图10-5所示:



设计模式 学习 3()
文章图片





在图10-5中,Image充当抽象类,其子类JPGImage、PNGImage、BMPImage和GIFImage充当扩充抽象类;ImageImp充当实现类接口,其子类WindowsImp、LinuxImp和UnixImp充当具体实现类。完整代码如下所示:
[java] view plain copy


  1. //像素矩阵类:辅助类,各种格式的文件最终都被转化为像素矩阵,不同的操作系统提供不同的方式显示像素矩阵
  2. class Matrix {
  3. //此处代码省略
  4. }
  5. //抽象图像类:抽象类
  6. abstract class Image {
  7. protected ImageImp imp;
  8. public void setImageImp(ImageImp imp) {
  9. this.imp = imp;
  10. }
  11. public abstract void parseFile(String fileName);
  12. }
  13. //抽象操作系统实现类:实现类接口
  14. interface ImageImp {
  15. public void doPaint(Matrix m); //显示像素矩阵m
  16. }
  17. //Windows操作系统实现类:具体实现类
  18. class WindowsImp implements ImageImp {
  19. public void doPaint(Matrix m) {
  20. //调用Windows系统的绘制函数绘制像素矩阵
  21. System.out.print("在Windows操作系统中显示图像:");
  22. }
  23. }
  24. //Linux操作系统实现类:具体实现类
  25. class LinuxImp implements ImageImp {
  26. public void doPaint(Matrix m) {
  27. //调用Linux系统的绘制函数绘制像素矩阵
  28. System.out.print("在Linux操作系统中显示图像:");
  29. }
  30. }
  31. //Unix操作系统实现类:具体实现类
  32. class UnixImp implements ImageImp {
  33. public void doPaint(Matrix m) {
  34. //调用Unix系统的绘制函数绘制像素矩阵
  35. System.out.print("在Unix操作系统中显示图像:");
  36. }
  37. }
  38. //JPG格式图像:扩充抽象类
  39. class JPGImage extends Image {
  40. public void parseFile(String fileName) {
  41. //模拟解析JPG文件并获得一个像素矩阵对象m;
  42. Matrix m = new Matrix();
  43. imp.doPaint(m);
  44. System.out.println(fileName + ",格式为JPG。");
  45. }
  46. }
  47. //PNG格式图像:扩充抽象类
  48. class PNGImage extends Image {
  49. public void parseFile(String fileName) {
  50. //模拟解析PNG文件并获得一个像素矩阵对象m;
  51. Matrix m = new Matrix();
  52. imp.doPaint(m);
  53. System.out.println(fileName + ",格式为PNG。");
  54. }
  55. }
  56. //BMP格式图像:扩充抽象类
  57. class BMPImage extends Image {
  58. public void parseFile(String fileName) {
  59. //模拟解析BMP文件并获得一个像素矩阵对象m;
  60. Matrix m = new Matrix();
  61. imp.doPaint(m);
  62. System.out.println(fileName + ",格式为BMP。");
  63. }
  64. }
  65. //GIF格式图像:扩充抽象类
  66. class GIFImage extends Image {
  67. public void parseFile(String fileName) {
  68. //模拟解析GIF文件并获得一个像素矩阵对象m;
  69. Matrix m = new Matrix();
  70. imp.doPaint(m);
  71. System.out.println(fileName + ",格式为GIF。");
  72. }
  73. }
为了让系统具有更好的灵活性和可扩展性,我们引入了配置文件,将具体扩充抽象类和具体实现类类名都存储在配置文件中,再通过反射生成对象,将生成的具体实现类对象注入到扩充抽象类对象中,其中,配置文件config.xml的代码如下所示:
[html] view plain copy


  1. JPGImage
  2. WindowsImp
用于读取配置文件config.xml并反射生成对象的XMLUtil类的代码如下所示:
[java] view plain copy


  1. import javax.xml.parsers.*;
  2. import org.w3c.dom.*;
  3. import org.xml.sax.SAXException;
  4. import java.io.*;
  5. public class XMLUtil {
  6. //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
  7. public static Object getBean(String args) {
  8. try {
  9. //创建文档对象
  10. DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
  11. DocumentBuilder builder = dFactory.newDocumentBuilder();
  12. Document doc;
  13. doc = builder.parse(new File("config.xml"));
  14. NodeList nl=null;
  15. Node classNode=null;
  16. String cName=null;
  17. nl = doc.getElementsByTagName("className");
  18. if(args.equals("image")) {
  19. //获取第一个包含类名的节点,即扩充抽象类
  20. classNode=nl.item(0).getFirstChild();
  21. }
  22. else if(args.equals("os")) {
  23. //获取第二个包含类名的节点,即具体实现类
  24. classNode=nl.item(1).getFirstChild();
  25. }
  26. cName=classNode.getNodeValue();
  27. //通过类名生成实例对象并将其返回
  28. Class c=Class.forName(cName);
  29. Object obj=c.newInstance();
  30. return obj;
  31. }
  32. catch(Exception e) {
  33. e.printStackTrace();
  34. return null;
  35. }
  36. }
  37. }
编写如下客户端测试代码:
[java] view plain copy


  1. class Client {
  2. public static void main(String args[]) {
  3. Image image;
  4. ImageImp imp;
  5. image = (Image)XMLUtil.getBean("image");
  6. imp = (ImageImp)XMLUtil.getBean("os");
  7. image.setImageImp(imp);
  8. image.parseFile("小龙女");
  9. }
  10. }




组合模式:




Sunny软件公司欲开发一个杀毒(AntiVirus)软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现需要提供该杀毒软件的整体框架设计方案


答案:
[java] view plain copy


  1. //为了突出核心框架代码,我们对杀毒过程的实现进行了大量简化
  2. import java.util.*;
  3. //图像文件类
  4. class ImageFile {
  5. private String name;
  6. public ImageFile(String name) {
  7. this.name = name;
  8. }
  9. public void killVirus() {
  10. //简化代码,模拟杀毒
  11. System.out.println("----对图像文件'" + name + "'进行杀毒");
  12. }
  13. }
  14. //文本文件类
  15. class TextFile {
  16. private String name;
  17. public TextFile(String name) {
  18. this.name = name;
  19. }
  20. public void killVirus() {
  21. //简化代码,模拟杀毒
  22. System.out.println("----对文本文件'" + name + "'进行杀毒");
  23. }
  24. }
  25. //文件夹类
  26. class Folder {
  27. private String name;
  28. //定义集合folderList,用于存储Folder类型的成员
  29. private ArrayList folderList = new ArrayList();
  30. //定义集合imageList,用于存储ImageFile类型的成员
  31. private ArrayList imageList = new ArrayList();
  32. //定义集合textList,用于存储TextFile类型的成员
  33. private ArrayList textList = new ArrayList();
  34. public Folder(String name) {
  35. this.name = name;
  36. }
  37. //增加新的Folder类型的成员
  38. public void addFolder(Folder f) {
  39. folderList.add(f);
  40. }
  41. //增加新的ImageFile类型的成员
  42. public void addImageFile(ImageFile image) {
  43. imageList.add(image);
  44. }
  45. //增加新的TextFile类型的成员
  46. public void addTextFile(TextFile text) {
  47. textList.add(text);
  48. }
  49. //需提供三个不同的方法removeFolder()、removeImageFile()和removeTextFile()来删除成员,代码省略
  50. //需提供三个不同的方法getChildFolder(int i)、getChildImageFile(int i)和getChildTextFile(int i)来获取成员,代码省略
  51. public void killVirus() {
  52. System.out.println("****对文件夹'" + name + "'进行杀毒"); //模拟杀毒
  53. //如果是Folder类型的成员,递归调用Folder的killVirus()方法
  54. for(Object obj : folderList) {
  55. ((Folder)obj).killVirus();
  56. }
  57. //如果是ImageFile类型的成员,调用ImageFile的killVirus()方法
  58. for(Object obj : imageList) {
  59. ((ImageFile)obj).killVirus();
  60. }
  61. //如果是TextFile类型的成员,调用TextFile的killVirus()方法
  62. for(Object obj : textList) {
  63. ((TextFile)obj).killVirus();
  64. }
  65. }
  66. }
编写如下客户端测试代码进行测试:
[java] view plain copy


  1. class Client {
  2. public static void main(String args[]) {
  3. Folder folder1,folder2,folder3;
  4. folder1 = new Folder("Sunny的资料");
  5. folder2 = new Folder("图像文件");
  6. folder3 = new Folder("文本文件");
  7. ImageFile image1,image2;
  8. image1 = new ImageFile("小龙女.jpg");
  9. image2 = new ImageFile("张无忌.gif");
  10. TextFile text1,text2;
  11. text1 = new TextFile("九阴真经.txt");
  12. text2 = new TextFile("葵花宝典.doc");
  13. folder2.addImageFile(image1);
  14. folder2.addImageFile(image2);
  15. folder3.addTextFile(text1);
  16. folder3.addTextFile(text2);
  17. folder1.addFolder(folder2);
  18. folder1.addFolder(folder3);
  19. folder1.killVirus();
  20. }
  21. }






现在这个设计 不符合开闭 原则,如果新增加了 video 类型的文件,就需要改 文件夹的代码,怎么办?


在图11-5中, AbstractFile充当抽象构件类,Folder充当容器构件类,ImageFile、TextFile和VideoFile充当叶子构件类。完整代码如下所示:
[java] view plain copy


  1. import java.util.*;
  2. //抽象文件类:抽象构件
  3. abstract class AbstractFile {
  4. public abstract void add(AbstractFile file);
  5. public abstract void remove(AbstractFile file);
  6. public abstract AbstractFile getChild(int i);
  7. public abstract void killVirus();
  8. }
  9. //图像文件类:叶子构件
  10. class ImageFile extends AbstractFile {
  11. private String name;
  12. public ImageFile(String name) {
  13. this.name = name;
  14. }
  15. public void add(AbstractFile file) {
  16. System.out.println("对不起,不支持该方法!");
  17. }
  18. public void remove(AbstractFile file) {
  19. System.out.println("对不起,不支持该方法!");
  20. }
  21. public AbstractFile getChild(int i) {
  22. System.out.println("对不起,不支持该方法!");
  23. return null;
  24. }
  25. public void killVirus() {
  26. //模拟杀毒
  27. System.out.println("----对图像文件'" + name + "'进行杀毒");
  28. }
  29. }
  30. //文本文件类:叶子构件
  31. class TextFile extends AbstractFile {
  32. private String name;
  33. public TextFile(String name) {
  34. this.name = name;
  35. }
  36. public void add(AbstractFile file) {
  37. System.out.println("对不起,不支持该方法!");
  38. }
  39. public void remove(AbstractFile file) {
  40. System.out.println("对不起,不支持该方法!");
  41. }
  42. public AbstractFile getChild(int i) {
  43. System.out.println("对不起,不支持该方法!");
  44. return null;
  45. }
  46. public void killVirus() {
  47. //模拟杀毒
  48. System.out.println("----对文本文件'" + name + "'进行杀毒");
  49. }
  50. }
  51. //视频文件类:叶子构件
  52. class VideoFile extends AbstractFile {
  53. private String name;
  54. public VideoFile(String name) {
  55. this.name = name;
  56. }
  57. public void add(AbstractFile file) {
  58. System.out.println("对不起,不支持该方法!");
  59. }
  60. public void remove(AbstractFile file) {
  61. System.out.println("对不起,不支持该方法!");
  62. }
  63. public AbstractFile getChild(int i) {
  64. System.out.println("对不起,不支持该方法!");
  65. return null;
  66. }
  67. public void killVirus() {
  68. //模拟杀毒
  69. System.out.println("----对视频文件'" + name + "'进行杀毒");
  70. }
  71. }
  72. //文件夹类:容器构件
  73. class Folder extends AbstractFile {
  74. //定义集合fileList,用于存储AbstractFile类型的成员
  75. private ArrayList fileList=new ArrayList();
  76. private String name;
  77. public Folder(String name) {
  78. this.name = name;
  79. }
  80. public void add(AbstractFile file) {
  81. fileList.add(file);
  82. }
  83. public void remove(AbstractFile file) {
  84. fileList.remove(file);
  85. }
  86. public AbstractFile getChild(int i) {
  87. return (AbstractFile)fileList.get(i);
  88. }
  89. public void killVirus() {
  90. System.out.println("****对文件夹'" + name + "'进行杀毒"); //模拟杀毒
  91. //递归调用成员构件的killVirus()方法
  92. for(Object obj : fileList) {
  93. ((AbstractFile)obj).killVirus();
  94. }
  95. }
  96. }
编写如下客户端测试代码:
[java] view plain copy


  1. class Client {
  2. public static void main(String args[]) {
  3. //针对抽象构件编程
  4. AbstractFile file1,file2,file3,file4,file5,folder1,folder2,folder3,folder4;
  5. folder1 = new Folder("Sunny的资料");
  6. folder2 = new Folder("图像文件");
  7. folder3 = new Folder("文本文件");
  8. folder4 = new Folder("视频文件");
  9. file1 = new ImageFile("小龙女.jpg");
  10. file2 = new ImageFile("张无忌.gif");
  11. file3 = new TextFile("九阴真经.txt");
  12. file4 = new TextFile("葵花宝典.doc");
  13. file5 = new VideoFile("笑傲江湖.rmvb");
  14. folder2.add(file1);
  15. folder2.add(file2);
  16. folder3.add(file3);
  17. folder3.add(file4);
  18. folder4.add(file5);
  19. folder1.add(folder2);
  20. folder1.add(folder3);
  21. folder1.add(folder4);
  22. //从“Sunny的资料”节点开始进行杀毒操作
  23. folder1.killVirus();
  24. }
  25. }






透明组合模式与安全组合模式
通过引入组合模式,Sunny公司设计的杀毒软件具有良好的可扩展性,在增加新的文件类型时,无须修改现有类库代码,只需增加一个新的文件类作为AbstractFile类的子类即可,但是由于在AbstractFile中声明了大量用于管理和访问成员构件的方法,例如add()、remove()等方法,我们不得不在新增的文件类中实现这些方法,提供对应的错误提示和异常处理。为了简化代码,我们有以下两个解决方案:
解决方案一:将叶子构件的add()、remove()等方法的实现代码移至AbstractFile类中,由AbstractFile提供统一的默认实现,代码如下所示:
[java] view plain copy


  1. //提供默认实现的抽象构件类
  2. abstract class AbstractFile {
  3. public void add(AbstractFile file) {
  4. System.out.println("对不起,不支持该方法!");
  5. }
  6. public void remove(AbstractFile file) {
  7. System.out.println("对不起,不支持该方法!");
  8. }
  9. public AbstractFile getChild(int i) {
  10. System.out.println("对不起,不支持该方法!");
  11. return null;
  12. }
  13. public abstract void killVirus();
  14. }
如果客户端代码针对抽象类AbstractFile编程,在调用文件对象的这些方法时将出现错误提示。如果不希望出现任何错误提示,我们可以在客户端定义文件对象时不使用抽象层,而直接使用具体叶子构件本身,客户端代码片段如下所示:
[java] view plain copy


  1. class Client {
  2. public static void main(String args[]) {
  3. //不能透明处理叶子构件
  4. ImageFile file1,file2;
  5. TextFile file3,file4;
  6. VideoFile file5;
  7. AbstractFile folder1,folder2,folder3,folder4;
  8. //其他代码省略
  9. }
  10. }
这样就产生了一种不透明的使用方式,即在客户端不能全部针对抽象构件类编程,需要使用具体叶子构件类型来定义叶子对象。
解决方案二:除此之外,还有一种解决方法是在抽象构件AbstractFile中不声明任何用于访问和管理成员构件的方法,代码如下所示:
[java] view plain copy


  1. abstract class AbstractFile {
  2. public abstract void killVirus();
  3. }
此时,由于在AbstractFile中没有声明add()、remove()等访问和管理成员的方法,其叶子构件子类无须提供实现;而且无论客户端如何定义叶子构件对象都无法调用到这些方法,不需要做任何错误和异常处理,容器构件再根据需要增加访问和管理成员的方法,但这时候也存在一个问题:客户端不得不使用容器类本身来声明容器构件对象,否则无法访问其中新增的add()、remove()等方法,如果客户端一致性地对待叶子和容器,将会导致容器构件的新增对客户端不可见,客户端代码对于容器构件无法再使用抽象构件来定义,客户端代码片段如下所示:
[java] view plain copy


  1. class Client {
  2. public static void main(String args[]) {
  3. AbstractFile file1,file2,file3,file4,file5;
  4. Folder folder1,folder2,folder3,folder4; //不能透明处理容器构件
  5. //其他代码省略
  6. }
  7. }
在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式:
(1) 透明组合模式
透明组合模式中,抽象构件Component中声明了所有用于管理成员对象的方法,包括add()、remove()以及getChild()等方法,这样做的好处是确保所有的构件类都有相同的接口。在客户端看来,叶子对象与容器对象所提供的方法是一致的,客户端可以相同地对待所有的对象。透明组合模式也是组合模式的标准形式,虽然上面的解决方案一在客户端可以有不透明的实现方法,但是由于在抽象构件中包含add()、remove()等方法,因此它还是透明组合模式,透明组合模式的完整结构如图11-6所示:

设计模式 学习 3()
文章图片








    推荐阅读