Interpreter模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
当类中的许多方法组合成了一种隐式语言的元素(如条件表达式),可以使用Interpreter模式为隐式语言的元素定义类,将方法转化成类组合解释语言。
类图:
案例:搜索对象表达式会使用像 “and”, “not” 和 “or” (称为非终结表达式) 这样的词,也会使用像”100”, “small” 和 “blue” 这样的值(称为终结表达式)。例如 搜索产品:
• 找出价格低于100.00的产品;
• 找出价格低于100.00但不是蓝色的产品;
• 找出蓝色的,小型号的,价格低于200.00的产品。
如果为每个产品搜索组合编写一个方法,这样往往包含很多重复的代码。使用Interpreter: Specification模式 通过使用简单语法和对象组合类建模搜索表达式,支持不同的产品查询,减少代码重复。
案例: 下面的例子使用TDD(测试驱动开发)和重构技术一步一步实现Interpreter模式:
TDD Step 1: 测试之前的准备。
1)产品对象
2)包含多个产品的集合对象
3)需要一个ProductFinder类,提供查询产品功能,ProductFinder对象了解产品的集合对象
public class ProductFinderTest {
//初始化代码
private ProductFinder finder;
private List<Product> products = new ArrayList<Product>();
@Before
public void setup() {
products.add(new Product("Toy A", Color.RED, ProductSize.MEDIUM, 10.00f));
products.add(new Product("Toy B", Color.YELLOW, ProductSize.SMALL, 20.00f));
products.add(new Product("Toy C", Color.PINK, ProductSize.LARGE, 9.99f));
products.add(new Product("Toy D", Color.WHITE, ProductSize.SMALL, 10.00f));
products.add(new Product("Toy E", Color.RED, ProductSize.NOT_APPLICABLE, 200.00f));
finder = new ProductFinder(products);
}
}
TDD Step 2: 上面的测试准备代码失败,需要定义相关的类使它通过测试。
public class Product {
private String name;
private Color color;
private float price;
private ProductSize size;
public Product(String name, Color color, ProductSize size, float price) {
super();
this.name = name;
this.color = color;
this.size = size;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public ProductSize getSize() {
return size;
}
public void setSize(ProductSize size) {
this.size = size;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
public enum ProductSize {
SMALL,MEDIUM,LARGE,NOT_APPLICABLE
}
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
}
TDD Step 3: 写一个testFindByColor()测试方法,检查ProductFinder对象的byColor(…)方法是否可以查询到红色的玩具。
public class ProductFinderTest {
//省略前面的初始化代码
@Test
public void testFinderByColor() {
assertEquals("found 2 red products", finder.byColor(Color.RED).size(), 2);
}
}
TDD Step 4: 因为ProductFinder对象没有提供byColor(…)方法,测试代码失败,编写byColor(…)方法让测试通过。
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getColor().equals(color)) {
foundProducts.add(product);
}
}
return foundProducts;
}
}
TDD Step 5: 同上编写出能查询到价格是$20.00的产品的代码。
public class ProductFinderTest {
//省略前面的初始化代码
@Test
public void testFinderByColor() {
assertEquals("found 2 red products", finder.byColor(Color.RED).size(), 2);
}
@Test
public void testFinderByPrice() {
assertEquals("found 1 product that cost $20.00", finder.byPrice(20.00f).size(), 1);
}
}
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getColor().equals(color)) {
foundProducts.add(product);
}
}
return result;
}
public List<Product> byPrice(float price) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getPrice()==price) {
foundProducts.add(product);
}
}
return foundProducts;
}
}
TDD Step 6: 上面的代码已经看到重复。下一个测试关注颜色和价格下限(低于$20.00的红色产品),另一个测试关注价格下限但不是某个颜色 (低于10.00的但不是白色的产品)
public class ProductFinderTest {
//省略前面的初始化代码
@Test
public void testFinderByColor() {
assertEquals("found 2 red products", finder.byColor(Color.RED).size(), 2);
}
@Test
public void testFinderByPrice() {
assertEquals("found 1 product that cost $20.00", finder.byPrice(20.00f).size(), 1);
}
@Test
public void testFinderByColorAndBelowPrice() {
assertEquals("found 1 red products below $20.00", finder.byColorAndBelowPrice(Color.RED, 20.00f).size(), 1);
}
@Test
public void testFinderByBelowPriceNotAColor() {
assertEquals("found 1 non-white product below $10.00", finder.byBelowPriceNotAColor(Color.RED, 10.00f).size(), 1);
}
}
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getColor().equals(color)) {
foundProducts.add(product);
}
}
return foundProducts;
}
public List<Product> byPrice(float price) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getPrice()==price) {
foundProducts.add(product);
}
}
return foundProducts;
}
public List<Product> byColorAndBelowPrice(Color color, float price) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getPrice() < price
&& product.getColor().equals(color)) {
foundProducts.add(product);
}
}
return foundProducts;
}
public List<Product> byBelowPriceNotAColor(Color color, float price) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getPrice() < price
&& !product.getColor().equals(color)) {
foundProducts.add(product);
}
}
return foundProducts;
}
}
可以看到上面四个查询方法除了条件表达式不同,其它代码都重复,重构需要登场了。
这些条件表达式语法非常适合使用Interpreter:Specification规格模式,每个终结表达式(color, size, price)和非终结表达式(and, or, not) 都代表一种规格,可以通过定义规格类来解释条件表达式。
注: 以下每次重构都要通过测试。
重构第一步:从byColor(Color color)查询方法开始,为它的条件参数 color 创建一个具体的规格类ColorSpec, 它包含一个Color字段,并提供访问方法。
public class ColorSpec {
private Color color;
public ColorSpec(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
修改byColor(Color color)方法,添加一个spec局部变量,条件表达中用对规格类的访问方法getColor()引用代替color参数引用。
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
ColorSpec spec = new ColorSpec(color);
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getColor().equals(spec.getColor())) {
foundProducts.add(product);
}
}
return foundProducts;
}
//省略其它代码
}
重构第二步:提炼方法(Extract Method)替换条件表达式 。
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
ColorSpec spec = new ColorSpec(color);
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(isSatisfiedBy(spec, product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
private boolean isSatisfiedBy(ColorSpec spec, Product product) {
return product.getColor().equals(spec.getColor());
}
//省略其它代码
}
重构第三步:现在,应该搬移方法(Move Method)重构将isSatisfiedBy(…)方法搬移到ColorSpec中,并修改byColor(Color color)方法。
public class ColorSpec {
private Color color;
public ColorSpec(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
public boolean isSatisfiedBy(Product product) {
return product.getColor().equals(getColor());
}
}
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
ColorSpec spec = new ColorSpec(color);
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
//省略其它代码
}
重构第四步:对ColorSpec应该提炼超类重构(Extract Superclass),创建规格超类或接口。
public interface Spec {
public boolean isSatisfiedBy(Product product);
}
public class ColorSpec implements Spec {
private Color color;
public ColorSpec(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
public boolean isSatisfiedBy(Product product) {
return product.getColor().equals(getColor());
}
}
对类似的对象查询方法重复前面的重构,创建规格类。
创建PriceSpec规格类:
public class PriceSpec implements Spec {
private float price;
public PriceSpec(float price) {
this.price = price;
}
public float getPrice() {
return price;
}
public boolean isSatisfiedBy(Product product) {
return product.getPrice() == getPrice();
}
}
创建belowPriceSpec规格类:
public class BelowPriceSpec implements Spec {
private float priceThreshold;
public BelowPriceSpec(float priceThreshold) {
this.priceThreshold = priceThreshold;
}
public float getPriceThreshold() {
return priceThreshold;
}
public boolean isSatisfiedBy(Product product) {
return product.getPrice() < this.getPriceThreshold();
}
}
创建AndSpec规格类:
public class AndSpec implements Spec {
private Spec augend, addend;
public AndSpec(Spec augend, Spec addend) {
this.augend = augend;
this.addend = addend;
}
public Spec getAugend() {
return augend;
}
public Spec getAddend() {
return addend;
}
public boolean isSatisfiedBy(Product product) {
return augend.isSatisfiedBy(product) && addend.isSatisfiedBy(product);
}
}
使用前面创建的规格类重构byColorAndBelowPrice(Color color, float price) 查询方法:
public List<Product> byColorAndBelowPrice(Color color, float price) {
Spec colorSpec = new ColorSpec(color);
Spec belowPriceSpec = new BelowPriceSpec(price);
Spec spec = new AndSpec(colorSpec, belowPriceSpec);
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts ;
}
创建NotSpec规格类:
public class NotSpec implements Spec {
private Spec specToNegate;
public NotSpec(Spec specToNegate) {
this.specToNegate = specToNegate;
}
public Spec getSpecToNegate() {
return specToNegate;
}
public boolean isSatisfiedBy(Product product) {
return !specToNegate.isSatisfiedBy(product);
}
}
使用前面创建的规格类重构byBelowPriceNotAColor(Color color, float price) 查询方法:
public List<Product> byBelowPriceNotAColor(Color color, float price) {
Spec colorSpec = new ColorSpec(color);
Spec belowPriceSpec = new BelowPriceSpec(price);
Spec notSpec = new NotSpec(colorSpec);
Spec spec = new AndSpec(belowPriceSpec, notSpec);
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
现在的ProductFinder类变成这样:
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
ColorSpec spec = new ColorSpec(color);
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
public List<Product> byPrice(float price) {
PriceSpec spec = new PriceSpec(price);
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(product.getPrice()==spec.getPrice()) {
foundProducts.add(product);
}
}
return foundProducts;
}
public List<Product> byColorAndBelowPrice(Color color, float price) {
List<Product> foundProducts = new ArrayList<Product>();
Spec colorSpec = new ColorSpec(color);
Spec belowPriceSpec = new BelowPriceSpec(price);
Spec spec = new AndSpec(colorSpec, belowPriceSpec);
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
public List<Product> byBelowPriceNotAColor(Color color, float price) {
List<Product> foundProducts = new ArrayList<Product>();
Spec colorSpec = new ColorSpec(color);
Spec belowPriceSpec = new BelowPriceSpec(price);
Spec notSpec = new NotSpec(colorSpec);
Spec spec = new AndSpec(belowPriceSpec, notSpec);
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
}
ProductFinder类的所有查询方法的方法体一样,除了规格对象的创建。应用提炼方法(Extract Method)重构。
提炼方法selectBy(Spec spec):
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> byColor(Color color) {
ColorSpec spec = new ColorSpec(color);
return selectBy(spec);
}
public List<Product> byColorAndBelowPrice(Color color, float price) {
Spec colorSpec = new ColorSpec(color);
Spec belowPriceSpec = new BelowPriceSpec(price);
Spec spec = new AndSpec(colorSpec, belowPriceSpec);
return selectBy(spec);
}
public List<Product> byBelowPriceNotAColor(Color color, float price) {
Spec colorSpec = new ColorSpec(color);
Spec belowPriceSpec = new BelowPriceSpec(price);
Spec notSpec = new NotSpec(colorSpec);
Spec spec = new AndSpec(belowPriceSpec, notSpec);
return selectBy(spec);
}
private List<Product> selectBy(Spec spec) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
}
最后使用方法内联化重构(Inline Method),ProductFinder类 只保留 selectBy(Spec spec)方法,由客户端(测试代码)创建Spec规格对象。
最终的ProductFinder类:
package refactor.patterns.interpreter;
import java.util.ArrayList;
import java.util.List;
public class ProductFinder {
private List<Product> products;
public ProductFinder(List<Product> products) {
this.products = products;
}
public List<Product> selectBy(Spec spec) {
List<Product> foundProducts = new ArrayList<Product>();
for(Product product : products){
if(spec.isSatisfiedBy(product)) {
foundProducts.add(product);
}
}
return foundProducts;
}
}
最终的测试代码:
package refactor.patterns.interpreter;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class ProductFinderTest {
private ProductFinder finder;
private List<Product> products = new ArrayList<Product>();
@Before
public void setup() {
products.add(new Product("Toy A", Color.RED, ProductSize.MEDIUM, 10.00f));
products.add(new Product("Toy B", Color.YELLOW, ProductSize.SMALL, 20.00f));
products.add(new Product("Toy C", Color.PINK, ProductSize.LARGE, 9.99f));
products.add(new Product("Toy D", Color.WHITE, ProductSize.SMALL, 10.00f));
products.add(new Product("Toy E", Color.RED, ProductSize.NOT_APPLICABLE, 200.00f));
finder = new ProductFinder(products);
}
@Test
public void testFinderByColor() {
ColorSpec spec = new ColorSpec(Color.RED);
assertEquals("found 2 red products", finder.selectBy(spec).size(), 2);
}
@Test
public void testFinderByPrice() {
PriceSpec spec = new PriceSpec(20.00f);
assertEquals("found 1 product that cost $20.00", finder.selectBy(spec).size(), 1);
}
@Test
public void testFinderByColorAndBelowPrice() {
Spec colorSpec = new ColorSpec(Color.RED);
Spec belowPriceSpec = new BelowPriceSpec(20.00f);
Spec spec = new AndSpec(colorSpec, belowPriceSpec);
assertEquals("found 1 red products below $20.00", finder.selectBy(spec).size(), 1);
}
@Test
public void testFinderByBelowPriceNotAColor() {
Spec colorSpec = new ColorSpec(Color.RED);
Spec belowPriceSpec = new BelowPriceSpec(10.00f);
Spec notSpec = new NotSpec(colorSpec);
Spec spec = new AndSpec(belowPriceSpec, notSpec);
assertEquals("found 1 non-white product below $10.00", finder.selectBy(spec).size(), 1);
}
}