TechEd 2008 notes: “Design Patterns” in Dynamic Languages
These notes are probably fairly coherent. They’re definitely worth the read if you’re interested in design patterns and know something about Ruby, although I don’t know whether my notes would stand up very well if you don’t already know Ruby.
“Design Patterns” in Dynamic Languages
Neal Ford
Software Architect / Meme Wrangler
ThoughtWorks
Pattern solutions from weaker languages have more elegant solutions in dynamic languages
Ruby on Rails is running on IronRuby (as of RailsConf last week)
“Design Patterns: Making C++ Suck Less” by GoF
- Really good for two things
- Good definition for common kinds of problems we encounter
- Insomnia cure
- Examples in C++ and Smalltalk
- People mistakenly thought it was a recipe book
Patterns define common problems. Dynamic languages give you better tools to solve those problems.
Not “static vs. dynamic”. It’s “ceremony vs. essence”. Statically-typed languages like C# require a lot of ceremony to get things done; long distance between intent and result. Dynamic languages have less distance.
ITERATOR
Side note: Neal isn’t a big fan of UML, because it’s not technical enough for technical people, and too technical for non-technical people. This is the only talk where you’ll ever see him use UML.
Provide way to access elements of an aggregate sequentially without exposing the details.
container.each {|i| puts i}
Built-in iterator: each. Can also make an iterator object. C# has very similar things: IEnumerable/IEnumerator and foreach
COMMAND
Encapsulates a request as an object.
commands = []
(1..10).each do |i|
commands << proc { count += i }
end
Any language with closures already has Command built-in. There’s a trend here: languages are adding built-in patterns. C# has this too.
But: Command pattern may also support undoable operations.
class Command def initialize(do_proc, undo_proc)
Dynamic languages don’t preclude structure, but they let you delay it until necessary.
See blog post / rant: “Execution in the Kingdom of Nouns“. Command was essentially designed to give you a way to pass verbs around.
BUILDER
Separate construction process so the same process can create different representations.
Dynamicize with ad hoc combinations: method_missing. So instead of calling several methods in a row to set up different features, you can do things like parsing the method name: add_cd_and_dvd_and_turbo (”And” is a “bubble word” in DSL terms: it’s there for readability only.)
Used by Rails for find methods: find_by_column1_and_column2_…
INTERPRETER
Given a language, define a grammar and interpreter. (Tough to use as a recipe!)
Admission that the language you’re writing your app in isn’t expressive enough to get the job done.
Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
GoF book assumes you’re writing a parser, but an increasingly common solution is an internal DSL (e.g. LINQ).
recipe = Recipe.new "Spicy bread" recipe.add 200.grams.of "Flour" recipe.add 1.lb.of "Nutmeg"
Need to be able to add methods to numbers. No problem. Open class. More flexible than extension methods in C#, because you can change or remove methods.
class Numeric
def gram
self
end
alias_method :grams, :gram
def pound
self * 453.59237
end
alias_method :pounds, :pound
alias_method :lb, :pound
alias_method :lbs, :pound
def of ingredient
if ingredient.kind_of? String
ingredient = Ingredient.new(ingredient)
end
ingredient.quantity = self
ingredient
end
end
Side note: I might have done of by making a to_ingredient method on both String and Ingredient, and call that. Wonder if that would actually be better or not, though it would avoid the kind_of? call.
Semi-external DSL:
ingredient "flour" has Protein=11.5, Lipid=1.45, Sugars=1.12, Calcium=20, Sodium=0
Do a couple of substitutions to change that into a legal line of Ruby code, and then do instance_eval.
Internal DSL == embedded interpreter. Meets the intent of the GoF interpreter pattern better than lex/yacc, especially since it’s less prohibitive. Ruby on Rails is essentially a collection of internal DSLs.
FACTORY
Define interface for creating an object; let subclasses decide which class to instantiate.
Trivial in Ruby:
def create_from_factory(factory) factory.new end
DECORATOR
Attach additional responsibilities dynamically. Flexible alternative to subclassing.
module Decorator
def initialize(decorated)
@decorated = decorated
end
def method_missing(method, *args)
args.empty? ?
@decorated.send(method) :
@decorated.send(method, args)
end
end
Whip.new(Coffee.new).cost
Or, make the decorations modules:
module Whipped
def cost
super + 0.2
end
end
x = Coffee.new
x.extend Sprinkles
x.extend Whipped
Or make it even nicer:
Coffee.with Sprinkles, Whip
RECORDER
class Recorder
def initialize
@messages = []
end
def method_missing(method, *args, &block)
@messages << [method, args, block]
end
def play_back_to(obj)
@messages.each do |method, args, block|
obj.send(method, *args, &block)
end
end
end
Interesting nuance: what about methods that are already defined on Recorder, e.g. to_s? Anticipated in Ruby 1.8: there’s a class called BlankSlate.
ADAPTER
Convert one interface to another. Round hole / square peg.
class SquarePegAdapter
def radius
Math.sqrt(((@peg.width / 2) ** 2) * 2)
That’s the traditional adapter: a wrapper. But in Ruby, you could solve it a different way:
class SquarePeg
def radius
Math.sqrt(((width / 2) ** 2) * 2)
end
end
Can also add methods to a single instance instead of the entire class.
What if SquarePeg already had a radius method?
class SquarePeg
include InterfaceSwitching
def width
@width
end
def_interface :normal, :width
def width
@width / 3
end
def_interface :holes, :width
def initialize(width)
set_interface :normal
@width = width
end
end
Then you can use a with_interface method to adapt the class to a particular interface, yield self, then put itself back. Short-lived adapter.
DYNAMIC LANGUAGE PATTERNS
ARIDIFIER
Pragmatic Programmer: “Don’t Repeat Yourself.”
Ceremonious languages create floods. They create repetition you don’t even see because you’re so used to it. Essence languages allow aridification.
class Grade
class << self
def for_score_of(grade)
case grade
when 90..100: 'A'
when 80..90 : 'B'
when 70..80 : 'C'
when 60..70 : 'D'
when Integer: 'F'
when /['A-D]/, /[F]/ : grade
else raise "Not a grade: #{grade{"
end
end
end
end
def test_numerical_grades
for g in 90..100
assert_equal "A", Grade.for_score_of(g)
end
for g in 80..90
assert_equal "B", Grade.for_score_of(g)
end
Lots of repetition. Better:
TestCrades.class_eval do
grade_range = {
'A' => 90..100,
...
grade_range.each do |k, v|
method_name = ("test_" + k + "_letter_grade").to_sym
define_method method_name do
...
No duplication. You write the loop once, inside that call to define_method.
STATE
Door with states: Open and Closed. Model the states as mixins, and extend them. Problem: old methods don’t go away.
Solution: Mixology Ruby gem that adds unmix and mixin methods.
Dynamic languages use language facilities to create simpler solution. GoF solutions are all structural; that’s why they all have UML diagrams: the way to solve problems is to add structure. You can still do that in dynamic languages, but often don’t need to.
Understand patterns for what they are: descriptions of common problems. Don’t get caught up in implementation details. Implement solutions that are more elegant and take advantage of your tools.
“Simplexity”: taking complicated code and move it to another place; may add more complexity somewhere else, but simplifies other code. Languages with strong metaprogramming.
Q&A
Which of these patterns are implementable in IronRuby? — They’re running IronRuby against the Ruby language specs. Virtually all of these should run now.
Looks like cleverness for cleverness’ sake. — Compare to AOP or LINQ. Not everyone on your team has to understand how it works internally. But don’t go hog-wild with this stuff. Metaprogramming was 3.5 - 5% of his team’s Ruby code. Use metaprogramming surgically, not as a sledgehammer.
Some programmers don’t understand e.g. C++ templates. Is metaprogramming the same kind of problem? — No, because in C++, templates are in your face all the time; the complexity is hard to hide. In Ruby, it just looks like you’re adding keywords to the language.
Why not just write your own language on the DLR? — Some people probably will, but writing a good, expressive language is really hard, way harder than making a good framework.
At a company that doesn’t use any dynamic languages, how to decide which one to use? — Start using one to do something you already need to do; you’ll get a flavor for how it feels and how its concepts map to your view of the world. Start with infrastructure stuff. Start using rake, or writing tests with IronRuby, or do scripting.
Aren’t these languages dangerous because you don’t have static typing? — Yes. But: a compiler is a good verifier, but only for a very small surface area. Unit testing, with dynamic languages, is not optional. (Arguably, it’s not optional with static languages either.)
Can you create unit tests that cover all possible scenarios? — Sure. They go for 100% code coverage.
Does he know whether RSpec works in IronRuby? — Doesn’t know, but if Rails works, RSpec likely does.
June 3rd, 2008 at 11:05 pm
[…] use. E.g., “static” vs. “dynamic” languages. (I already noted this “ essence vs. ceremony“.) Something nasty and multithreaded would be better written in something like […]
June 9th, 2008 at 7:25 am
[…] White has posted very detailed notes from TechEd 2008: #1 ~ #2 ~ #3 ~ #4 ~ #5 ~ #6 ~ #7 ~ #8 ~ #9 ~ […]
June 12th, 2008 at 6:48 am
[…] Great for experimentation: you can change something’s return type and there’s a much better chance that everything will still compile (Roy would probably say there’s more essence and less ceremony) […]