본문 바로가기
Practical Python Design Patterns

Interpreter Pattern

by 자동매매 2023. 3. 28.

CHAPTER Interpreter Pattern

Silvia Broome: What do you do when you can t sleep? Tobin Keller: I stay awake.

e Interpreter

Sometimes it makes sense to not use Python to describe a problem or the solution to such a problem. When we need a language geared toward a specific problem domain, we turn to something called a domain-specific language.

Domain-Specific Languages

Some languages, like Python, were created to tackle any type of problem. Python belongs to a whole family of languages, called general-purpose languages, designed to be used

to solve any problem. On rare occasions, it makes more sense to create a language that does only one thing, but that does it extremely well. These languages, which belongto a family called domain-specific languages (DSLs), are helpful for people who are experts

in some specific domain but are not programmers.

You have probably already encountered DSLs in your programming career. If you

ever tinkered on a website, you know CSS, which is a DSL for defining how HTML should be rendered in a browser.

style.css

body {

color: #00ff00; }

179

' Wessel Badenhorst 2017

W. Badenhorst, Practical Python Design Patterns, https://doi.org/10.1007/978-1-4842-2680-3_12
CHAPTER INTERPRETER PATTERN

This little bit of CSS code tells a browser to render all the text inside the body tag of the web page that uses this CSS file in green. The code needed by the browser to do this is a lot more complex than this simple snippet of code, yet CSS is simple enough for most people to pick up in a couple of hours. HTML itself is a DSL that is interpreted by the browser and displayed as a formatted document with a fairly intricate layout.

Alternatively, you may have used Aloe in behavior-driven development, where you define acceptance criteria for a project in human-readable sentences.

Install the aloe framework using pip $ pip install aloe

Now, we can write some tests. zero.feature

Feature: Compute factorial In order to play with aloe As beginners

We’ll implement factorial

Scenario: Factorial of 0

Given I have the number 0 When I compute its factorial Then I see the number 1

Next, you map these steps to some Python code so the Aloe framework can understand the behavior described.

steps.py

from aloe import *

@step(r’I have the number (\d+)’) def have_the_number(step, number): world.number = int(number)

@step(’I compute its factorial’)

def compute_its_factorial(step):

world.number = factorial(world.number)

180
CHAPTER INTERPRETER PATTERN

@step(r’I see the number (\d+)’)

def check_number(step, expected): expected = int(expected)

assert world.number == expected, \ *"Got %d"* % world.number

def factorial(number): return -1

This code lets you define the outcome of the program in terms that a non-technical person can understand and then maps those terms to actual code, which makes sure

that they execute as described in the features file. The description in the .feature file

is the DSL, and the Python code in the steps.py file is used to change the words and phrases in the DSL into something a computer can understand.

When you decide to create a DSL (practice domain engineering), you are enabling clear communication between domain experts and developers. Not only will the experts

be able to express themselves more clearly, but the developers will understand what they require, and so will the computer. What you will generally find is that a domain expert

will describe a problem, or the solution to a problem, from their domain to a developer, who will then immediately write down the description in the domain-specific language. Domain experts can understand this description of the problem or solution as presented

by the developer and will be able to suggest changes or expansions to the description. Descriptions can thus be turned into functioning solutions during the course of a meeting, or over a couple of days at most, greatly increasing the productivity of everyone involved.

Let s make this practical.

You have been contracted to create a system restaurants can use to define their specials. These specials sometimes have simple rules like Buy one get one free, but other times they are a lot more complex, like If you buy three pizzas on a Tuesday, the cheapest one is free. The challenge is that these rules change often, and as such redeploying the code for every change just does not make sense. If you did not care about being forced

to change the code every week when your customer decides to change the rules for specials, the potential magnitude of the if statement this would result in should be enough to scare you off.

Since we have been talking about DSLs, you might suspect that creating a DSL for the restaurant is the solution. Any changes or additions to the criteria for a special could then be stored independent of the main application and only be interpreted when called upon.

181
CHAPTER 13 INTERPRETER PATTERN

In a very informative talk, Neil Green, Technical Lead at ICE, suggested the following process for developing a DSL:

  1. Understand your domain.
  2. Model your domain.
  3. Implement your domain.

If we were to follow Neil s guidance, we would go talk to the restaurant owner. We would try to learn how he or she expresses the rules for specials, and then try to capture these rules in a language that makes sense to us as well as to the owner. We would then draw some sort of diagram based on our conversation. If the owner agrees that we captured the essence of what he or she was saying, we get to write some code to implement our model.

DSLs come in two flavors: internal and external. External DSLs have code written in an external file or as a string. This string or file is then read and parsed by the application before it gets used. CSS is an example of an external DSL, where the browser is tasked with reading, parsing, and interpreting the textual data that represents how HTML elements should be displayed. Internal DSLs, on the other hand, use features of the language Python, in this case to enable people to write code that resembles the

domain syntax, an example would be how numpy uses Python to allow mathematicians

to describe linear algebra problems.

The interpreter pattern specifically caters to internal DSLs.

The idea is to create a syntax that is significantly less comprehensive than a general- purpose language, yet significantly more expressive, specifically as it relates to the domain in question.

Have a look at the following code, which defines rules for specials run by our imaginary restaurant and is written as conditionals in Python.

pizzas = [item for tab.items if item.type == "pizza"]

if len(pizzas) > 2 and day_of_week == 4:

cheapest_pizza_price = min([pizza.price for pizza in pizzas]) tab.add_discount(cheapest_pizza_price)

drinks = [item for tab.items if item.type == "drink"]

if 17 < hour_now < 19:

for item in tab.items:

if item.type == "drink":

item.price = item.price * 0.90

182
CHAPTER INTERPRETER PATTERN

if tab.customer.is_member():

for item in tab.items:

item.price = item.price * 0.85

Now, look at the stark contrast between the previous code snippet and the definition of the same rules in a simple DSL for defining specials.

If tab contains 2 pizzas on Wednesdays cheapest one is free Every day from 17:00 to 19:00 drinks are less 10%

All items are less 15% for members

We will not yet look at the interpretation, which will come a little later. What I want you to notice is how much more clearly the DSL communicates how the rules of the specials will be applied. It is written in a way that will make sense to a business owner and will allow them to either affirm that it does indeed fit their intent or that a mistake was made and corrections will be needed. Ambiguity is reduced, and adding new rules or specials becomes more simple a win on both counts.

Advantages of DSLs

The level of understanding and communication with regards to the domain for which the DSL is created is greatly elevated. Domain experts can reason about the expression of both the problem and the solution in the DSL without their having a software engineering background. The development of business information systems can thus move from software developers to the domain experts. This results in richer, more accurate systems expressed in terms the experts being aided by the system can understand.

Disadvantages of DSLs

Before you head out and begin creating DSLs for every problem domain you can dream of, remember that there is a cost involved in learning a DSL, even if it makes sense for the domain experts. Someone new may understand what your DSL is expressing, but they will still need to learn the language implementation in order to be able to work with it. The deeper and richer your DSL, the more knowledge the domain expert needs to operate the language, which might have an impact on the number of people who will be

183
CHAPTER 13 INTERPRETER PATTERN

able to use and maintain the system. Keep in mind that the use of a DSL should aid the business, not hamper it, or it will discard the language in the long run, if it ever adopts it in the first place.

We have considered the pros and cons of having a DSL and decide to move forward with one for specifying rules for the restaurant specials.

On a macro level, you want to accomplish two tasks. First, you want to define

the language. Specifically, you want to define the semantics (meaning) and syntax (structure) of the language. Next, you want to write code that will be able to take the language as input and translate it into a form that a machine can understand and action.

Based on your conversations with customers, extract the things that are involved

in the process as well as the actions taken by every entity involved. After this, isolate common words or phrases that have a specific meaning in the domain under consideration. Use these three elements to construct a grammar, which you will discuss with the domain experts before implementing it.

Before we look at an example of this process in the context of the restaurant specials, I want you start thinking of yourself as a tool maker. Whenever you encounter a frustration or problem, you should begin thinking of how you would go about solving this problem and what tools you would code to make the process ten times easier or faster. This is the mindset that sets the best software developers and engineers apart from the rest of the field.

In our example case, we started by having a series of conversations with the restaurant owner (the domain expert) and identified the following rules for specials:

Members always get 15% off their total tab

During happy hour, which happens from 17:00 to 19:00 weekdays, all drinks are less 10%

Mondays are buy one get one free burger nights

Thursday are ’eat all you can’ ribs

Sunday nights after 18:00 buy three pizzas and get the cheapest one free when you buy 6 you get the two cheapest ones free and so on for every three additional pizzas

Then, we identified the things that were mentioned:

members tabs

happy hour

184
CHAPTER INTERPRETER PATTERN

drinks weekdays Mondays burgers Thursday ribs Sunday pizzas

We also identified the actions that could take place:

get 15% discount get 10% discount get one free

eat all you can

Finally, we recognized the key ideas as they relate to specials in the restaurant:

If a customer is of a certain type they always get a fixed % off their total tab At certain times all items of a specific type gets discounted with a fixed percentage

At certain times you get a second item of a certain type free if you were to buy one item of that type

Generalizing the elements in the key ideas results in the following rules for specials: If certain conditions are met certain items get a % discount

One way to formally express a grammar is by writing it in Extended Backus-Naur form (EBNF For more information on EBNF see: https://www.cs.umd.edu/class/fall2002/ cmsc214/Tutorial/ebnf.html). Our restaurant example in ENBF looks like this:

rule: "If ", conditions, " then ", item_type, " get ", discount

conditions: condition | conditions " and " conditions | conditions " or " conditions

condition: time_condition | item_condition | customer_condition

discount: number, "% discount" | "cheapest ", number, item_type " free"

185
CHAPTER 13 INTERPRETER PATTERN

time_condition: "today is ", day_of_week | "time is between ", time, " and ", time | "today is a week day" | "today not a week day"

day_of_week: "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday"

time: hours, ":", minutes

hours: hour_tens, hour_ones

hour_tens: "0" | "1"

hour_ones: digit

minutes: minute_tens, minute_ones

minute_tens: "0" | "1" | "2" | "3" | "4" | "5"

minute_ones: digit

item_condition: "item is a ", item_type | "there are ", number, " of ", item_type

item_type: "pizza" | "burger" | "drink" | "chips"

number: {digit}

digit: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

customer_condition: "customer is a ", customer_type customer_type: "member" | "non-member"

You have a set of terminal symbols that cannot be substituted with any pattern, as well as a set of non-terminal production rules that can be used to replace a non-terminal (a place holder symbol) with a sequence of terminals or with some recipe of other non-terminal patterns. Different ways in which a non-terminal can be substituted are separated by the pipe character | .

When you are creating an external DSL, a package such as PyParsing can be used

to translate strings written in a grammar like the one just seen to a data type that can be handled by Python code.

You have now seen an example of how you can move through the process of domain modeling by understanding the domain via conversations with domain experts, identifying key elements of the domain, codifying this understanding in some sort of domain model that can be verified by domain experts, and finally implementing the DSL as a final product of the process.

From this point on, we will be dealing with an internal DSL.

If we were to implement the special rules as an internal DSL, we would need to basically create one class per symbol.

186
CHAPTER 13 INTERPRETER PATTERN

We begin by creating just the stubs based on the grammar we defined earlier:

class Tab(object):

pass

class Item(object):

pass

class Customer(object):

pass

class Discount(object):

pass

class Rule(object):

pass

class CustomerType(object):

pass

class ItemType(object):

pass

class Conditions(object):

pass

class Condition(object):

pass

class TimeCondition(object):

pass

class DayOfWeek(object):

pass

class Time(object):

pass

class Hours(object):

pass

class HourTens(object):

pass

187
CHAPTER INTERPRETER PATTERN

class HourOnes(object):

pass

class Minutes(object):

pass

class MinuteTens(object):

pass

class MinuteOnes(object):

pass

class ItemCondition(object):

pass

class Number(object):

pass

class Digit(object):

pass

class CustomerCondition(object):

pass

Not all of these classes will be needed in the final design, and as such you should pay attention to the YAGNI principle; basically, the idea is that you should not build things you are not going to need. In this case, I have written them all out so you can see that we took every single thing mentioned in the process of defining the grammar and created a class for each.

Before we reduce the classes and create the final internal DSL, we are going to touch on the composite pattern, which will be useful when we start building our interpreter.

Composite Pattern

When you have a container with elements that could themselves be containers, you have a good case for using the composite pattern. Look at the EBNF version of the grammar we are developing for the restaurant; an item can be a combo that contains items.

188
CHAPTER INTERPRETER PATTERN

The composite pattern defines both composite (i.e., non-terminals) classes and leaf (i.e., terminals) classes that can be used to construct a composite component, such as a special rule.

class Leaf(object):

def __init__(self, *args, **kwargs):

pass

def component_function(self):

print("Leaf")

class Composite(object):

def __init__(self, *args, **kwargs):

self.children = []

def component_function(self): for child in children:

child.component_function()

def add(self, child):

self.children.append(child)

def remove(self, child):

self.children.remove(child)

In less dynamic languages, you would have also had to define a super class that would be inherited by both the Composite and the Leaf classes, but since Python uses duck typing, we are once again saved from creating unnecessary boilerplate code.

Internal DSL Implementation Using the Composite Pattern

Let us consider an implementation of the first rule for discounts from the restaurant in the beginning of this chapter, as some rudimentary internal DSL.

class Tab(object):

def __init__(self, customer):

self.items = [] self.discounts = [] self.customer = customer

189
CHAPTER 13 INTERPRETER PATTERN

def calculate_cost(self):

return sum(x.cost for x in self.items)

def calculate_discount(self):

return sum(x for x in self.discounts)

class Item(object):

def __init__(self, name, item_type, cost):

self.name = name

self.item_type = item_type

self.cost = cost

class ItemType(object):

def __init__(self, name):

self.name = name

class Customer(object):

def __init__(self, customer_type, name):

self.customer_type = customer_type self.name = name

def is_a(self, customer_type):

return self.customer_type == customer_type

class Discount(object):

def __init__(self, amount): self.amount = amount

class CustomerType(object):

def __init__(self, customer_type):

self.customer_type = customer_type

class Rule(object):

def __init__(self, tab):

self.tab = tab self.conditions = [] self.discounts = []

def add_condition(self, test_value):

self.conditions.append(test_value)

190
CHAPTER INTERPRETER PATTERN

def add_percentage_discount(self, item_type, percent):

if item_type == "any item":

f = lambda x: True

else:

f = lambda x: x.item_type == item_type items_to_discount = [item for item in self.tab.items if f(item)]

for item in items_to_discount:

discount = Discount(item.cost * (percent/100.0)) self.discounts.append(discount)

def apply(self):

if all(self.conditions):

return sum(x.amount for x in self.discounts)

return 0

if __name__ == "__main__":

member = CustomerType("Member") member_customer = Customer(member, "John") tab = Tab(member_customer)

pizza = ItemType("pizza") burger = ItemType("Burger") drink = ItemType("Drink")

tab.items.append(Item("Margarita", pizza, 15)) tab.items.append(Item("Cheddar Melt", burger, 6)) tab.items.append(Item("Latte", drink, 4))

rule = Rule(tab) rule.add_condition(tab.customer.is_a(member)) rule.add_percentage_discount("any item", 15)

tab.discounts.append( rule.apply()

)

191
CHAPTER INTERPRETER PATTERN

print(

"Calculated cost: {}\nDiscount applied: {}\n{}% Discount applied". format(

tab.calculate_cost(),

tab.calculate_discount(),

100 * tab.calculate_discount() / tab.calculate_cost()

)

)

Now that we have a single rule working, using objects that result in some form

of readable code, let s return to the implementation of the DSL using the composite pattern. Conditions can be a set of conjuncted conditions, a set of disjuncted conditions, or a single Boolean expression.

class AndConditions(object): def __init__(self):

self.conditions = []

def evaluate(self, tab):

return all(x.evaluate(tab) for x in self.conditions)

def add(self, condition):

self.conditions.append(condition)

def remove(self, condition):

self.conditions.remove(condition)

class OrConditions(object): def __init__(self):

self.conditions = []

def evaluate(self, tab):

return any(x.evaluate(tab) for x in self.conditions)

def add(self, condition):

self.conditions.append(condition)

def remove(self, condition):

self.conditions.remove(condition)

192
CHAPTER 13 INTERPRETER PATTERN

class Condition(object):

def __init__(self, condition_function):

self.test = condition_function

def evaluate(self, tab):

return self.test(tab)

class Discounts(object): def __init__(self):

self.children = []

def calculate(self, tab):

return sum(x.calculate(tab) for x in self.children)

def add(self, child):

self.children.append(child)

def remove(self, child):

self.children.remove(child)

class Discount(object):

def __init__(self, test_function, discount_function):

self.test = test_function

self.discount = discount_function

def calculate(self, tab):

return sum(self.discount(item) for item in tab.items if self. test(item))

class Rule(object):

def __init__(self, tab):

self.tab = tab

self.conditions = AndConditions() self.discounts = Discounts()

def add_conditions(self, conditions):

self.conditions.add(conditions)

def add_discount(self, test_function, discount_function):

discount = Discount(test_function, discount_function) self.discounts.add(discount)

193
CHAPTER INTERPRETER PATTERN

def apply(self):

if self.conditions.evaluate(self.tab):

return self.discounts.calculate(self.tab)

return 0

Implementing the Interpreter Pattern

Two types of people use software: those who are satisfied with the offering out of the box and those who are willing to tinker and tweak to make the software fit their needs more perfectly. The interpreter pattern is interesting only to the people in this second group since they are the people who will be willing to invest the time needed to learn how to use the DSL to make the software fit their needs.

Back in the restaurant, one owner will be happy with some basic templates for specials, like a two-for-one offer, while another restaurant owner will want to tweak and expand his offers.

We can now generalize the basic implementation for the DSL we did previously to create a way of solving these problems in the future.

Every expression type gets a class (as before), and every class has an interpret method. A class and an object to store the global context will also be needed. This context is passed to the interpret function of the next object in the interpretation stream. Assume that parsing already took place.

The interpreter recursively traverses the container object until the answer to the problem is reached.

class NonTerminal(object):

def __init__(self, expression):

self.expression = expression

def interpret(self):

self.expression.interpret()

class Terminal(object): def interpret(self):

pass

194
CHAPTER INTERPRETER PATTERN

Finally, the interpreter pattern can be used to decide if a certain tab qualifies for any specials. First, the tab and item classes are defined, followed by the classes needed for the grammar. Then, some sentences in the grammar are implemented and tested using test tabs. Note that we hard code the types in this example so the code can be run; usually, those are the types of things you would like to store in some file or database.

import datetime

class Rule(object):

def __init__(self, conditions, discounts):

self.conditions = conditions self.discounts = discounts

def evaluate(self, tab):

if self.conditions.evaluate(tab):

return self.discounts.calculate(tab)

return 0

class Conditions(object):

def __init__(self, expression):

self.expression = expression

def evaluate(self, tab):

return self.expression.evaluate(tab)

class And(object):

def __init__(self, expression1, expression2):

self.expression1 = expression1 self.expression2 = expression2

def evaluate(self, tab):

return self.expression1.evaluate(tab) and self.expression2. evaluate(tab)

class Or(object):

def __init__(self, expression1, expression2):

self.expression1 = expression1 self.expression2 = expression2

195
CHAPTER INTERPRETER PATTERN

def evaluate(self, tab):

return self.expression1.evaluate(tab) or self.expression2. evaluate(tab)

class PercentageDiscount(object):

def __init__(self, item_type, percentage):

self.item_type = item_type self.percentage = percentage

def calculate(self, tab):

return (sum([x.cost for x in tab.items if x.item_type == self.item_type]) * self.percentage) / 100

class CheapestFree(object):

def __init__(self, item_type):

self.item_type = item_type

def calculate(self, tab):

try:

return min([x.cost for x in tab.items if x.item_type == self.item_type])

except:

return 0

class TodayIs(object):

def __init__(self, day_of_week):

self.day_of_week = day_of_week

def evaluate(self, tab):

return datetime.datetime.today().weekday() == self.day_of_week.name

class TimeIsBetween(object):

def __init__(self, from_time, to_time):

self.from_time = from_time self.to_time = to_time

def evaluate(self, tab):

hour_now = datetime.datetime.today().hour minute_now = datetime.datetime.today().minute

196
CHAPTER INTERPRETER PATTERN

from_hour, from_minute = [int(x) for x in self.from_time. split(":")]

to_hour, to_minute = [int(x) for x in self.to_time.split(":")]

hour_in_range = from_hour <= hour_now < to_hour

begin_edge = hour_now == from_hour and minute_now > from_minute end_edge = hour_now == to_hour and minute_now < to_minute

return any(hour_in_range, begin_edge, end_edge)

class TodayIsAWeekDay(object):

def __init__(self):

pass

def evaluate(self, tab):

week_days = [

"Monday",

"Tuesday",

"Wednesday",

"Thursday",

"Friday",

]

return datetime.datetime.today().weekday() in week_days

class TodayIsAWeekedDay(object):

def __init__(self):

pass

def evaluate(self, tab):

weekend_days = [

"Saturday",

"Sunday",

]

return datetime.datetime.today().weekday() in weekend_days

class DayOfTheWeek(object): def __init__(self, name): self.name = name

197
CHAPTER 13 INTERPRETER PATTERN

class ItemIsA(object):

def __init__(self, item_type):

self.item_type = item_type

def evaluate(self, item):

return self.item_type == item.item_type

class NumberOfItemsOfType(object):

def __init__(self, number_of_items, item_type):

self.number = number_of_items self.item_type = item_type

def evaluate(self, tab):

return len([x for x in tab.items if x.item_type == self.item_type]) == self.number

class CustomerIsA(object):

def __init__(self, customer_type):

self.customer_type = customer_type

def evaluate(self, tab):

return tab.customer.customer_type == self.customer_type

class Tab(object):

def __init__(self, customer):

self.items = [] self.discounts = [] self.customer = customer

def calculate_cost(self):

return sum(x.cost for x in self.items)

def calculate_discount(self):

return sum(x for x in self.discounts)

class Item(object):

def __init__(self, name, item_type, cost):

self.name = name

self.item_type = item_type

self.cost = cost

198
CHAPTER INTERPRETER PATTERN

class ItemType(object):

def __init__(self, name):

self.name = name

class Customer(object):

def __init__(self, customer_type, name):

self.customer_type = customer_type self.name = name

class CustomerType(object):

def __init__(self, customer_type):

self.customer_type = customer_type

member = CustomerType("Member") pizza = ItemType("pizza")

burger = ItemType("Burger")

drink = ItemType("Drink")

monday = DayOfTheWeek("Monday")

def setup_demo_tab():

member_customer = Customer(member, "John") tab = Tab(member_customer)

tab.items.append(Item("Margarita", pizza, 15)) tab.items.append(Item("Cheddar Melt", burger, 6)) tab.items.append(Item("Hawaian", pizza, 12)) tab.items.append(Item("Latte", drink, 4)) tab.items.append(Item("Club", pizza, 17))

return tab

if __name__ == "__main__":

tab = setup_demo_tab()

rules = []

  • Members always get 15% off their total tab rules.append(

199
CHAPTER 13 INTERPRETER PATTERN

Rule(

CustomerIsA(member), PercentageDiscount("any_item", 15)

)

)

  • During happy hour, which happens from 17:00 to 19:00 weekdays, all drinks are less 10%

rules.append(

Rule(

And(TimeIsBetween("17:00", "19:00"), TodayIsAWeekDay()), PercentageDiscount(drink, 10)

)

)

  • Mondays are buy one get one free burger nights rules.append(

Rule(

And(TodayIs(monday), NumberOfItemsOfType(burger, 2)), CheapestFree(burger)

)

)

for rule in rules:

tab.discounts.append(rule.evaluate(tab))

print(

"Calculated cost: {}\nDiscount applied: {}\n".format(

tab.calculate_cost(),

tab.calculate_discount()

)

)

In this chapter, you saw two ways of interpreting a grammar and internal DSL. We looked at the composite pattern, then used it to interpret a rule for specials in a restaurant. Then, we built on that idea, together with the general interpreter pattern, to develop a more complete interpreter to test conditions and calculate what discounts apply. Essentially you worked through the whole process from getting business requirements to defining the problem in terms of a DSL and then implementing the DSL in code.

200
CHAPTER 13 INTERPRETER PATTERN

Parting Shots

Wherever you are on your programming journey, decide right now that you will do the hard work needed to become better. You do this by working on programming projects and doing programming challenges.

One such challenge by Jeff Bay can be found in Thought Works Anthology, published by The Pragmatic Programmers.

Attempt a non-trivial project (one that will require more than 1,000 lines of code to solve) by following these rules:

  1. Only one level of indentation is allowed per method, thus no if statements in loops or nesting.
  2. You re not allowed to use the keyword else.
  3. All primitive types and strings must be wrapped in objects specifically for the use they are put to.
  4. Collections are first class, and as such require their own objects.
  5. Don t abbreviate names. If names are too long, you are probably doing more than one thing in a method or class don t do that.
  6. Only one object operator allowed per line, so object.method() is ok, but object.attribute.method() is not.
  7. Keep your entities small (packages < 15 objects, classes < 50 lines, methods < 5 lines).
  8. No class may have more than two instance variables.
  9. You are not allowed to use getters, setters, or access properties directly.

Exercises

Follow the same thought process we used when defining a DSL for

the restaurant to define a DSL that you can implement in your own to-do list application. is DSL should allow your program to pick up cues like every Wednesday . . . and translate them to recurring tasks. Also add awareness of date and some other indicators of time relative to some other date or time.

201
CHAPTER INTERPRETER PATTERN

Implement your DSL from the previous exercise in Python so it can

interpret to-do items and extract the necessary characteristics.

Build a basic to-do list application using one of the popular Python

web frameworks and have it interpret to-do s using your DSL interpreter.

Find some interesting improvements or expansions to make the to-do list application something you can use on a daily basis.

202

'Practical Python Design Patterns' 카테고리의 다른 글

Observer Pattern  (0) 2023.03.29
Iterator Pattern  (0) 2023.03.29
Command Pattern  (0) 2023.03.28
Chain of Responsibility Pattern  (0) 2023.03.28
Proxy Pattern  (0) 2023.03.28

댓글