聊一聊设计模式(五)

123 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 24 天,点击查看活动详情

每日英语:

Some people go to priests;others to poetry;I to my friends.

有些人求助于牧师,有些人求助于诗歌,而我则求助于我的朋友们。 -弗吉尼亚·伍尔夫

除了上一篇介绍的建造者模式、外观模式、桥接模式,还有很多其他的常用设计模式。在这里,我将介绍一下迭代器模式、解释器模式、享元模式。

迭代器模式

迭代器模式是一种行为型设计模式,它允许你访问一个聚合对象的各个元素,而又不暴露该对象的内部细节。迭代器模式将迭代算法封装在独立的迭代器对象中,这样可以在不改变聚合对象结构的情况下,通过不同的迭代器对象遍历聚合对象。

以下是迭代器模式的示例代码:

public interface Iterator<T> {
    boolean hasNext();
    T next();
}

public interface IterableCollection<T> {
    Iterator<T> getIterator();
}

public class NameRepository implements IterableCollection<String> {
    private String[] names = {"Alice", "Bob", "Charlie", "Dave"};
    
    @Override
    public Iterator<String> getIterator() {
        return new NameIterator();
    }
    
    private class NameIterator implements Iterator<String> {
        private int index = 0;
        
        @Override
        public boolean hasNext() {
            return index < names.length;
        }
        
        @Override
        public String next() {
            return names[index++];
        }
    }
}

public class Client {
    public static void main(String[] args) {
        IterableCollection<String> namesRepository = new NameRepository();
        Iterator<String> iterator = namesRepository.getIterator();
        
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

在这个例子中,我们使用迭代器模式来遍历一个字符串数组。Iterator 接口定义了 hasNext() 和 next() 方法,用于访问聚合对象的各个元素。IterableCollection 接口表示聚合对象,其中 getIterator() 方法返回一个 Iterator 对象。NameRepository 类是一个具体的聚合对象,它包含一个字符串数组,实现了 getIterator() 方法。NameIterator 类是一个具体的迭代器对象,它实现了 Iterator 接口,并使用内部的 index 变量来迭代字符串数组。

在 Client 类中,我们创建了一个 NameRepository 对象,并通过 getIterator() 方法获取一个 Iterator 对象。然后我们使用 while 循环遍历聚合对象的各个元素,每次调用 hasNext() 和 next() 方法,输出一个字符串。这个过程对客户端是透明的,客户端只需要获取聚合对象和迭代器对象,就可以完成一个复杂的任务。

解释器模式

解释器模式是一种行为型设计模式,它用于解释一种特定语言的语法或表达式。解释器模式定义了一个语法解释器,它通过解析和执行语法规则来实现特定的行为。

以下是解释器模式的示例代码:

public interface Expression {
    boolean interpret(String context);
}

public class TerminalExpression implements Expression {
    private String data;
    
    public TerminalExpression(String data) {
        this.data = data;
    }
    
    @Override
    public boolean interpret(String context) {
        return context.contains(data);
    }
}

public class OrExpression implements Expression {
    private Expression expr1;
    private Expression expr2;
    
    public OrExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    
    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) || expr2.interpret(context);
    }
}

public class AndExpression implements Expression {
    private Expression expr1;
    private Expression expr2;
    
    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    
    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) && expr2.interpret(context);
    }
}

public class Client {
    public static void main(String[] args) {
        Expression person1 = new TerminalExpression("Alice");
        Expression person2 = new TerminalExpression("Bob");
        Expression isSingle = new OrExpression(person1, person2);
        
        Expression person3 = new TerminalExpression("Charlie");
        Expression person4 = new TerminalExpression("David");
        Expression isMarried = new AndExpression(person3, person4);
        
        boolean aliceSingle = isSingle.interpret("Alice");
        boolean bobSingle = isSingle.interpret("Bob");
        boolean charlieMarried = isMarried.interpret("Charlie and David are married");
        
        System.out.println("Is Alice single? " + aliceSingle);
        System.out.println("Is Bob single? " + bobSingle);
        System.out.println("Is Charlie married? " + charlieMarried);
    }
}

在这个例子中,我们使用解释器模式来判断一个人是否单身或已婚。Expression 接口定义了 interpret() 方法,用于解释语法规则。TerminalExpression 类是一个终端表达式,它包含一个字符串,并实现了 interpret() 方法,用于判断给定的上下文是否包含这个字符串。OrExpression 和 AndExpression 类是非终端表达式,它们包含两个 Expression 对象,并实现了 interpret() 方法,用于按照指定的逻辑操作符解释这两个表达式。Client 类是一个客户端,它创建了几个 Expression 对象,并使用 interpret() 方法来判断一些字符串是否符合特定的语法规则。

享元模式

另外一种常用的设计模式是享元模式(Flyweight Pattern),它主要用于在存在大量相似对象时,共享这些对象以减少内存占用。这种模式可以提高系统的性能和效率,特别是当系统中存在大量重复对象时。

享元模式的核心思想是将一个对象的状态拆分成内部状态和外部状态,内部状态指的是对象共享部分,而外部状态则是随着对象的不同而不同的部分。享元模式通过共享内部状态,来减少创建对象的数量,从而节省内存。

下面是一个简单的Java代码示例:

public class Tree {
   private final String type;
   private final String color;
   private final String texture;
 
   public Tree(String type, String color, String texture) {
      this.type = type;
      this.color = color;
      this.texture = texture;
   }
 
   public void draw(int x, int y) {
      System.out.println("Drawing a " + type + " tree with " + color + " color and " + texture + " texture at " + x + ", " + y);
   }
}

在这个示例中,我们定义了一个Tree类,它有三个属性:type、color和texture。我们可以在需要的时候创建一个Tree对象,然后调用它的draw()方法进行绘制。但是,如果我们需要创建大量的Tree对象,这将会占用大量的内存。

现在,我们可以使用享元模式来减少创建对象的数量。首先,我们创建一个TreeFactory类,它将负责管理已经创建的Tree对象。该类包含一个Map对象,用于存储已经创建的Tree对象。它提供了一个getTree()方法,该方法接受三个参数:type、color和texture,然后返回一个Tree对象。

import java.util.HashMap;
import java.util.Map;
 
public class TreeFactory {
   private static final Map<String, Tree> treeMap = new HashMap<>();
 
   public static Tree getTree(String type, String color, String texture) {
      String key = type + color + texture;
 
      if (!treeMap.containsKey(key)) {
         treeMap.put(key, new Tree(type, color, texture));
      }
 
      return treeMap.get(key);
   }
}

现在,我们可以使用TreeFactory类来创建Tree对象,而不是直接使用Tree类。例如:

Tree tree1 = TreeFactory.getTree("pine", "green", "smooth");
Tree tree2 = TreeFactory.getTree("oak", "brown", "rough");
Tree tree3 = TreeFactory.getTree("pine", "green", "smooth");

tree1.draw(10, 20);
tree2.draw(30, 40);
tree3.draw(50, 60);

在这个示例中,我们使用TreeFactory类来创建Tree对象,而不是直接使用Tree类。当我们需要创建一个新的Tree对象时,我们首先检查Map中是否已经有一个具有相同属性的Tree对象。如果没有,我们就创建一个新的Tree对象并将其添加到Map中;否则,我们就返回Map中已经存在。

总结

本篇主要带大家了聊了聊迭代器模式、解释器模式、享元模式,大家有兴趣的话,可以cv一下代码,跑一跑,感受一下java设计模式的魅力。