对每个应用来说,至少有以下两种不同的用户分类。
基本用户:这类用户只希望能够凭直觉使用应用。他们不喜欢花太多时间配置或学习应用的内部。对他们来说,基本的用法就足够了。
高级用户:这些用户,实际上通常是少数,不介意花费额外的时间学习如何使用应用的高级特性。如果知道学会之后能得到以下好处,他们甚至会去学习一种配置(或脚本)语言。
能够更好地控制一个应用
以更好的方式表达想法
提高生产力
解释器(Interpreter)模式仅能引起应用的高级用户的兴趣。这是因为解释器模式背后的主要思想是让非初级用户和领域专家使用一门简单的语言来表达想种简单的语言法。然而,什么是一种简单的语言?对于我们的需求来说,一就是没编程语言那么复杂的语言。
一般而言,我们想要创建的是一种领域特定语言(Domain Specific Language,DSL)。DSL是一种针对一个特定领域的有限表达能力的计算机语言。很多不同的事情都使用DSL,比如,战斗模拟、记账、可视化、配置、通信协议等。DSL分为内部DSL和外部DSL。
内部DSL构建在一种宿主编程语言之上。内部DSL的一个例子是,使用Python解决线性方程组的一种语言。使用内部DSL的优势是我们不必担心创建、编译及解析语法,因为这些已经被宿主语言解决掉了。劣势是会受限于宿主语言的特性。如果宿主语言不具备这些特性,构建一种表达能力强、简洁而且优美的内部DSL是富有挑战性的。
外部DSL不依赖某种宿主语言。DSL的创建者可以决定语言的方方面面(语法、句法等),但也要负责为其创建一个解析器和编译器。为一种新语言创建解析器和编译器是一个非常复杂、长期而又痛苦的过程。
解释器模式仅与内部DSL相关。因此,我们的目标是使用宿主语言提供的特性构建一种简单但有用的语言,在这里,宿主语言是Python。注意,解释器根本不处理语言解析,它假设我们已经有某种便利形式的解析好的数据,可以是抽象语法树(abstract syntax tree,AST)或任何其他好用的数据结构。
举例实现
我们来创建一种内部DSL控制一个智能屋。一个事件的形式为command->receiver->arguments。参数部分是可选的。不要求参数的事件例子如下:
open -> gate
要求参数的事件例子如下:
increase -> boiler temperature -> 3 degrees
实现一种内部DSL有多种方式。我们可以使用普通的正则表达式、字符串处理、操作符重载的组合以及元编程,或者一个
能帮我们完成困难工作的库/工具。在这里我们使用一个工具来完成解析工作,该工具名为pyparsing。下面我用pip安装它。
代码非常简单,tests里面放了很多个命令,根据之前定义好的event格式用pyparsing进行解析,代码文件interpreter.py
# coding: utf-8
from pyparsing import Word, OneOrMore, Optional, Group, Suppress, alphanums
class Gate:
def __init__(self):
self.is_open = False
def __str__(self):
return 'open' if self.is_open else 'closed'
def open(self):
print('opening the gate')
self.is_open = True
def close(self):
print('closing the gate')
self.is_open = False
class Garage:
def __init__(self):
self.is_open = False
def __str__(self):
return 'open' if self.is_open else 'closed'
def open(self):
print('opening the garage')
self.is_open = True
def close(self):
print('closing the garage')
self.is_open = False
class Aircondition:
def __init__(self):
self.is_on = False
def __str__(self):
return 'on' if self.is_on else 'off'
def turn_on(self):
print('turning on the aircondition')
self.is_on = True
def turn_off(self):
print('turning off the aircondition')
self.is_on = False
class Heating:
def __init__(self):
self.is_on = True
def __str__(self):
return 'on' if self.is_on else 'off'
def turn_on(self):
print('turning on the heating')
self.is_on = True
def turn_off(self):
print('turning off the heating')
self.is_on = False
class Boiler:
def __init__(self):
self.temperature = 83
def __str__(self):
return 'boiler temperature:{}'.format(self.temperature)
def increase_temperature(self, amount):
print("increasing the boiler's temperature by {} degrees".format(self.temperature))
self.temperature += amount
def decrease_temperature(self, amount):
print("decresing the boiler's temperature by {} degrees".format(self.temperature))
self.temperature -= amount
class Fridge:
def __init__(self):
self.temperature = 2
def __str__(self):
return "fridge temperature: {}".format(self.temperature)
def increase_temperature(self, amount):
print("increasing the fridge's temperature by {} degrees".format(self.temperature))
self.temperature += amount
def decrease_temperature(self, amount):
print("decresing the fridge's temperature by {} degrees".format(self.temperature))
self.temperature -= amount
def main():
word = Word(alphanums)
command = Group(OneOrMore(word))
token = Suppress("->")
device = Group(OneOrMore(word))
argument = Group(OneOrMore(word))
event = command + token + device + Optional(token + argument)
gate = Gate()
garage = Garage()
aico = Aircondition()
heating = Heating()
boiler = Boiler()
fridge = Fridge()
tests = ('open-> gate',
'close->garage',
'turn on-> aircondition',
'turn off -> heating',
'increase-> boiler temperature -> 5 degrees',
'decrease-> fridge temperature -> 2 degrees')
open_actions = {'gate': gate.open,
'garage': garage.open,
'aircondition': aico.turn_on,
'heating': heating.turn_on,
'boiler temperature': boiler.increase_temperature,
'fridge temperature': fridge.increase_temperature}
close_actions = {
'gate': gate.close,
'garage': garage.close,
'aircondition': aico.turn_off,
'heating': heating.turn_off,
'boiler temperature': boiler.decrease_temperature,
'fridge temperature': fridge.decrease_temperature
}
for t in tests:
if len(event.parseString(t)) == 2:
cmd, dev = event.parseString(t)
cmd_str, dev_str = ' '.join(cmd), ' '.join(dev)
if 'open' in cmd_str or 'turn on' in cmd_str:
open_actions[dev_str]()
elif 'close' in cmd_str or 'turn off' in cmd_str:
close_actions[dev_str]()
elif len(event.parseString(t)) == 3:
cmd, dev, arg = event.parseString(t)
cmd_str, dev_str, arg_str = ' '.join(cmd), ' '.join(dev), ' '.join(arg)
num_arg = 0
try:
num_arg = int(arg_str.split()[0])
except ValueError as err:
print("expected number but got: '{}'".format(arg_str[0]))
if 'increase' in cmd_str and num_arg > 0:
open_actions[dev_str](num_arg)
elif 'decrease' in cmd_str and num_arg > 0:
close_actions[dev_str](num_arg)
if __name__ == '__main__':
main()
运行结果如下:
opening the gate
closing the garage
turning on the aircondition
turning off the heating
increasing the boiler's temperature by 83 degrees
decresing the fridge's temperature by 2 degrees
Process finished with exit code 0