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.
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:
- Understand your domain.
- Model your domain.
- 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.
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.
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.
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
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:
- Only one level of indentation is allowed per method, thus no if statements in loops or nesting.
- You re not allowed to use the keyword else.
- All primitive types and strings must be wrapped in objects specifically for the use they are put to.
- Collections are first class, and as such require their own objects.
- 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.
- Only one object operator allowed per line, so object.method() is ok, but object.attribute.method() is not.
- Keep your entities small (packages < 15 objects, classes < 50 lines, methods < 5 lines).
- No class may have more than two instance variables.
- You are not allowed to use getters, setters, or access properties directly.
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.
'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 |
댓글