迭代器模式

实现结构以及优点

迭代器模式也叫游标模式, 将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。

设计模式: 行为型(下) - 图1

容器对象通过依赖注入传递到迭代器类中。遍历集合一般有三种方式:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。相对于 for 循环遍历,利用迭代器来遍历有下面三个优势:

  • 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
  • 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
  • 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中基于接口而非实现编程,替换迭代器也变得更加容易。

遍历集合的同时增删元素的问题

可能导致不可预期行为, 也叫未决行为.

[a,b,c]此时cursor=1, 指向b
添加元素示例:
如果在b前添加个d, [a,d,b,c], 继续遍历, b会被重复遍历; 但在b后添加元素无影响
删除元素示例:
如果删除a,[b,c], 继续遍历, c无法遍历到; 但在b后删除元素无影响

为了避免这一情况, 有两种解决方案

  • 遍历时不允许增删元素—>由于无法判断遍历的终止, 因此不适合
  • 遍历时增删元素后报错—>jdk采用
  1. 在 ArrayList 中定义一个成员变量 modCount,记录集合被修改的次数,集合每调用一次增加或删除元素的函数,就会给 modCount 加 1。
  2. 当通过调用集合上的 iterator() 函数来创建迭代器的时候,我们把 modCount 值传递给迭代器的 expectedModCount 成员变量,之后每次调用迭代器上的 hasNext()、next()、currentItem() 函数,我们都会检查集合上的 modCount 是否等于 expectedModCount,也就是看,在创建完迭代器之后,modCount 是否改变过。
  3. 如果两个值不相同,那就说明集合存储的元素已经改变了,要么增加了元素,要么删除了元素,所以抛出运行时异常 ```java

public class ArrayIterator implements Iterator { private int cursor; private ArrayList arrayList; private int expectedModCount;

public ArrayIterator(ArrayList arrayList) { this.cursor = 0; this.arrayList = arrayList; this.expectedModCount = arrayList.modCount; }

@Override public boolean hasNext() { checkForComodification(); return cursor < arrayList.size(); }

@Override public void next() { checkForComodification(); cursor++; }

@Override public Object currentItem() { checkForComodification(); return arrayList.get(cursor); }

private void checkForComodification() { if (arrayList.modCount != expectedModCount) throw new ConcurrentModificationException(); } }

//代码示例 public class Demo { public static void main(String[] args) { List names = new ArrayList<>(); names.add(“a”); names.add(“b”); names.add(“c”); names.add(“d”);

  1. Iterator<String> iterator = names.iterator();
  2. iterator.next();
  3. names.remove("a");
  4. iterator.next();//抛出ConcurrentModificationException异常

} }

  1. <a name="3grjz"></a>
  2. ### iterator的remove()方法实现
  3. 1. next()方法返回cursor()位置的元素, 将cursor保存在lastRet, 并将cursor+1
  4. 1. remove()方法删除lastRet位置的元素, 并将lastRet赋值给cursor
  5. 示例<br />假设a元素对应位置为1<br />E a=iterator.next();//游标变为1+1位置, lastRet在1位置<br />iterator.remove();//删除a, 将游标重置到1位置
  6. ```java
  7. public class ArrayList<E> {
  8. transient Object[] elementData;
  9. private int size;
  10. public Iterator<E> iterator() {
  11. return new Itr();
  12. }
  13. private class Itr implements Iterator<E> {
  14. int cursor; // index of next element to return
  15. int lastRet = -1; // index of last element returned; -1 if no such
  16. int expectedModCount = modCount;
  17. Itr() {}
  18. public boolean hasNext() {
  19. return cursor != size;
  20. }
  21. @SuppressWarnings("unchecked")
  22. public E next() {
  23. checkForComodification();
  24. int i = cursor;
  25. if (i >= size)
  26. throw new NoSuchElementException();
  27. Object[] elementData = ArrayList.this.elementData;
  28. if (i >= elementData.length)
  29. throw new ConcurrentModificationException();
  30. cursor = i + 1;
  31. return (E) elementData[lastRet = i];
  32. }
  33. public void remove() {
  34. if (lastRet < 0)
  35. throw new IllegalStateException();
  36. checkForComodification();
  37. try {
  38. ArrayList.this.remove(lastRet);
  39. cursor = lastRet;
  40. lastRet = -1;
  41. expectedModCount = modCount;
  42. } catch (IndexOutOfBoundsException ex) {
  43. throw new ConcurrentModificationException();
  44. }
  45. }
  46. }
  47. }

访问者模式

业务代码示例:

  1. public abstract class ResourceFile {
  2. protected String filePath;
  3. public ResourceFile(String filePath) {
  4. this.filePath = filePath;
  5. }
  6. }
  7. public class PdfFile extends ResourceFile {
  8. public PdfFile(String filePath) {
  9. super(filePath);
  10. }
  11. //...
  12. }
  13. //...PPTFile、WordFile代码省略...
  14. public class Extractor {
  15. public void extract2txt(PPTFile pptFile) {
  16. //...
  17. System.out.println("Extract PPT.");
  18. }
  19. public void extract2txt(PdfFile pdfFile) {
  20. //...
  21. System.out.println("Extract PDF.");
  22. }
  23. public void extract2txt(WordFile wordFile) {
  24. //...
  25. System.out.println("Extract WORD.");
  26. }
  27. }
  28. public class ToolApplication {
  29. public static void main(String[] args) {
  30. Extractor extractor = new Extractor();
  31. List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
  32. for (ResourceFile resourceFile : resourceFiles) {
  33. extractor.extract2txt(resourceFile);
  34. }
  35. }
  36. private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
  37. List<ResourceFile> resourceFiles = new ArrayList<>();
  38. //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
  39. resourceFiles.add(new PdfFile("a.pdf"));
  40. resourceFiles.add(new WordFile("b.word"));
  41. resourceFiles.add(new PPTFile("c.ppt"));
  42. return resourceFiles;
  43. }
  44. }

上面的代码是编译通过不了的,第 37 行会报错
多态是一种动态绑定,可以在运行时获取对象的实际类型,来运行实际类型对应的方法。而函数重载是一种静态绑定,在编译时并不能获取对象的实际类型,而是根据声明类型执行声明类型对应的方法。

访问者模式

  1. public abstract class ResourceFile {
  2. protected String filePath;
  3. public ResourceFile(String filePath) {
  4. this.filePath = filePath;
  5. }
  6. abstract public void accept(Visitor vistor);
  7. }
  8. public class PdfFile extends ResourceFile {
  9. public PdfFile(String filePath) {
  10. super(filePath);
  11. }
  12. @Override
  13. public void accept(Visitor visitor) {
  14. visitor.visit(this);
  15. }
  16. //...
  17. }
  18. //...PPTFile、WordFile跟PdfFile类似,这里就省略了...
  19. public interface Visitor {
  20. void visit(PdfFile pdfFile);
  21. void visit(PPTFile pdfFile);
  22. void visit(WordFile pdfFile);
  23. }
  24. public class Extractor implements Visitor {
  25. @Override
  26. public void visit(PPTFile pptFile) {
  27. //...
  28. System.out.println("Extract PPT.");
  29. }
  30. @Override
  31. public void visit(PdfFile pdfFile) {
  32. //...
  33. System.out.println("Extract PDF.");
  34. }
  35. @Override
  36. public void visit(WordFile wordFile) {
  37. //...
  38. System.out.println("Extract WORD.");
  39. }
  40. }
  41. public class Compressor implements Visitor {
  42. @Override
  43. public void visit(PPTFile pptFile) {
  44. //...
  45. System.out.println("Compress PPT.");
  46. }
  47. @Override
  48. public void visit(PdfFile pdfFile) {
  49. //...
  50. System.out.println("Compress PDF.");
  51. }
  52. @Override
  53. public void visit(WordFile wordFile) {
  54. //...
  55. System.out.println("Compress WORD.");
  56. }
  57. }
  58. public class ToolApplication {
  59. public static void main(String[] args) {
  60. Extractor extractor = new Extractor();
  61. List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
  62. for (ResourceFile resourceFile : resourceFiles) {
  63. resourceFile.accept(extractor);
  64. }
  65. Compressor compressor = new Compressor();
  66. for(ResourceFile resourceFile : resourceFiles) {
  67. resourceFile.accept(compressor);
  68. }
  69. }
  70. private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
  71. List<ResourceFile> resourceFiles = new ArrayList<>();
  72. //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
  73. resourceFiles.add(new PdfFile("a.pdf"));
  74. resourceFiles.add(new WordFile("b.word"));
  75. resourceFiles.add(new PPTFile("c.ppt"));
  76. return resourceFiles;
  77. }
  78. }

设计模式: 行为型(下) - 图2

访问者模式
允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。

一般来说,访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作(抽取文本、压缩等),但为了避免不断添加功能导致类(PdfFile、PPTFile、WordFile)不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类(Extractor、Compressor)中。

备忘录模式

备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。这个模式的定义表达了两部分内容:一部分是,存储副本以便后期恢复;另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。

对于大对象的备份来说,备份占用的存储空间会比较大,备份和恢复的耗时会比较长。针对这个问题,不同的业务场景有不同的处理方式。比如,只备份必要的恢复信息,结合最新的数据来恢复;再比如,全量备份和增量备份相结合,低频全量备份,高频增量备份,两者结合来做恢复。

示例代码:

  1. public class InputText {
  2. private StringBuilder text = new StringBuilder();
  3. public String getText() {
  4. return text.toString();
  5. }
  6. public void append(String input) {
  7. text.append(input);
  8. }
  9. public Snapshot createSnapshot() {
  10. return new Snapshot(text.toString());
  11. }
  12. public void restoreSnapshot(Snapshot snapshot) {
  13. this.text.replace(0, this.text.length(), snapshot.getText());
  14. }
  15. }
  16. public class Snapshot {
  17. private String text;
  18. public Snapshot(String text) {
  19. this.text = text;
  20. }
  21. public String getText() {
  22. return this.text;
  23. }
  24. }
  25. public class SnapshotHolder {
  26. private Stack<Snapshot> snapshots = new Stack<>();
  27. public Snapshot popSnapshot() {
  28. return snapshots.pop();
  29. }
  30. public void pushSnapshot(Snapshot snapshot) {
  31. snapshots.push(snapshot);
  32. }
  33. }
  34. public class ApplicationMain {
  35. public static void main(String[] args) {
  36. InputText inputText = new InputText();
  37. SnapshotHolder snapshotsHolder = new SnapshotHolder();
  38. Scanner scanner = new Scanner(System.in);
  39. while (scanner.hasNext()) {
  40. String input = scanner.next();
  41. if (input.equals(":list")) {
  42. System.out.println(inputText.toString());
  43. } else if (input.equals(":undo")) {
  44. Snapshot snapshot = snapshotsHolder.popSnapshot();
  45. inputText.restoreSnapshot(snapshot);
  46. } else {
  47. snapshotsHolder.pushSnapshot(inputText.createSnapshot());
  48. inputText.append(input);
  49. }
  50. }
  51. }
  52. }

命令模式

命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。

解释器模式

解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
解释器模式只在一些特定的领域会被用到,比如编译器、规则引擎、正则表达式。

它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分成一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析

中介模式

中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
设计模式: 行为型(下) - 图3

观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。