Más contenido relacionado Similar a Dynamic Language Practices (20) Dynamic Language Practices1. Developer Practices for
Dynamic Languages
“Unlearning Java/C#”
Dr Paul King, Director
ASERT, Australia
paulk@asert.com.au
2. Topics
Introduction
• Design patterns
• Refactoring
• Polyglot programming
• SOLID principles
© ASERT 2006-2010
• Other topics
• More Info
ESDC 2010 - 2
3. Introduction …
• Developer practices
– Well understood and documented for
traditional languages like Java, C++ and C#
– But dynamic languages like Groovy, Ruby,
Python, Boo, JavaScript and others,
change the ground rules
© ASERT 2006-2010
– Many of the rules and patterns we have
been taught must be adapted or adjusted;
some no longer apply at all
4. ...Introduction...
• What does Immutability mean?
– When even constants can be changed
• What does encapsulation mean?
– When I can peek at internal state or
when using languages without state
• How can I devise tests
© ASERT 2006-2010
at development time?
– When my system can change in
unknown ways at runtime
• How can IDEs help me?
– If I no longer spoon feed static-type information to my
IDE, what level of support can it give me in terms of
code completion and error checking
5. … Introduction
• Traditional developer practice guidelines
– Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
(1995). Design Patterns: Elements of Reusable Object-
Oriented Software. Addison-Wesley.
– Martin Fowler (1999). Refactoring: Improving the Design of
Existing Code. Addison-Wesley.
– Joshua Bloch (2001). Effective Java Programming
© ASERT 2006-2010
Language Guide. Prentice Hall.
– Robert C Martin (2002), Agile Software Development,
Principles, Patterns, and Practices. Prentice Hall.
– Robert C Martin (2006), Agile Principles, Patterns, and
Practices in C#. Prentice Hall.
• In the dynamic language world, are the
guidelines in these books FACT or MYTH?
• But first let’s look at what we mean by
dynamic languages and dynamic typing
6. What do I mean by Dynamic Language?
• I prefer a flexible
definition
• One or more of:
– Dynamic typing
• Greater polymorphism
– Metaprogramming
© ASERT 2006-2010
• Allow language itself to be dynamically changed
• Allow hooks into object lifecycle and method calls
• Open classes/monkey patching
– Work with code as easily as data
• Closures
• Higher-order programming
– Escape hatches
• Hooks for polyglot programming
7. … Static vs Dynamic Typing …
• MYTH or TRUTH?
Static typing is just spoon feeding the
compiler/IDE. It represents the old-school
way of thinking and requires extra work
while providing no real value.
© ASERT 2006-2010
Static VS Dynamic
8. Static vs Dynamic Typing …
• Static: the type of each variable
(or expression) must be known
at compile time
© ASERT 2006-2010
dynamic advocates: like programming
wearing a straight-jacket?
Unnecessary complexity
9. …Static vs Dynamic Typing …
• Static Typing Pros
– Errors are often detected earlier and with better error
messages
– Code can sometimes be clearer – you don’t need to infer
the types to understand the code – especially when
revisiting the code later
© ASERT 2006-2010
– Safer because certain kinds of injection hacks don’t apply
– Code can be more declarative
– Better IDE support: refactoring, editing and other forms
of source processing support is often possible
– Better optimisations are often possible
– Often easier to understand a system from the outside
(“self-documenting” statically-typed APIs and interfaces)
– With generics support you can start to nail down even
complex cases
10. …Static vs Dynamic Typing …
• Dynamic: type information is known only
at runtime
© ASERT 2006-2010
static advocates: like tightrope walking
with no net?
11. … Static vs Dynamic Typing …
• Dynamic Typing Pros
– Speed development through duck-typing
and less boiler-plate code
– Clearer more concise code is easier to
read and maintain
– Allow more expressiveness through DSLs
© ASERT 2006-2010
– You should have comprehensive tests anyway, why not
cover off types as part of those tests
– Enforced healthy practices:
• Static language developers may get a false sense of
security and not design/test for runtime issues
• Less likely to neglect good documentation and/or good
coding conventions on the grounds that your static
types make everything “inherently” clear
12. …Static vs Dynamic Typing …
• Strong vs weak typing
– Strong: List<Integer> myList
– Weak: Object myList
• Type safety
– How is this provided if at all?
© ASERT 2006-2010
• Type inference
– Is this supported?
13. …Static and Dynamic Typing…
© ASERT 2006-2010
Source: http://www.slideshare.net/pcalcado/one-or-two-things-you-may-not-know-about-typesystems
15. Typing Approaches…
interface Duck {
def waddle()
def quack()
}
class DuckImpl implements Duck {
© ASERT 2006-2010
def waddle() { println "waddle" }
def quack() { println "quack" }
}
class Goose {
def waddle() { println "Goose waddle" }
def quack() { println "Goose quack" }
}
16. …Typing Approaches…
• Inheritance hierarchies
– Very clear intent but use sparingly
• Interface-oriented design
– Use if it adds clarity & your language supports it
– If you do use it, stick to fine-grained interfaces
• Dynamic interface-oriented design
© ASERT 2006-2010
Source: Rick DeNatale
– If your language doesn’t support it natively you © David Friel
can use a guard: is_a?, kind_of?, instanceof
• Chicken typing
– Use a guard: responds_to?, respondsTo
• Duck typing
– Use when flexibility is important but have appropriate tests in
place; e.g. you don’t want to violate the Liskov Substitution
Principal[15] by not considering a refused bequest[13].
• AKA roll your own type safety
17. …Typing Approaches
• Implicit vs Explicit interfaces
– Inheritance too restrictive?
– Duck-typing too flexible? Menu
set_sides()
Shape <<interface>> <<interface>> Rectangle
draw() Shape RegularPolygon draw()
draw() set_side() set_sides()
© ASERT 2006-2010
Rectangle Square
draw() draw()
set_sides() Rectangle Square EquilateralTriangle set_side()
draw() draw() draw()
set_sides() set_side() set_side()
Square Pistol
draw() draw()
set_sides()
I tend to use Explicit types
for major boundaries and EquilateralTriangle
implicit types internally. draw()
set_side()
Adapted from Interface-Oriented Design [2]
18. … Static vs Dynamic Typing …
• MYTH
Removing static typing always leads to
more concise and readable code.
© ASERT 2006-2010
X incorrect
19. … Static vs Dynamic Typing …
• An example interface Reversible {
def reverse()
}
class ReversibleString implements Reversible {
def reverse() { /* */ }
}
class ReversibleArray implements Reversible {
def reverse() { /* */ }
}
© ASERT 2006-2010
Reversible[] things = [
new ReversibleString(), new ReversibleArray()
]
for (i in 0..<things.size()) {
things[i].reverse()
}
def things = ["abc", [1, 2 ,3]]
def expected = ["cba", [3, 2, 1]]
assert things*.reverse() == expected
20. … Static vs Dynamic Typing ...
interface Reversible {
With dynamically def reverse()
typed languages, }
there is no need to class ReversibleString implements Reversible {
explicitly declare the def reverse() { /* */ }
}
types of variables or
the “protocols” class ReversibleArray implements Reversible {
def reverse() { /* */ }
observed by our }
© ASERT 2006-2010
objects:
Less code Reversible[] things = [
new ReversibleString(), new ReversibleArray()
Less declarative ]
Less IDE support for (i in 0..<things.size()) {
More testing things[i].reverse()
Less Robust? }
def things = ["abc", [1, 2 ,3]]
def expected = ["cba", [3, 2, 1]]
assert things*.reverse() == expected
21. … Static vs Dynamic Typing …
• MYTH
Dynamic typing means the IDE can’t
provide support for completion and early
syntax error checks.
© ASERT 2006-2010
X incorrect
22. … Static vs Dynamic Typing ...
• Consider Groovy in Intellij
• And Eclipse
© ASERT 2006-2010
Eclipse example: http://contraptionsforprogramming.blogspot.com/
23. Typing approaches and IDEs…
• Class A has a bit of duplication
class A {
def helper
def make() {
helper.invoke('create')
}
def get() {
© ASERT 2006-2010
helper.invoke('read')
}
def change() {
helper.invoke('update')
}
def remove() {
helper.invoke('delete')
}
}
24. … Typing approaches and IDEs …
• No problems, we can refactor out the dup
class B {
def helper
def make() {
invoke('create')
}
def get() {
© ASERT 2006-2010
invoke('read')
}
def change() {
invoke('update')
}
def remove() {
invoke('delete')
}
private invoke(cmd) {
helper.invoke(cmd)
}
}
25. … Typing approaches and IDEs …
• But we can do more using a dynamic
language by leveraging metaprogramming
class C {
def helper
def commands = [
make: 'create',
© ASERT 2006-2010
get: 'read',
change: 'update',
remove: 'delete'
]
def invokeMethod(String name, ignoredArgs) {
helper.invoke(commands[name])
}
}
• Which is a whole lot nicer?
• At the expense of IDE completion? … ...
26. … Typing approaches and IDEs …
class Dumper {
def name
def invokeMethod(String methodName, args) {
println "$name: called $methodName with $args"
}
}
© ASERT 2006-2010
for (x in [A, B, C]) {
def o = x.newInstance()
o.helper = new Dumper(name: "$x.name's helper")
o.make()
o.get()
o.change()
o.remove()
}
27. … Typing approaches and IDEs
• … At the expense of IDE completion?
© ASERT 2006-2010
But remember:
“clearly express intent”
28. … Static vs Dynamic Typing …
© ASERT 2006-2010
• MYTH
Static typing means runtime errors are a
thing of the past.
X incorrect
Source: http://www.slideshare.net/pcalcado/one-or-two-things-you-may-not-know-about-typesystems (phillip calçado)
29. … Static vs Dynamic Typing ...
• Consider Lift (based on Scala)
<lift:surround with="default" at="content">
<h2>Welcome to your project!</h2>
<p><lift:hellWorld.howdy /></p>
</lift:surround> Result: No error but
empty home page
© ASERT 2006-2010
<lift:surrond with="default" at="content">
<h2>Welcome to your project!</h2>
<p><lift:hellWorld.howdy /></p>
</lift:surround>
Source: http://zef.me/2371/when-scala-dsls-fail
30. Static and Dynamic Strong Typing
© ASERT 2006-2010
Source: http://www.slideshare.net/pcalcado/one-or-two-things-you-may-not-know-about-typesystems
31. Static vs Dynamic Typing
Static Dynamic
Syntax bugs
Optimisation
© ASERT 2006-2010
Arithmetic bugs
Logic bugs
approx same approx same
Resource bugs
Concurrency bugs
Power
Flexibility
32. Static vs Dynamic Typing Verdict
• MYTH or TRUTH?
Static typing is just spoon feeding the
compiler. It represents the old-school way
of thinking and requires extra work while
providing no real value.
© ASERT 2006-2010
...but not a total lie either...
...dynamic languages certainly assist
with removing duplication, clutter and
boilerplate code...
33. An open debate
© ASERT 2006-2010
Source: http://www.slideshare.net/pcalcado/one-or-two-things-you-may-not-know-about-typesystems
34. Topics
• Introduction
Design patterns
• Refactoring
• Polyglot programming
• SOLID principles
© ASERT 2006-2010
• Other topics
• More Info
35. Language features instead of Patterns
• So called "Design Patterns" are merely
hacks to overcome the limitations of your
language
– You call that a
language?
© ASERT 2006-2010
– This is a language
• "Design Patterns" are really anti-patterns
you must sometimes put up with because
your language is so archaic!
• In my superior language, that would be
built-in, simply a library, so easy, ...
36. Language features instead of Patterns
• So called "Design Patterns" are merely
hacks to overcome the limitations of your
language
– You call that a
language?
© ASERT 2006-2010
– This is a language
• "Design Patterns" are really anti-patterns
you must sometimes put up with because
your language is so archaic!
• In my superior language, that would be
built-in, simply a library, so easy, ...
37. Adapter Pattern…
class RoundPeg {
def radius
String toString() { "RoundPeg with radius $radius" }
}
class RoundHole {
def radius
def pegFits(peg) { peg.radius <= radius }
String toString() { "RoundHole with radius $radius" }
}
© ASERT 2006-2010
def pretty(hole, peg) {
if (hole.pegFits(peg)) println "$peg fits in $hole"
else println "$peg does not fit in $hole"
}
def hole = new RoundHole(radius:4.0)
(3..6).each { w -> pretty(hole, new RoundPeg(radius:w)) }
RoundPeg with radius 3 fits in RoundHole with radius 4.0
RoundPeg with radius 4 fits in RoundHole with radius 4.0
RoundPeg with radius 5 does not fit in RoundHole with radius 4.0
RoundPeg with radius 6 does not fit in RoundHole with radius 4.0
38. …Adapter Pattern…
class SquarePeg {
def width
String toString() { "SquarePeg with width $width" }
}
class SquarePegAdapter {
def peg
def getRadius() { Math.sqrt(((peg.width/2) ** 2)*2) }
String toString() {
"SquarePegAdapter with width $peg.width (and notional radius $radius)"
© ASERT 2006-2010
}
}
def hole = new RoundHole(radius:4.0)
(4..7).each { w ->
pretty(hole, new SquarePegAdapter(peg: new SquarePeg(width: w))) }
SquarePegAdapter with width 4 (and notional radius 2.8284271247461903)
fits in RoundHole with radius 4.0
SquarePegAdapter with width 5 (and notional radius 3.5355339059327378)
fits in RoundHole with radius 4.0
SquarePegAdapter with width 6 (and notional radius 4.242640687119285)
does not fit in RoundHole with radius 4.0
SquarePegAdapter with width 7 (and notional radius 4.949747468305833)
does not fit in RoundHole with radius 4.0
39. …Adapter Pattern
SquarePeg.metaClass.getRadius =
{ Math.sqrt(((delegate.width/2)**2)*2) }
(4..7).each { w -> pretty(hole, new SquarePeg(width:w)) }
© ASERT 2006-2010
Adapter Pattern
Do I create a whole new class
or just add the method I need
on the fly?
Consider the Pros and Cons!
SquarePeg with width 4 fits in RoundHole with radius 4.0
SquarePeg with width 5 fits in RoundHole with radius 4.0
SquarePeg with width 6 does not fit in RoundHole with radius 4.0
SquarePeg with width 7 does not fit in RoundHole with radius 4.0
Further reading: James Lyndsay, Agile is Groovy, Testing is Square
40. Adapter Pattern Verdict
• Dynamic languages can make it easier to
apply the adapter pattern to the extent that
its use may not even be apparent:
– Express intent more clearly and improves readability
– Aids refactoring
–
© ASERT 2006-2010
Can help with test creation
– Avoids class proliferation
• At the expense of class pollution?
– But you still need testing
41. Immutable Pattern...
• Java Immutable Class boilerplate
– As per Joshua Bloch // ...
@Override
Effective Java public boolean equals(Object obj) {
if (this == obj)
public final class Punter { return true;
private final String first; if (obj == null)
private final String last; return false;
if (getClass() != obj.getClass())
public String getFirst() { return false;
return first; Punter other = (Punter) obj;
} if (first == null) {
© ASERT 2006-2010
if (other.first != null)
public String getLast() { return false;
return last; } else if (!first.equals(other.first))
} return false;
if (last == null) {
@Override if (other.last != null)
public int hashCode() { return false;
final int prime = 31; } else if (!last.equals(other.last))
int result = 1; return false;
result = prime * result + ((first == null) return true;
? 0 : first.hashCode()); }
result = prime * result + ((last == null)
? 0 : last.hashCode()); @Override
return result; public String toString() {
} return "Punter(first:" + first
+ ", last:" + last + ")";
public Punter(String first, String last) { }
this.first = first;
this.last = last; }
}
// ...
QCON 2010 - 41
42. ...Immutable Pattern
@Immutable class Punter {
String first, last
© ASERT 2006-2010
}
QCON 2010 - 42
43. Visitor Pattern
abstract class Shape {}
class Rectangle extends Shape {
def x, y, width, height Visitor Pattern abstract class Shape {
Rectangle(x, y, width, height) {
def accept(Closure yield) { yield(this) }
} without closures
this.x = x; this.y = y; this.width = width; this.height = height
}
def union(rect) {
if (!rect) return this
def minx = [rect.x, x].min()
with closures class Rectangle extends Shape {
def maxx = [rect.x + width, x + width].max()
def miny = [rect.y, y].min()
def maxy = [rect.y + height, y + height].max() def x, y, w, h
new Rectangle(minx, miny, maxx - minx, maxy - miny)
} def bounds() { this }
def accept(visitor) { def union(rect) {
visitor.visit_rectangle(this)
} if (!rect) return this
}
def minx = [rect.x, x].min()
class Line extends Shape {
def x1, y1, x2, y2 def maxx = [rect.x + w, x + w].max()
Line(x1, y1, x2, y2) { def miny = [rect.y, y].min()
this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2
} def maxy = [rect.y + h, y + h].max()
def accept(visitor) { new Rectangle(x:minx, y:miny, w:maxx - minx, h:maxy - miny)
visitor.visit_line(this)
} }
© ASERT 2006-2010
}
}
class Group extends Shape {
def shapes = []
def add(shape) { shapes += shape } class Line extends Shape {
def remove(shape) { shapes -= shape } def x1, y1, x2, y2
def accept(visitor) { def bounds() {
visitor.visit_group(this)
} new Rectangle(x:x1, y:y1, w:x2-y1, h:x2-y2)
}
}
class BoundingRectangleVisitor {
def bounds }
def visit_rectangle(rectangle) {
if (bounds)
bounds = bounds.union(rectangle) class Group {
else
bounds = rectangle def shapes = []
}
def leftShift(shape) { shapes += shape }
def visit_line(line) {
def line_bounds = new Rectangle(line.x1, line.y1, line.x2 - line.y1, line.x2 - line.y2) def accept(Closure yield) { shapes.each{it.accept(yield)} }
if (bounds)
bounds = bounds.union(line_bounds) }
else
bounds = line_bounds
}
def group = new Group()
def visit_group(group) {
group.shapes.each {shape -> shape.accept(this) } group << new Rectangle(x:100, y:40, w:10, h:5)
}
} group << new Rectangle(x:100, y:70, w:10, h:5)
def group = new Group() group << new Line(x1:90, y1:30, x2:60, y2:5)
group.add(new Rectangle(100, 40, 10, 5))
group.add(new Rectangle(100, 70, 10, 5)) def bounds
group.add(new Line(90, 30, 60, 5))
def visitor = new BoundingRectangleVisitor() group.accept{ bounds = it.bounds().union(bounds) }
group.accept(visitor)
bounding_box = visitor.bounds println bounds.dump()
println bounding_box.dump()
See also Ruby Visitor
44. Visitor Pattern Verdict
• Dynamic languages can make it easier to
apply the visitor pattern to the extent that
its use may not even be apparent:
– Express intent more clearly and improves readability
– Aids refactoring
–
© ASERT 2006-2010
Avoids class proliferation
– But you still need testing
46. Language features instead of Patterns…
interface Calc {
def execute(n, m) Strategy Pattern
}
with interfaces
class CalcByMult implements Calc { with closures
def execute(n, m) { n * m }
}
def multiplicationStrategies = [
class CalcByManyAdds implements Calc {
def execute(n, m) {
{ n, m -> n * m },
def result = 0 { n, m ->
n.times { def total = 0; n.times{ total += m }; total },
result += m { n, m -> ([m] * n).sum() }
} ]
© ASERT 2006-2010
return result
}
}
def sampleData = [
[3, 4, 12],
def sampleData = [ [5, -5, -25]
[3, 4, 12], ]
[5, -5, -25]
] sampleData.each{ data ->
Calc[] multiplicationStrategies = [
multiplicationStrategies.each{ calc ->
new CalcByMult(), assert data[2] == calc(data[0], data[1])
new CalcByManyAdds() }
] }
sampleData.each {data ->
multiplicationStrategies.each {calc ->
assert data[2] == calc.execute(data[0], data[1])
}
}
47. Strategy Pattern Verdict
• Dynamic languages can make it easier to
apply the strategy pattern to the extent
that its use may not even be apparent:
– Express intent more clearly and improves readability
– Closures open up whole new possibilities for solving
problems
© ASERT 2006-2010
– Aids refactoring
– Can help with test creation
– Avoids class proliferation
– But you still need testing
48. Builder Pattern: MarkupBuilder…
• Builder pattern from the GoF at the syntax-level
• Represents easily any nested tree-structured data
import groovy.xml.* • Create new builder
def b = new MarkupBuilder()
b.html { • Call pretended methods
© ASERT 2006-2010
head { title 'Hello' } (html, head, ...)
body { • Arguments are Closures
ul {
for (count in 1..5) { • Builder code looks very
li "world $count" declarative but is ordinary
} } } } Groovy program code and
can contain any kind of
NodeBuilder, DomBuilder, logic
SwingBuilder, AntBuilder, …
49. ...Builder Pattern: MarkupBuilder
<html>
<head>
import groovy.xml.* <title>Hello</title>
def b = new MarkupBuilder() </head>
b.html { <body>
© ASERT 2006-2010
head { title 'Hello' } <ul>
body { <li>world 1</li>
ul { <li>world 2</li>
for (count in 1..5) { <li>world 3</li>
li "world $count" <li>world 4</li>
} } } } <li>world 5</li>
</ul>
</body>
</html>
50. Builder Pattern: SwingBuilder
import java.awt.FlowLayout
builder = new groovy.swing.SwingBuilder()
langs = ["Groovy", "Ruby", "Python", "Pnuts"]
gui = builder.frame(size: [290, 100],
title: 'Swinging with Groovy!’) {
panel(layout: new FlowLayout()) {
panel(layout: new FlowLayout()) {
for (lang in langs) {
© ASERT 2006-2010
checkBox(text: lang)
}
}
button(text: 'Groovy Button', actionPerformed: {
builder.optionPane(message: 'Indubitably Groovy!').
createDialog(null, 'Zen Message').show()
})
button(text: 'Groovy Quit',
actionPerformed: {System.exit(0)})
}
}
gui.show()
Source: http://www.ibm.com/developerworks/java/library/j-pg04125/
51. Builder Pattern: JavaFX Script
Frame {
title: "Hello World F3"
width: 200
content: Label {
text: "Hello World"
© ASERT 2006-2010
}
visible: true
}
52. Builder Pattern: Cheri::Swing
# requires JRuby
require 'rubygems'
© ASERT 2006-2010
require 'cheri/swing'
include Cheri::Swing
@frame = swing.frame('Hello') {
size 500,200
flow_layout
on_window_closing {|event| @frame.dispose}
button('Hit me') {
on_click { puts 'button clicked' }
}
}
@frame.show
53. Builder Pattern: AntBuilder
def ant = new AntBuilder()
ant.echo("hello") // let's just call one task
// create a block of Ant using the builder pattern
ant.sequential {
myDir = "target/test/"
mkdir(dir: myDir)
© ASERT 2006-2010
copy(todir: myDir) {
fileset(dir: "src/test") {
include(name: "**/*.groovy")
}
}
echo("done")
}
// now let's do some normal Groovy again
file = new File("target/test/AntTest.groovy")
assert file.exists()
54. Builder Pattern Verdict
• The builder pattern in combination with
dynamic languages helps me:
– Express intent more clearly and improves readability
– Aids refactoring
– Can help with test creation
– Tests are still important
© ASERT 2006-2010
55. Delegation Pattern ...
• Traditional approach to creating a class that is an
extension of another class is to use inheritance
– Clearest intent & simplest, clearest code for simple cases
class Person {
private name, age
Person(name, age) {
this.name = name
this.age = age
© ASERT 2006-2010
}
def haveBirthday() { age++ }
String toString() { "$name is $age years old" }
}
class StaffMemberUsingInheritance extends Person {
private salary
StaffMemberUsingInheritance(name, age, salary) {
super(name, age)
this.salary = salary
}
String toString() {
super.toString() + " and has a salary of $salary"
}
}
56. … Delegation Pattern ...
• Most common alternative is to use delegation
– Intention less clear (can be helped with interfaces)
– Overcomes multiple inheritance issues & inheritance abuse
class StaffMemberUsingDelegation {
private delegate
private salary
StaffMemberUsingDelegation(name, age, salary) {
© ASERT 2006-2010
delegate = new Person(name, age)
this.salary = salary
}
def haveBirthday() {
delegate.haveBirthday()
}
String toString() {
delegate.toString() + " and has a salary of $salary"
}
}
57. … Delegation Pattern …
• Downside of delegation is maintenance issues
– Refactoring overhead if we change the base class
– Meta-programming allows us to achieve inheritance
like behavior by intercepting missing method calls
(invokeMethod or method_missing)
– You could take this further with Groovy using named
© ASERT 2006-2010
parameters rather than the traditional positional
parameters shown here (future versions of Ruby may
have this too)
58. … Delegation Pattern …
class StaffMemberUsingMOP {
private delegate
private salary
StaffMemberUsingMOP(name, age, salary) {
delegate = new Person(name, age)
this.salary = salary
}
def invokeMethod(String name, args) {
delegate.invokeMethod name, args
}
© ASERT 2006-2010
String toString() {
delegate.toString() + " and has a salary of $salary"
}
}
def p1 = new StaffMemberUsingInheritance("Tom", 20, 1000)
def p2 = new StaffMemberUsingDelegation("Dick", 25, 1100)
def p3 = new StaffMemberUsingMOP("Harry", 30, 1200)
p1.haveBirthday()
println p1
p2.haveBirthday() Tom is 21 years old and has a salary of 1000
println p2 Dick is 26 years old and has a salary of 1100
p3.haveBirthday()
Harry is 31 years old and has a salary of 1200
println p3
59. … Delegation Pattern
• Going Further
–The example shown (on the previous slide) codes the
delegate directly but both Groovy and Ruby let you
encapsulate the delegation pattern as a library:
• Groovy: Delegator, Injecto; Ruby: forwardable, delegate
–But only if I don’t want to add logic as I delegate
• E.g. If I wanted to make haveBirthday() increment salary
© ASERT 2006-2010
class StaffMemberUsingLibrary {
private salary
private person
StaffMemberUsingLibrary(name, age, salary) {
person = new Person(name, age)
this.salary = salary
def delegator = new Delegator(StaffMemberUsingLibrary, person)
delegator.delegate haveBirthday
}
String toString() {
person.toString() + " and has a salary of $salary"
}
}
60. Better Design Patterns: Delegate…
public Date getWhen() {
import java.util.Date;
return when;
}
public class Event {
private String title;
public void setWhen(Date when) {
private String url;
this.when = when;
private Date when;
}
public String getUrl() {
public boolean before(Date other) {
return url;
return when.before(other);
}
© ASERT 2006-2010
}
public void setUrl(String url) {
public void setTime(long time) {
this.url = url;
when.setTime(time);
}
}
public String getTitle() {
public long getTime() {
return title;
return when.getTime();
}
}
public void setTitle(String title) {
public boolean after(Date other) {
this.title = title;
return when.after(other);
}
}
// ...
// ...
QCON 2010 - 60
61. …Better Design Patterns: Delegate…
public Date getWhen() {
import java.util.Date;
return when;
boilerplate }
public class Event {
private String title;
public void setWhen(Date when) {
private String url;
this.when = when;
private Date when;
}
public String getUrl() {
public boolean before(Date other) {
return url;
return when.before(other);
}
© ASERT 2006-2010
}
public void setUrl(String url) {
public void setTime(long time) {
this.url = url;
when.setTime(time);
}
}
public String getTitle() {
public long getTime() {
return title;
return when.getTime();
}
}
public void setTitle(String title) {
public boolean after(Date other) {
this.title = title;
return when.after(other);
}
}
// ...
// ...
QCON 2010 - 61
62. …Better Design Patterns: Delegate
class Event {
String title, url
@Delegate Date when
}
© ASERT 2006-2010
def gr8conf = new Event(title: "GR8 Conference",
url: "http://www.gr8conf.org",
when: Date.parse("yyyy/MM/dd", "2009/05/18"))
def javaOne = new Event(title: "JavaOne",
url: "http://java.sun.com/javaone/",
when: Date.parse("yyyy/MM/dd", "2009/06/02"))
assert gr8conf.before(javaOne.when)
QCON 2010 - 62
63. Delegation Pattern Verdict
• The delegation pattern can be expressed
more succinctly with dynamic languages:
– Express intent more clearly and improves readability
– Aids refactoring
– But don’t forget the testing implications
© ASERT 2006-2010
64. Singleton Pattern…
• Pattern Intent • Static language discussion
points
– Ensure that only one – Need exactly one instance of a class
instance of a class is and a well-known controlled access
created point
• Allows for lazy creation of instance
– Provide a global point of – More flexible than static class
access to the object variables and methods alone
• Permits refinement of operations and
– Allow multiple instances representation through subclassing
© ASERT 2006-2010
in the future without – Reduces name space clutter
affecting a singleton • Compared to using static approach
– Multi-threading implications
class's clients
– Serializable implications
• need to have readResolve() method to
avoid spurious copies
– Garbage collection implications
• May need "sticky" static self-reference
– Need to be careful subclassing
• Parent may already create instance or be
final or constructor may be hidden
65. …Singleton Pattern…
• The details quickly get messy …
public final class Singleton {
private static final class SingletonHolder {
static final Singleton singleton = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.singleton;
© ASERT 2006-2010
}
}
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
// Exists only to thwart instantiation.
}
private Object readResolve() {
return INSTANCE;
}
}
66. …Singleton Pattern…
• State of the art approach in Java?
– Use an IoC framework, e.g. Spring or Guice
import com.google.inject.*
@ImplementedBy(CalculatorImpl)
interface Calculator {
def add(a, b)
}
© ASERT 2006-2010
@Singleton
class CalculatorImpl implements Calculator {
private total = 0
def add(a, b) { total++; a + b }
def getTotalCalculations() { 'Total Calculations: ' + total }
String toString() { 'Calc: ' + hashCode()}
}
class Client {
@Inject Calculator calc
// ...
}
def injector = Guice.createInjector()
67. …Singleton Pattern…
• But it is easy using meta-programming
– Old style
class Calculator {
private total = 0
def add(a, b) { total++; a + b }
© ASERT 2006-2010
def getTotalCalculations() { 'Total Calculations: ' + total }
String toString() { 'Calc: ' + hashCode()}
}
class CalculatorMetaClass extends MetaClassImpl {
private final static INSTANCE = new Calculator()
CalculatorMetaClass() { super(Calculator) }
def invokeConstructor(Object[] arguments) { return INSTANCE }
}
def registry = GroovySystem.metaClassRegistry
registry.setMetaClass(Calculator, new CalculatorMetaClass())
68. …Singleton Pattern…
• But it is easy using meta-programming
class Calculator {
def total = 0
def add(a, b) { total++; a + b }
}
def INSTANCE = new Calculator()
© ASERT 2006-2010
Calculator.metaClass.constructor = { -> INSTANCE }
def c1 = new Calculator()
def c2 = new Calculator()
assert c1.add(1, 2) == 3
assert c2.add(3, 4) == 7
assert c1.is(c2)
assert [c1, c2].total == [2, 2]
69. …Singleton Pattern…
• Or annotations
@Singleton(lazy=true)
class X {
def getHello () {
"Hello, World!"
© ASERT 2006-2010
}
}
println X.instance.hello
70. …Singleton Pattern…
• And again with Ruby
class Aardvark class Aardvark
private_class_method :new private_class_method :new
@@instance = new def Aardvark.instance
def Aardvark.instance @@instance = new if not @@instance
@@instance @@instance
end end
end end
© ASERT 2006-2010
module ThreadSafeSingleton
def self.append_features(clazz)
require 'thread'
clazz.module_eval {
private_class_method :new
@instance_mutex = Mutex.new
def self.instance
@instance_mutex.synchronize {
@instance = new if not (@instance)
@instance
}
end
}
end
end Source: http://c2.com/cgi/wiki?RubySingleton
71. …Singleton Pattern
• Or for Python
– Classic class version (pre 2.2)
class Borg:
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
– Non-classic class version
© ASERT 2006-2010
class Singleton (object):
instance = None
def __new__(cls, *args, **kargs):
if cls.instance is None:
cls.instance = object.__new__(cls, *args, **kargs)
return cls.instance
# Usage
mySingleton1 = Singleton()
mySingleton2 = Singleton()
assert mySingleton1 is mySingleton2
Source: [10] and wikipedia
72. Singleton Pattern Verdict
• The singleton pattern can be expressed in
better ways with dynamic languages:
– Express intent more clearly and improves readability
– Aids refactoring
– But don’t forgot testing implications
© ASERT 2006-2010
73. Pattern Summary
• Patterns can be replaced by language
features and libraries
© ASERT 2006-2010
• So patterns aren’t important any more!
...
74. Topics
• Introduction
• Design patterns
Refactoring
• Polyglot programming
• SOLID principles
© ASERT 2006-2010
• Other topics
• More Info
75. Refactoring Refactoring
• Out with the Old
– Some refactorings no longer make sense
• In with the New
– There are some new refactorings
• Times … they are a changin’
© ASERT 2006-2010
– Some refactorings are done differently
76. Encapsulate Downcast Refactoring
• Description
– Context: A method returns an object that
needs to be downcasted by its callers
– Solution: Move the downcast to within the method
• Is there a point in a dynamic language?
– Maybe but not usually
© ASERT 2006-2010
// Before refactoring
Object lastReading() {
return readings.lastElement()
}
// After refactoring
Reading lastReading() {
return (Reading) readings.lastElement()
}
77. Introduce Generics Refactoring
• Description
– Context: Casting is a runtime hack that allows
JVM to clean up a mess caused by a compiler
that couldn’t infer intent
– Solution: Use Generics to reveal intent to compiler
• Is there a point in a dynamic language?
© ASERT 2006-2010
– Maybe but not usually
// Traditional Java style
List myIntList = new LinkedList()
myIntList.add(new Integer(0))
Integer result = (Integer) myIntList.iterator().next()
// Java generified style
List<Integer> myIntList2 = new LinkedList<Integer>()
myIntList2.add(new Integer(0))
Integer result2 = myIntList2.iterator().next()
// Groovier style
def myIntList3 = [0]
def result3 = myIntList3.iterator().next()
78. Enabling a functional style …
• Consider the Maximum Segment Sum
(MSS) problem
– Take a list of integers; the MSS is the maximum of the sums of
any number of adjacent integers
• Imperative solution:
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
© ASERT 2006-2010
def size = numbers.size()
def max = null
(0..<size).each { from ->
(from..<size).each { to ->
def sum = numbers[from..to].sum()
if (max == null || sum > max) max = sum
}
}
println "Maximum Segment Sum of $numbers is $max"
79. … Enabling a functional style …
• A first attempt at a more functional style:
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
© ASERT 2006-2010
def size = numbers.size()
def max = [0..<size, 0..<size].combinations().collect{
numbers[it[0]..it[1]].sum()
}.max()
println "Maximum Segment Sum of $numbers is $max"
80. … Enabling a functional style …
• An even more functional style
– A known solution using functional composition:
mss = max º sum* º (flatten º tails* º inits)
– Where inits and tails are defined as follows:
© ASERT 2006-2010
letters = ['a', 'b', 'c', 'd']
assert letters.inits() == [ assert letters.tails() == [
['a'], ['d'],
['a', 'b'], ['c', 'd'],
['a', 'b', 'c'], ['b', 'c', 'd'],
['a', 'b', 'c', 'd'] ['a', 'b', 'c', 'd']
] ]
81. … Enabling a functional style
• An even more functional style
mss = max º sum* º (flatten º tails* º inits)
def segs = { it.inits()*.tails().sum() }
def solve = { segs(it)*.sum().max() }
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
© ASERT 2006-2010
println "Maximum Segment Sum of $numbers is ${solve numbers}"
Notes:
– sum() is one-level flatten in Groovy, flatten() is recursive
– Metaprogramming allowed us to enhance all Lists
List.metaClass {
inits{ (0..<delegate.size()).collect{ delegate[0..it] } }
tails{ delegate.reverse().inits() }
}
Source: http://hamletdarcy.blogspot.com/2008/07/groovy-vs-f-showdown-side-by-side.html
82. Refactoring recipes with a curry base
• Static: Replace parameter with method
– Refactoring [13]: Chapter 10
• Context
– An object invokes a method, then passes the result as
a parameter for a method. The receiver can also
invoke this method.
© ASERT 2006-2010
• Solution
– Remove the parameter and let the receiver invoke the
method.
• Dynamic solution
– Partial Application: Currying
83. Replace parameter with method …
class Order {
private int quantity, itemPrice Let's explore the
Order(q, p) {quantity = q; itemPrice = p}
traditional refactoring
double getPrice() {
int basePrice = quantity * itemPrice
int discountLevel
if (quantity > 100) discountLevel = 2
else discountLevel = 1
© ASERT 2006-2010
double finalPrice = discountedPrice(basePrice, discountLevel)
return finalPrice
}
private double discountedPrice(int basePrice, int discountLevel) {
if (discountLevel == 2) return basePrice * 0.8
return basePrice * 0.9
}
}
println new Order(120, 5).price // => 480.0
84. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
int basePrice = quantity * itemPrice
int discountLevel
if (quantity > 100) discountLevel = 2
else discountLevel = 1
© ASERT 2006-2010
double finalPrice = discountedPrice(basePrice, discountLevel)
return finalPrice
}
private double discountedPrice(int basePrice, int discountLevel) {
if (discountLevel == 2) return basePrice * 0.8
return basePrice * 0.9
}
}
println new Order(120, 5).price // => 480.0
85. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
int basePrice = quantity * itemPrice
double finalPrice = discountedPrice(basePrice)
return finalPrice
}
© ASERT 2006-2010
private double discountedPrice(int basePrice) {
if (getDiscountLevel() == 2) return basePrice * 0.8
return basePrice * 0.9
}
private int getDiscountLevel() {
if (quantity > 100) return 2
return 1
}
}
println new Order(120, 5).price // => 480.0
86. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
int basePrice = quantity * itemPrice
double finalPrice = discountedPrice(basePrice)
return finalPrice
}
© ASERT 2006-2010
private double discountedPrice(int basePrice) {
if (getDiscountLevel() == 2) return basePrice * 0.8
return basePrice * 0.9
}
private int getDiscountLevel() {
if (quantity > 100) return 2
return 1
}
}
println new Order(120, 5).price // => 480.0
87. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
return discountedPrice(getBasePrice())
}
private double discountedPrice(int basePrice) {
© ASERT 2006-2010
if (getDiscountLevel() == 2) return basePrice * 0.8
return basePrice * 0.9
}
private int getBasePrice() {
quantity * itemPrice
}
private int getDiscountLevel() {
if (quantity > 100) return 2
return 1
}
}
println new Order(120, 5).price // => 480.0
88. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
return discountedPrice(getBasePrice())
}
private double discountedPrice(int basePrice) {
© ASERT 2006-2010
if (getDiscountLevel() == 2) return basePrice * 0.8
return basePrice * 0.9
}
private int getBasePrice() {
quantity * itemPrice
}
private int getDiscountLevel() {
if (quantity > 100) return 2
return 1
}
}
println new Order(120, 5).price // => 480.0
89. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
return discountedPrice()
}
private double discountedPrice() {
© ASERT 2006-2010
if (getDiscountLevel() == 2) return getBasePrice() * 0.8
return getBasePrice() * 0.9
}
private int getBasePrice() {
quantity * itemPrice
}
private int getDiscountLevel() {
if (quantity > 100) return 2
return 1
}
}
println new Order(120, 5).price // => 480.0
90. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
return discountedPrice()
}
private double discountedPrice() {
© ASERT 2006-2010
if (getDiscountLevel() == 2) return getBasePrice() * 0.8
return getBasePrice() * 0.9
}
private int getBasePrice() {
quantity * itemPrice
}
private int getDiscountLevel() {
if (quantity > 100) return 2
return 1
}
}
println new Order(120, 5).price // => 480.0
91. … Replace parameter with method
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
if (getDiscountLevel() == 2) return getBasePrice() * 0.8
return getBasePrice() * 0.9
}
© ASERT 2006-2010
private getBasePrice() {
quantity * itemPrice
}
private getDiscountLevel() {
if (quantity > 100) return 2
Note the now small
return 1 parameter lists
}
}
println new Order(120, 5).price // => 480.0
92. Some functional style …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
def discountedPrice = { basePrice, discountLevel ->
discountLevel == 2 ? basePrice * 0.8 : basePrice * 0.9 }
def price = {
int basePrice = quantity * itemPrice
© ASERT 2006-2010
def discountLevel = (quantity > 100) ? 2 : 1
discountedPrice(basePrice, discountLevel) }
}
println new Order(120, 5).price() // => 480.0
Traditional refactoring
still applicable if we used
closures rather than methods...
93. … Some functional style …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
def basePrice = { quantity * itemPrice }
def discountLevel = { quantity > 100 ? 2 : 1 }
def price = {
© ASERT 2006-2010
discountLevel() == 2 ? basePrice() * 0.8 : basePrice() * 0.9 }
}
println new Order(120, 5).price() // => 480.0
... as we see here
94. … Some functional style …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
def basePrice = { quantity * itemPrice }
def discountLevel = { quantity > 100 ? 2 : 1 }
def discountedPrice = { basePrice, discountLevel ->
© ASERT 2006-2010
discountLevel == 2 ? basePrice * 0.8 : basePrice * 0.9 }
def price = {
discountedPrice.curry(basePrice()).curry(discountLevel()).call() }
}
println new Order(120, 5).price() // => 480.0
But we can also use currying
95. … Some functional style
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
def basePrice = { quantity * itemPrice }
def discountLevel = { quantity > 100 ? 2 : 1 }
def discountedPrice(basePrice, discountLevel) {
© ASERT 2006-2010
discountLevel == 2 ? basePrice * 0.8 : basePrice * 0.9
}
def price = {
this.&discountedPrice.curry(basePrice()).curry(discountLevel()).call()
}
}
println new Order(120, 5).price() // => 480.0
We can also use currying with methods
96. Closure Refactoring …
• Complex code involving closures
// Before refactoring
def phrase = "The quick brown fox jumps over the lazy dog"
def result = phrase.toLowerCase().toList().
findAll{ it in "aeiou".toList() }. // like WHERE ...
© ASERT 2006-2010
groupBy{ it }. // like GROUP BY ...
findAll{ it.value.size() > 1 }. // like HAVING ...
sort{ it.key }.reverse(). // like ORDER BY ...
collect{ "$it.key:${it.value.size()}" }.
join(", ")
println result
97. … Closure Refactoring …
• Possible Refactoring
// Refactored helper closures
def lowercaseLetters = phrase.toLowerCase()
def vowels = { it in "aeiou".toList() }
def occursMoreThanOnce = { it.value.size() > 1 }
def byReverseKey = { a, b -> b.key <=> a.key }
def self = { it }
© ASERT 2006-2010
def entriesAsPrettyString = { "$it.key:${it.value.size()}" }
def withCommaDelimiter = ", "
// Refactored main closure
println lowercaseLetters.
findAll(vowels).
groupBy(self).
findAll(occursMoreThanOnce).
sort(byReverseKey).
collect(entriesAsPrettyString).
join(withCommaDelimiter)
98. … Closure Refactoring
# Add group_by to the Array class
class Array
def group_by
group_hash = {}
uniq.each do |e|
group_hash[e] = select { |i| i == e }.size
end
group_hash
end
end
# Before refactoring
phrase = "The quick brown fox jumps over the lazy dog"
© ASERT 2006-2010
puts phrase.downcase.
scan(/[aeiou]/). # like WHERE ...
group_by. # like GROUP BY ...
select { |key, value| value > 1 }. # like HAVING ...
sort.reverse. # like ORDER BY ... DESC
collect{ |key, value| "#{key}:#{value}" }.join(', ')
# Refactored version
lowercase_letters = phrase.downcase
vowels = /[aeiou]/
occurs_more_than_once = lambda { |key,value| value > 1 }
entries_as_pretty_string = lambda { |key, value| "#{key}:#{value}" }
puts lowercase_letters.
scan(vowels).
group_by.
select(&occurs_more_than_once).
sort.reverse.
collect(&entries_as_pretty_string).join(', ')
99. Unnecessary Complexity Refactoring
• Dynamic Code Creation
– What to look for: Code uses eval,
class_eval or module_eval to build new
code dynamically
– Issues: harder to read, fluid abstractions
are harder to understand, harder to test
© ASERT 2006-2010
and debug
– What to do:
• move string form of eval to block forms or use
define_method
• move method_missing to use class_eval
(example of Replace Dynamic Receptor with
Dynamic Method Definition)
• consider using Move Eval from Run-time to
Parse-time to overcome bottlenecks
Source: Dynamic Code Creation in Chapter 7: Unnecessary Complexity (Refactoring in Ruby)
100. Topics
• Introduction
• Design patterns
• Refactoring
Polyglot programming
• SOLID principles
© ASERT 2006-2010
• Other topics
• More Info
101. Programming Paradigms...
• Named state
(imperative style –
leads to modularity)
vs unnamed state
http://www.info.ucl.ac.be/~pvr/paradigms.html
(functional and logic
style)
• Deterministic vs
© ASERT 2006-2010
observable
nondeterminism
(threads, guards)
• Sequential vs
concurrent
(message passing
and shared state
styles)
102. © ASERT 2006-2010
...Programming Paradigms
http://www.info.ucl.ac.be/~pvr/paradigms.html
103. Polyglot Programming…
• Groovy calling clojure
@Grab('org.clojure:clojure:1.0.0')
import clojure.lang.Compiler
import clojure.lang.RT
def src = new File('temp.clj')
src.text = '''
© ASERT 2006-2010
(ns groovy)
(defn factorial [n]
(if (< n 2)
1
(* n (factorial (- n 1)))))
'''
src.withReader { reader ->
Compiler.load reader
}
def fac = RT.var('groovy', 'factorial')
println fac.invoke(5)
104. …Polyglot Programming
• C# calling F#
// F# Code
type FCallback = delegate of int*int -> int;;
type FCallback =
delegate of int * int -> int
© ASERT 2006-2010
let f3 (f:FCallback) a b = f.Invoke(a,b);;
val f3 : FCallback -> int -> int -> int
// C# Code
// method gets converted to the delegate automatically in C#
int a = Module1.f3(Module1.f2, 10, 20);
105. Topics
• Introduction
• Design patterns
• Refactoring
• Polyglot programming
SOLID principles
© ASERT 2006-2010
• Other topics
• More Info
107. SOLID Principles
• Single Responsibility Principle
• Open/Closed Principle
• Liskov Substitution Principle
© ASERT 2006-2010
• Interface Segregation Principle
• Dependency Inversion Principle
109. Open-Closed Principle...
• Fundamental rule to make
your software flexible
– Many other OOP principles, methodologies and
conventions revolve around this principle
• Open-Closed Principle (OCP) states:
• Software entities should be open for
© ASERT 2006-2010
extension, but closed for modification
• References
– Bertrand Meyer, Object Oriented Software
Construction (88, 97)
– Robert C Martin, The Open-Closed Principle
– Craig Larman, Protected Variation: The Importance of
Being Closed
Picture source: http://www.vitalygorn.com
110. ...Open-Closed Principle...
• Following the Rules
– Encapsulation: Make anything that shouldn’t be seen
private
– Polymorphism: Force things to be handled using
abstract classes or interfaces
• When making class hierarchies:
© ASERT 2006-2010
– Make anything that shouldn’t be open final
– Polymorphism: Always follow weaker pre stronger
post (object substitutability in the static world)
• When making changes that might break
existing clients
– Add a new class into the hierarchy
– No compilation of existing code! No breakages!