学习设计模式第十八 - 解释器模式

部分示例代码来自DoFactory

 

概述

如果一个特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

 

意图

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

 

UML

 

1 解释器模式的UML

  

参与者

这个模式涉及的类或对象:

  • AbstractExpression

    • 定义执行一个操作的接口

  • TerminalExpression

    • 实现解释器操作相关的语法中的末端符号。

    • 语句中每一个末端符号需要一个实例。

  • NonterminalExpression

    • 语法中每一个规则R::=R1R2...Rn需要这样一个类

    • 维护R1到Rn每一个符号的AbstractExpression类型的实例变量。

    • 实现一个处理语法中非终端节点的解释操作。解释操作在表示R1到Rn的变量上递归调用自身。

  • Context

    • 保存对于解释器的全局信息

  • Client

    • 构建一个抽象语法树表示语法定义的语言中一个特定的句子。抽象语法树由NonterminalExpression和TerminalExpressin类型的实例组装而成。

    • 调用解释操作

 

适用性

如果你的应用很复杂且需要高级配置系统,你可以提供一个脚本语言使终端用户可以通过一些简单的脚本来操作应用程序。解释器模式解决此类通过创建一种脚本语言使终端用户可以自定义解决方案的特定问题。

事实是如果你真需要这种类型的控制,使用已有的命令解释器或表达式计算工具可能更简单更快速。VBScript可以考虑,另外还有Microsoft新支持的.NET动态语言(IronPython等)。

这种类型的问题使它们自己拥有了一种语言的特性。这种语言描述了一个应该被设计为易于理解、易于定义的问题域。另外,这个语言需要映射到一个语法。语法通常是层次树状结构,这种结构自上而下穿过多个层次以终端结点(也称字面量)结束。这种类型的问题,表示为一个语法,可以使用解释器模式来实现。知名的汉诺塔问题是这个类型问题的一个例子,其可以通过一个简单的语言来编码并使用解释器模式来实现。参见:http://home.earthlink.net/~huston2/ps/hanoi_article.html

解释器模式与其它几种模式有一些相似处。正如状态模式和策略模式,它将处理委托给一系列专用的类型。其与组合模式也有一些相似处;基本的解释器是一个Composite的增强,虽然比起Composite模式,它通常需要更复杂的对象组合在一起。

当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:

  • 该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。

  • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的。

总结,当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。

 

DoFactory GoF代码

这个解释器模式的实例中,定义了一种语法并提供一个解释器来处理待分析的语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Interpreter pattern 
// Structural example 
using System;
using System.Collections;
  
namespace DoFactory.GangOfFour.Interpreter.Structural
{
    // MainApp test application
    class MainApp
    {
        static void Main()
        {
            Context context = new Context();
  
            // Usually a tree 
            ArrayList list = new ArrayList();
  
            // Populate 'abstract syntax tree' 
            list.Add(new TerminalExpression());
            list.Add(new NonterminalExpression());
            list.Add(new TerminalExpression());
            list.Add(new TerminalExpression());
  
            // Interpret
            foreach (AbstractExpression exp in list)
            {
                exp.Interpret(context);
            }
  
            // Wait for user
            Console.ReadKey();
        }
    }
  
    // "Context"
    class Context
    {
    }
  
    // "AbstractExpression" abstract class
    abstract class AbstractExpression
    {
        public abstract void Interpret(Context context);
    }
  
    // "TerminalExpression"
    class TerminalExpression : AbstractExpression
    {
        public override void Interpret(Context context)
        {
            Console.WriteLine("Called Terminal.Interpret()");
        }
    }
  
    // "NonterminalExpression"
    class NonterminalExpression : AbstractExpression
    {
        public override void Interpret(Context context)
        {
            Console.WriteLine("Called Nonterminal.Interpret()");
        }
    }
}

这个解释器模式应用的例子演示了将一个罗马数字转换为一个十进制数。

例子中涉及到的类与解释器模式中标准的类对应关系如下:

  • AbstractExpression – Expression

  • TerminalExpression – ThousandExpression, HundredExpression, TenExpression, OneExpression

  • NonterminalExpression – not used

  • Context – Context

  • Client – InterpreterApp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Interpreter pattern 
// Real World example 
using System;
using System.Collections.Generic;
  
namespace DoFactory.GangOfFour.Interpreter.RealWorld
{
    // MainApp test application
    class MainApp
    {
        static void Main()
        {
            string roman = "MCMXXVIII";
            Context context = new Context(roman);
  
            // Build the 'parse tree'
            List<Expression> tree = new List<Expression>();
            tree.Add(new ThousandExpression());
            tree.Add(new HundredExpression());
            tree.Add(new TenExpression());
            tree.Add(new OneExpression());
  
            // Interpret
            foreach (Expression exp in tree)
            {
                exp.Interpret(context);
            }
  
            Console.WriteLine("{0} = {1}", roman, context.Output);
  
            // Wait for user
            Console.ReadKey();
        }
    }
  
    // "Context"
    class Context
    {
        private string _input;
        private int _output;
  
        // Constructor
        public Context(string input)
        {
            this._input = input;
        }
  
        // Gets or sets input
        public string Input
        {
            get return _input; }
            set { _input = value; }
        }
  
        // Gets or sets output
        public int Output
        {
            get return _output; }
            set { _output = value; }
        }
    }
  
    // "AbstractExpression"
    abstract class Expression
    {
        public void Interpret(Context context)
        {
            if (context.Input.Length == 0)
                return;
  
            if (context.Input.StartsWith(Nine()))
            {
                context.Output += (9 * Multiplier());
                context.Input = context.Input.Substring(2);
            }
            else if (context.Input.StartsWith(Four()))
            {
                context.Output += (4 * Multiplier());
                context.Input = context.Input.Substring(2);
            }
            else if (context.Input.StartsWith(Five()))
            {
                context.Output += (5 * Multiplier());
                context.Input = context.Input.Substring(1);
            }
  
            while (context.Input.StartsWith(One()))
            {
                context.Output += (1 * Multiplier());
                context.Input = context.Input.Substring(1);
            }
        }
  
        public abstract string One();
        public abstract string Four();
        public abstract string Five();
        public abstract string Nine();
        public abstract int Multiplier();
    }
  
    // "TerminalExpression"
    // Thousand checks for the Roman Numeral M
    class ThousandExpression : Expression
    {
        public override string One() { return "M"; }
        public override string Four() { return " "; }
        public override string Five() { return " "; }
        public override string Nine() { return " "; }
        public override int Multiplier() { return 1000; }
    }
  
    // "TerminalExpression"
    // Hundred checks C, CD, D or CM
    class HundredExpression : Expression
    {
        public override string One() { return "C"; }
        public override string Four() { return "CD"; }
        public override string Five() { return "D"; }
        public override string Nine() { return "CM"; }
        public override int Multiplier() { return 100; }
    }
  
    // "TerminalExpression"
    // Ten checks for X, XL, L and XC
    class TenExpression : Expression
    {
        public override string One() { return "X"; }
        public override string Four() { return "XL"; }
        public override string Five() { return "L"; }
        public override string Nine() { return "XC"; }
        public override int Multiplier() { return 10; }
    }
  
    // "TerminalExpression"
    // One checks for I, II, III, IV, V, VI, VI, VII, VIII, IX
    class OneExpression : Expression
    {
        public override string One() { return "I"; }
        public override string Four() { return "IV"; }
        public override string Five() { return "V"; }
        public override string Nine() { return "IX"; }
        public override int Multiplier() { return 1; }
    }
}

这段.NET优化版代码实现了与上面例子相同的功能,其中采用了更多现代化的.NET内置特性。上面代码中的抽象类由于没有实现在下面代码中被接口代替。另外,存储表达式(ThousandExpression, HundredExpression等)集合的分析树实现为一个类型参数为Expression的泛型列表。例子中也使用.NET3.0中对象初始化器自动属性等特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Interpreter pattern 
// .NET Optimized example 
using System;
using System.Collections.Generic;
  
namespace DoFactory.GangOfFour.Interpreter.NETOptimized
{
    class MainApp
    {
        static void Main()
        {
            // Construct the 'parse tree'
            var tree = new List<Expression>
            {
                new ThousandExpression(),
                new HundredExpression(),
                new TenExpression(),
                new OneExpression()
            };
  
            // Create the context (i.e. roman value)
            string roman = "MCMXXVIII";
            var context = new Context { Input = roman };
  
            // Interpret
            tree.ForEach(e => e.Interpret(context));
  
            Console.WriteLine("{0} = {1}", roman, context.Output);
  
            // Wait for user
            Console.ReadKey();
        }
    }
  
    // "Context"
    class Context
    {
        public string Input { getset; }
        public int Output { getset; }
    }
  
    // "AbstractExpression"
    abstract class Expression
    {
        public void Interpret(Context context)
        {
            if (context.Input.Length == 0)
                return;
  
            if (context.Input.StartsWith(Nine()))
            {
                context.Output += (9 * Multiplier());
                context.Input = context.Input.Substring(2);
            }
            else if (context.Input.StartsWith(Four()))
            {
                context.Output += (4 * Multiplier());
                context.Input = context.Input.Substring(2);
            }
            else if (context.Input.StartsWith(Five()))
            {
                context.Output += (5 * Multiplier());
                context.Input = context.Input.Substring(1);
            }
  
            while (context.Input.StartsWith(One()))
            {
                context.Output += (1 * Multiplier());
                context.Input = context.Input.Substring(1);
            }
        }
  
        public abstract string One();
        public abstract string Four();
        public abstract string Five();
        public abstract string Nine();
        public abstract int Multiplier();
    }
  
    // Thousand checks for the Roman Numeral M
    // "TerminalExpression"
    class ThousandExpression : Expression
    {
        public override string One() { return "M"; }
        public override string Four() { return " "; }
        public override string Five() { return " "; }
        public override string Nine() { return " "; }
        public override int Multiplier() { return 1000; }
    }
  
    // Hundred checks C, CD, D or CM
    // "TerminalExpression"
    class HundredExpression : Expression
    {
        public override string One() { return "C"; }
        public override string Four() { return "CD"; }
        public override string Five() { return "D"; }
        public override string Nine() { return "CM"; }
        public override int Multiplier() { return 100; }
    }
  
    // Ten checks for X, XL, L and XC
    // "TerminalExpression"
    class TenExpression : Expression
    {
        public override string One() { return "X"; }
        public override string Four() { return "XL"; }
        public override string Five() { return "L"; }
        public override string Nine() { return "XC"; }
        public override int Multiplier() { return 10; }
    }
  
    // One checks for I, II, III, IV, V, VI, VI, VII, VIII, IX
    // "TerminalExpression"
    class OneExpression : Expression
    {
        public override string One() { return "I"; }
        public override string Four() { return "IV"; }
        public override string Five() { return "V"; }
        public override string Nine() { return "IX"; }
        public override int Multiplier() { return 1; }
    }
}

 

来自《大话设计模式》的例子

这个例子使用解释器模式实现了一个乐谱解释程序,这个例子比较简单,只有终结符表达式,没有非终结符表达式。

UML:

图2.音乐解释器例子的UML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
using System;
using System.Collections.Generic;
using System.Text;
  
namespace 解释器模式
{
    class Program
    {
        static void Main(string[] args)
        {
            PlayContext context = new PlayContext();
            //音乐-上海滩
            Console.WriteLine("上海滩:");
            //context.演奏文本 = "T 500 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 D 0.5 E 0.5 G 3 D 0.5 E 0.5 O 1 A 3 A 0.5 O 2 C 0.5 D 1.5 E 0.5 D 0.5 O 1 B 0.5 A 0.5 O 2 C 0.5 O 1 G 3 P 0.5 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 D 0.5 E 0.5 G 3 D 0.5 E 0.5 O 1 A 3 A 0.5 O 2 C 0.5 D 1.5 E 0.5 D 0.5 O 1 B 0.5 A 0.5 G 0.5 O 2 C 3 P 0.5 O 3 C 0.5 C 0.5 O 2 A 0.5 O 3 C 2 P 0.5 O 2 A 0.5 O 3 C 0.5 O 2 A 0.5 G 2.5 G 0.5 E 0.5 A 1.5 G 0.5 C 1 D 0.25 C 0.25 D 0.5 E 2.5 E 0.5 E 0.5 D 0.5 E 2.5 O 3 C 0.5 C 0.5 O 2 B 0.5 A 3 E 0.5 E 0.5 D 1.5 E 0.5 O 3 C 0.5 O 2 B 0.5 A 0.5 E 0.5 G 2 P 0.5 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 D 0.5 E 0.5 G 3 D 0.5 E 0.5 O 1 A 3 A 0.5 O 2 C 0.5 D 1.5 E 0.5 D 0.5 O 1 B 0.5 A 0.5 G 0.5 O 2 C 3 ";
            context.PlayText = "T 500 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 ";
            //音乐-隐形的翅膀
            //Console.WriteLine("隐形的翅膀:"); 
            //context.演奏文本 = "T 1000 O 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 E 1 D 0.5 C 0.5 C 0.5 C 0.5 C 0.5 O 1 A 0.25 G 0.25 G 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 G 0.5 D 0.25 E 0.25 D 0.5 C 0.25 D 0.25 D 1 A 0.5 G 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 E 0.5 D 0.5 C 0.5 C 0.25 D 0.25 O 1 A 1 G 0.5 A 0.5 O 2 C 1.5 D 0.25 E 0.25 D 1 E 0.5 C 0.5 C 3 O 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 E 1 D 0.5 C 0.5 C 0.5 C 0.5 C 0.5 O 1 A 0.25 G 0.25 G 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 G 0.5 D 0.25 E 0.25 D 0.5 C 0.25 D 0.25 D 1 A 0.5 G 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 E 0.5 D 0.5 C 0.5 C 0.25 D 0.25 O 1 A 1 G 0.5 A 0.5 O 2 C 1.5 D 0.25 E 0.25 D 1 E 0.5 C 0.5 C 3 E 0.5 G 0.5 O 3 C 1.5 O 2 B 0.25 O 3 C 0.25 O 2 B 1 A 0.5 G 0.5 A 0.5 O 3 C 0.5 O 2 E 0.5 D 0.5 C 1 C 0.5 C 0.5 C 0.5 O 3 C 1 O 2 G 0.25 A 0.25 G 0.5 D 0.25 E 0.25 D 0.5 C 0.25 D 0.25 D 3 E 0.5 G 0.5 O 3 C 1.5 O 2 B 0.25 O 3 C 0.25 O 2 B 1 A 0.5 G 0.5 A 0.5 O 3 C 0.5 O 2 E 0.5 D 0.5 C 1 C 0.5 C 0.5 C 0.5 O 3 C 1 O 2 G 0.25 A 0.25 G 0.5 D 0.25 E 0.25 D 0.5 C 0.5 C 3 ";
            Expression expression = null;
            try
            {
                while (context.PlayText.Length > 0)
                {
                    string str = context.PlayText.Substring(0, 1);
                    switch (str)
                    {
                        case "O":
                            expression = new Scale();
                            break;
                        case "T":
                            expression = new Speed();
                            break;
                        case "C":
                        case "D":
                        case "E":
                        case "F":
                        case "G":
                        case "A":
                        case "B":
                        case "P":
                            expression = new Note();
                            break;
  
                    }
                    expression.Interpret(context);
  
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
  
            Console.Read();
        }
    }
    //演奏内容
    class PlayContext
    {
        //演奏文本
        private string text;
        public string PlayText
        {
            get return text; }
            set { text = value; }
        }
    }
  
    //表达式
    abstract class Expression
    {
        //解释器
        public void Interpret(PlayContext context)
        {
            if (context.PlayText.Length == 0)
            {
                return;
            }
            else
            {
                string playKey = context.PlayText.Substring(0, 1);
                context.PlayText = context.PlayText.Substring(2);
                double playValue = Convert.ToDouble(context.PlayText.Substring(0, context.PlayText.IndexOf(" ")));
                context.PlayText = context.PlayText.Substring(context.PlayText.IndexOf(" ") + 1);
  
                Excute(playKey, playValue);
  
            }
        }
        //执行
        public abstract void Excute(string key, double value);
    }
  
    //音符
    class Note : Expression
    {
        public override void Excute(string key, double value)
        {
            string note = "";
            switch (key)
            {
                case "C":
                    note = "1";
                    break;
                case "D":
                    note = "2";
                    break;
                case "E":
                    note = "3";
                    break;
                case "F":
                    note = "4";
                    break;
                case "G":
                    note = "5";
                    break;
                case "A":
                    note = "6";
                    break;
                case "B":
                    note = "7";
                    break;
  
            }
            Console.Write("{0} ", note);
        }
    }
  
    //音阶
    class Scale : Expression
    {
        public override void Excute(string key, double value)
        {
            string scale = "";
            switch (Convert.ToInt32(value))
            {
                case 1:
                    scale = "低音";
                    break;
                case 2:
                    scale = "中音";
                    break;
                case 3:
                    scale = "高音";
                    break;
  
            }
            Console.Write("{0} ", scale);
        }
    }
  
    //音速
    class Speed : Expression
    {
        public override void Excute(string key, double value)
        {
            string speed;
            if (value < 500)
                speed = "快速";
            else if (value >= 1000)
                speed = "慢速";
            else
                speed = "中速";
  
            Console.Write("{0} ", speed);
        }
    }
}

 

.NET中的解释器模式

目前还不知道在.NET Framework类库中有解释器模式的应用。

 

效果及实现要点

优点:

将每一个语法规则表示成一个类,方便于实现语言。

因为语法由许多类表示,所以你可以轻易地改变或扩展此语言。

通过在类结构中加入新的语法,可以在解释的同时增加新的行为,如美化输出格式或进行复杂的程序验证。

用途:

需要实现一个简单的语言时,可以使用解释器。

有一个简单的语法,且简单比效率更重要时,使用解释器。

可以处理脚本语言或编程语言。

缺点:

当语法规则的数目太大时,由于为文法中的每一条规则需要定义至少一个类,所以这个模式可能会变得非常复杂。在这种情况下,使用解释器/编译器的产生器可能更合适。

 

使用解释器模式,可以很容易地改变和扩展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。

 

总结

解释器模式不像其他模式一样使用范围那么广,但是在恰当的地方合理的使用解释器模式,起到的作用是四两拨千斤的。

posted @   hystar  阅读(209)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
历史上的今天:
2009-10-11 .Net学习难点讨论系列9 -泛型类型参数的约束 泛型方法
点击右上角即可分享
微信分享提示