Golang - Interpreter Pattern

Golang - Interpreter Pattern

Introduction

The Interpreter pattern is a behavioral pattern from the Gang of Four (GoF) design patterns that provides a way to evaluate and interpret a language grammar or expression. The pattern defines a language and its grammar, and then provides an interpreter that can interpret the language and execute the grammar. The Interpreter pattern is useful for implementing domain-specific languages and for parsing and interpreting user input.

Example

In Golang, the interpreter pattern can be implemented easily using the combination of interfaces, structs, and functions. Here is a more practical example of how to implement the Interpreter pattern in Golang.

Suppose we have an e-commerce website and we want to provide users with a way to filter products based on certain criteria such as price range, brand, color, etc. We can define a domain-specific language (DSL) to represent these filters and use the Interpreter pattern to parse and execute the DSL.

Let's define our DSL:

  • price represents the price filter

  • brand represents the brand filter

  • color represents the color filter

Each filter has a specific syntax:

  • price:range(min,max) where min and max are integers representing the minimum and maximum prices respectively

  • brand:brand_name where brand_name is a string representing the name of the brand

  • color:color_name where color_name is a string representing the name of the color

We can define our Interpreter interface and implement it for each filter type:

type Interpreter interface {
    Interpret(context *Context) []Product
}

type PriceFilter struct {
    min int
    max int
}

func (pf *PriceFilter) Interpret(context *Context) []Product {
    products := context.Products
    filtered := []Product{}
    for _, p := range products {
        if p.Price >= pf.min && p.Price <= pf.max {
            filtered = append(filtered, p)
        }
    }
    return filtered
}

type BrandFilter struct {
    brand string
}

func (bf *BrandFilter) Interpret(context *Context) []Product {
    products := context.Products
    filtered := []Product{}
    for _, p := range products {
        if p.Brand == bf.brand {
            filtered = append(filtered, p)
        }
    }
    return filtered
}

type ColorFilter struct {
    color string
}

func (cf *ColorFilter) Interpret(context *Context) []Product {
    products := context.Products
    filtered := []Product{}
    for _, p := range products {
        if p.Color == cf.color {
            filtered = append(filtered, p)
        }
    }
    return filtered
}

Next, we define a Context struct that contains the list of products and a Parser struct that can parse the DSL and build the filter tree:

type Product struct {
    Name  string
    Price int
    Brand string
    Color string
}

type Context struct {
    Products []Product
}

type Parser struct {
    stack []Interpreter
}

func (p *Parser) Parse(expression string) {
    tokens := strings.Split(expression, " ")
    for _, token := range tokens {
        parts := strings.Split(token, ":")
        switch parts[0] {
        case "price":
            args := strings.Trim(parts[1], "range()")
            prices := strings.Split(args, ",")
            min, _ := strconv.Atoi(prices[0])
            max, _ := strconv.Atoi(prices[1])
            filter := &PriceFilter{min: min, max: max}
            p.stack = append(p.stack, filter)
        case "brand":
            brand := parts[1]
            filter := &BrandFilter{brand: brand}
            p.stack = append(p.stack, filter)
        case "color":
            color := parts[1]
            filter := &ColorFilter{color: color}
            p.stack = append(p.stack, filter)
        }
    }
}

func (p *Parser) Result(context *Context) []Product {
    for _, i := range p.stack {
        context.Products = i.Interpret(context)
    }
    return context.Products
}

Finally, we can use the Parser to filter our list of products:

func main() {
    products := []Product{
        {Name: "Product 1", Price: 100, Brand: "Brand 1", Color: "Red"},
        {Name: "Product 2", Price: 50, Brand: "Brand 2", Color: "Blue"},
        {Name: "Product 3", Price: 200, Brand: "Brand 1", Color: "Green"},
    }
    context := &Context{Products: products}

    parser := &Parser{}
    parser.Parse("price:range(50,150) brand:Brand 1 color:Green")

    filtered := parser.Result(context)
    fmt.Println(filtered)
}

In this example, we define a DSL to filter products based on price range, brand, and color. We use the Interpreter pattern to parse and execute the DSL, resulting in a list of filtered products.

Conclusion

In conclusion, the Interpreter pattern is a powerful pattern that provides a way to evaluate and interpret a language grammar or expression. In Golang, it can be easily implemented using the combination of interfaces, structs, and functions. It can be used to implement domain-specific languages, parse and interpret user input, and filter data. By using the Interpreter pattern, we can create more powerful and expressive applications that can understand and execute complex user input and commands.


References

Did you find this article valuable?

Support Matthias Bruns by becoming a sponsor. Any amount is appreciated!