2. Introduction …
• Developer practices
– Well understood and documented for
traditional and agile approaches such
as Java, C++ and C# development
– But dynamic languages like Groovy,
Ruby, Python, Boo, JavaScript and
(c) ASERT 2006-2009
others change the ground rules
– Many of the rules and patterns we
have been taught no longer apply
3. … 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 Language
(c) ASERT 2006-2009
Guide. Prentice Hall.
– Robert C Martin (2002), Agile Software Development, Principles,
Patterns, and Practices. Prentice Hall.
• In the dynamic
language world,
are these guidelines
FACT or MYTH !!
4. Examples to ponder
• What does Immutability mean?
– When even constants can be changed
• What does encapsulation
mean?
– When I can peek at anything
(c) ASERT 2006-2009
under the covers
• How can I devise tests
at development time?
– When my system can change in
unknown ways at runtime
• How can IDEs help me?
– If I no longer spoon feed it static-typing information
or if my language now only allows checks at runtime
5. What do I mean by Dynamic Language?
• I prefer a loose definition
• One or more of:
– Dynamic typing
• Greater polymorphism
– Metaprogramming
• Allow language itself to be dynamically changed
(c) ASERT 2006-2009
• 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
6. Static vs Dynamic Typing …
• The Debate
– Static vs dynamic typing
• Static: the type of each variable
(or expression) must be known
at compile time
like wearing a straight-jacket?
• Dynamic: type information is
(c) ASERT 2006-2009
known only at runtime
like tightrope walking with no net?
– Strong vs weak typing
• Strong: List<Integer> myList
• Weak: Object myList
– Type safety
• How is this provided if at all?
– Type inference
• Is this supported?
7. …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
(c) ASERT 2006-2009
– 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
8. … 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
(c) ASERT 2006-2009
– 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
9. … Static vs Dynamic Typing …
• 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.
(c) ASERT 2006-2009
10. … Static vs Dynamic Typing …
• An example
interface Reversible {
def reverse()
}
class ReversibleString implements Reversible {
def reverse() { /* */ }
??? }
class ReversibleArray implements Reversible {
def reverse() { /* */ }
(c) ASERT 2006-2009
??? }
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
11. … 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
(c) ASERT 2006-2009
}
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
12. … Static vs Dynamic Typing …
• 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.
(c) ASERT 2006-2009
...but not a total lie either...
...dynamic languages certainly
assist with removing duplication
and sometimes removing clutter
and boilerplate code...
13. 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()
(c) ASERT 2006-2009
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]
15. Typing approaches and IDEs…
• Class A has a bit of duplication
class A {
def helper
def make() {
helper.invoke('create')
}
def get() {
(c) ASERT 2006-2009
helper.invoke('read')
}
def change() {
helper.invoke('update')
}
def remove() {
helper.invoke('delete')
}
}
16. … Typing approaches and IDEs …
• No problems, we can refactor out the dup
class B {
def helper
def make() {
invoke('create')
}
def get() {
(c) ASERT 2006-2009
invoke('read')
}
def change() {
invoke('update')
}
def remove() {
invoke('delete')
}
private invoke(cmd) {
helper.invoke(cmd)
}
}
17. … Typing approaches and IDEs …
• But we can do more using a dynamic
language by leveraging metaprogramming
class C {
def helper
def commands = [
make: 'create',
(c) ASERT 2006-2009
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? … ...
18. … Typing approaches and IDEs …
class Dumper {
def name
def invokeMethod(String methodName, args) {
println "$name: called $methodName with $args"
}
}
(c) ASERT 2006-2009
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()
}
19. … Typing approaches and IDEs
• … At the expense of IDE completion?
(c) ASERT 2006-2009
But remember:
“clearly express intent”
...
20. Language features instead of Patterns …
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" }
}
(c) ASERT 2006-2009
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
21. …Language features instead of Patterns…
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)"
(c) ASERT 2006-2009
}
}
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
22. … Language features instead of Patterns …
SquarePeg.metaClass.getRadius =
{ Math.sqrt(((delegate.width/2)**2)*2) }
(4..7).each { w -> pretty(hole, new SquarePeg(width:w)) }
Adapter Pattern
(c) ASERT 2006-2009
Do I create a whole
new class or just add
the method I need
on the fly?
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
23. 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
–
(c) ASERT 2006-2009
Can help with test creation
– Avoids class proliferation
– But you still need testing
24. … Language features instead of Patterns
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)
} }
(c) ASERT 2006-2009
}
}
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 [24]
25. 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
–
(c) ASERT 2006-2009
Avoids class proliferation
– But you still need testing
27. 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 {
{ n, m -> n * m },
def execute(n, m) {
def result = 0 { n, m ->
n.times { def total = 0; n.times{ total += m }; total },
result += m { n, m -> ([m] * n).sum() }
(c) ASERT 2006-2009
} ]
return result
}
def sampleData = [
}
[3, 4, 12],
def sampleData = [ [5, -5, -25]
[3, 4, 12], ]
[5, -5, -25]
] sampleData.each{ data ->
multiplicationStrategies.each{ calc ->
Calc[] multiplicationStrategies = [
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])
}
}
28. 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
(c) ASERT 2006-2009
– Aids refactoring
– Can help with test creation
– Avoids class proliferation
– But you still need testing
29. … Language features instead of Patterns …
• 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
(c) ASERT 2006-2009
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, …
30. … Language features instead of Patterns
<html>
<head>
import groovy.xml.* <title>Hello</title>
def b = new MarkupBuilder() </head>
b.html { <body>
(c) ASERT 2006-2009
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>
31. 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) {
(c) ASERT 2006-2009
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/
34. 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)
(c) ASERT 2006-2009
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()
35. 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
(c) ASERT 2006-2009
36. 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
(c) ASERT 2006-2009
}
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"
}
}
37. … 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
(c) ASERT 2006-2009
StaffMemberUsingDelegation(name, age, salary) {
delegate = new Person(name, age)
this.salary = salary
}
def haveBirthday() {
delegate.haveBirthday()
}
String toString() {
delegate.toString() + " and has a salary of $salary"
}
}
38. … 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
(c) ASERT 2006-2009
parameters rather than the traditional positional
parameters shown here (future versions of Ruby may
have this too)
39. … 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
}
(c) ASERT 2006-2009
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
40. … 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
(c) ASERT 2006-2009
• E.g. If I wanted to make haveBirthday() increment salary
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"
}
}
41. 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
(c) ASERT 2006-2009
42. 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
(c) ASERT 2006-2009
representation through subclassing
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
43. …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;
(c) ASERT 2006-2009
}
}
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;
}
}
44. …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)
}
(c) ASERT 2006-2009
@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()
45. …Singleton Pattern…
• But it is easy using meta-programming
– Old style
class Calculator {
private total = 0
def add(a, b) { total++; a + b }
(c) ASERT 2006-2009
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())
46. …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()
(c) ASERT 2006-2009
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]
47. …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
(c) ASERT 2006-2009
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
48. …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
(c) ASERT 2006-2009
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
49. 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
(c) ASERT 2006-2009
50. Pattern Summary
• Patterns can be replaced by language
features and libraries
(c) ASERT 2006-2009
• So patterns aren‟t important any more!
...
51. 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‟
(c) ASERT 2006-2009
– Some refactorings are done differently
52. 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
(c) ASERT 2006-2009
// Before refactoring
Object lastReading() {
return readings.lastElement()
}
// After refactoring
Reading lastReading() {
return (Reading) readings.lastElement()
}
53. 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?
(c) ASERT 2006-2009
– 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()
54. 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:
(c) ASERT 2006-2009
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
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"
55. … Enabling a functional style …
• A first attempt at a more functional style:
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
(c) ASERT 2006-2009
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"
56. … 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:
(c) ASERT 2006-2009
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']
] ]
57. … 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]
(c) ASERT 2006-2009
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
58. 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.
(c) ASERT 2006-2009
• Solution
– Remove the parameter and let the receiver invoke the
method.
• Dynamic solution
– Partial Application: Currying
59. 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
(c) ASERT 2006-2009
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
60. … 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
(c) ASERT 2006-2009
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
61. … 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
}
(c) ASERT 2006-2009
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
62. … 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
}
(c) ASERT 2006-2009
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
63. … 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) {
(c) ASERT 2006-2009
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
64. … 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) {
(c) ASERT 2006-2009
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
65. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
return discountedPrice()
}
private double discountedPrice() {
(c) ASERT 2006-2009
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
66. … Replace parameter with method …
class Order {
private int quantity, itemPrice
Order(q, p) {quantity = q; itemPrice = p}
double getPrice() {
return discountedPrice()
}
private double discountedPrice() {
(c) ASERT 2006-2009
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
67. … 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
}
(c) ASERT 2006-2009
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
72. 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 ...
(c) ASERT 2006-2009
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
74. … 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"
(c) ASERT 2006-2009
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(', ')
75. • This is the end of the talk
(c) ASERT 2006-2009
76. • This is the end of the talk
• NO!
• We haven’t questioned some fundamental
principles yet!
(c) ASERT 2006-2009
77. 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:
(c) ASERT 2006-2009
• Software entities should be open for
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
78. ...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:
(c) ASERT 2006-2009
– 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!
79. ...Open-Closed Principle...
• Part I: If I violate the Open part of OCP in
static languages
– I can‟t make the future enhancements I need
• Part II: If I violate the Closed part of OCP
– Client applications using my libraries might
(c) ASERT 2006-2009
break or require recompilation in the future
Class A Extendible Class A
Interface User Class A User
Class A‟ Class A‟
User Class A‟
User
Optional Optional
Class A Class A‟
Factory Factory
...
80. ...Open-Closed Principle...
• Part I: Consider Java‟s String class
– Has methods to convert to upper or
lower case but no swapCase() method?
– Traditionally, consider creating an
EnhancedString class using inheritance?
– I can‟t: String is immutable and final
(c) ASERT 2006-2009
• In OCP terms, it is not open for extension
• Dynamic language solution: open classes
String.metaClass.swapCase = {
#light
open String
delegate.collect{ c ->
type System.String with c in 'A'..'Z' ?
member x.swapCase =
seq { for letter in x.ToCharArray() do
c.toLowerCase() :
if (System.Char.IsLower(letter)) c.toUpperCase()
then yield System.Char.ToUpper(letter)
else yield System.Char.ToLower(letter)
}.join()
} }
printfn "result: %A" "Foo".swapCase
assert "Foo".swapCase() == "fOO"
...
81. ...Open-Closed Principle...
• Part II: Violating OCP (see [15])
class Square {
def side
}
class Circle {
def radius
}
(c) ASERT 2006-2009
class AreaCalculator {
double area(shape) {
switch (shape) {
case Square:
return shape.side * shape.side
case Circle:
return Math.PI * shape.radius ** 2
}
}
}
82. ...Open-Closed Principle...
def shapes = [
new Square(side: 3),
new Square(side: 2),
new Circle(radius: 1.5)
]
def calc = new AreaCalculator()
shapes.sort().each {s ->
println "Area of $s.class.name is ${calc.area(s)}"
(c) ASERT 2006-2009
}
• What‟s wrong
– If we wanted to introduce a Triangle, the
AreaCalculator would need to be recompiled
– If we wanted to change the order the shape
information was displayed, there might be many
changes to make
83. ...Open-Closed Principle...
* Our abstractions never designed sorting to be
• Dynamic shapes one of the things open for extension. See [15].
– No issue with adding Triangle but sorting is an issue *
class Square { Note: Duck-type
private side polymorphism
double area() { side ** 2 } instead of
} inheritance
class Circle { polymorphism,
(c) ASERT 2006-2009
private radius i.e. no base Shape
double area() { Math.PI * radius ** 2 } (abstract) class or
} interface.
Hmm… what are
def shapes = [ the testing
new Square(side:3), implications when
new Square(side:2), I add Triangle?
new Circle(radius:1.5)
Area of Square is 9.0
]
Area of Square is 4.0
// unsorted Area of Circle is 7.0685834705770345
def prettyPrint = { s ->
println "Area of $s.class.name is ${s.area()}" }
shapes.each(prettyPrint)
...
84. ...Open-Closed Principle...
• Dynamic sorting using Closures
– As long as we are happy having our sort “code”
within a closure we have complete freedom
– Sometimes representing our abstractions within
classes is appropriate; many times closures will do
Area of Square is 4.0
(c) ASERT 2006-2009
Area of Circle is 7.0685834705770345
// sorted by area Area of Square is 9.0
def byArea = { s -> s.area() }
shapes.sort(byArea).each(prettyPrint) Note: Make sure your
closures are testable.
// sorted circles before squares but otherwise by area
def byClassNameThenArea = { sa, sb ->
sa.class.name == sb.class.name ? Area of Circle is 7.06858...
sa.area() <=> sb.area() : Area of Square is 4.0
Area of Square is 9.0
sa.class.name <=> sb.class.name
}
shapes.sort(byClassNameThenArea).each(prettyPrint)
...
85. ...Open-Closed Principle...
• Instead of worrying about
– Rigidity
– Fragility
– Immobility
(Because they can be easily gotten
around even if you don‟t try to apply OCP)
(c) ASERT 2006-2009
• We must worry about
– Duplication
– Harder refactoring or testing
– Feature interaction
• And of course OCP then leads to ...
– Liskov Substitution Principle, Single Responsibility
Principle, Dependency Inversion Principle, ...
86. ...Open-Closed Principle...
• “Clean code” [23] states it this way:
– Procedural code (i.e. using data structures) makes it
easy to add new functions without changing existing
data structures but when new data structures are
added, all existing procedures may need to change
– OO code makes it easy to add new classes without
(c) ASERT 2006-2009
changing existing functions but when new functions
are added, all classes must change
• Recommendation?
– Choose procedural or OO approach based on
whether anticipated evolution of system involves
functions or data
– Use Visitor (dual dispatch) Pattern if you think both
functions and data might change
87. ...Open-Closed Principle...
class Square {
double side
}
class Rectangle {
double height, width
}
class Circle {
(c) ASERT 2006-2009
double radius
}
class Geometry {
def area(shape) {
switch (shape) {
case Square: return shape.side ** 2
case Rectangle: return shape.height * shape.width
case Circle: return PI * shape.radius ** 2
}
}
Can add perimeter() here without shape classes changing but if we
}
added a Triangle, area(), perimeter() etc. would need to change.
88. ...Open-Closed Principle...
interface Shape { If we add perimeter() here, each
double area() shape class must change but we can
} add new shapes with no changes
class Square implements Shape {
double side
double area() { side ** 2 }
}
(c) ASERT 2006-2009
class Rectangle implements Shape {
double height, width
double area() { height * width }
}
class Circle implements Shape {
double radius
double area() { PI * radius ** 2 }
}
89. ...Open-Closed Principle...
class Square {
double side
double area() { side ** 2 }
} We can easily add perimeter() here
but for any code requiring the perimeter()
method to exist, we should test that code
class Rectangle { with all shapes.
double height, width
double area() { height * width }
}
(c) ASERT 2006-2009
class Circle {
double radius
double area() { PI * radius ** 2 }
}
90. ...Open-Closed Principle...
• “Clean code” [23] recommendation:
– Choose procedural or OO approach or Visitor
• Agile variation:
– Defer moving to complicated solutions, e.g. Visitor
Pattern, but have in place sufficient tests so that you
can confidently refactor to use one later if needed
(c) ASERT 2006-2009
• Dynamic language variation:
– You won‟t need an explicit visitor (more on this later)
– Duck typing lets you add functions or data without
changing existing classes at the expense of static
type safety
– If you add a function you might need additional tests
for each class associated with that function
– If you add a new class you might need additional
tests for each function associated with that class
91. ...Open-Closed Principle
• Sometimes referred to as the Expression
Problem:
– Independently Extensible Solutions to the Expression Problem
by Matthias Zenger Martin Odersky
– http://www.scala-lang.org/docu/files/
IC_TECH_REPORT_200433.pdf
(c) ASERT 2006-2009
92. • This is the end of the talk
(c) ASERT 2006-2009
93. • This is the end of the talk
• NO!
• We haven’t looked at Advanced Topics yet
including Aspects, Testability, Feature Interaction
and DSLs!
(c) ASERT 2006-2009
94. What & why of dependency injection?
• Construction by hand
Loose
Coupling
• Factory pattern Flexibility
(c) ASERT 2006-2009
Testability
Reusability
• Service locator Lifecycle
Control
Central
• Dependency injection Control
95. Dependency Injection vs Metaprogramming …
• Dependency Injection
– Dependencies are explicitly declared and allowed to
be set externally (typically via constructor or setters)
• Transparent injection of dependent service objects into other
service objects by a controlling container hence the name
inversion of control
• Why?
(c) ASERT 2006-2009
– More flexible
• Central configuration of service objects
– Can be less work to do
• Service objects are instantiated by the dependency injection
framework
– Improves testability
– Improves reusability
– Improved lifecycle control
96. … Dependency Injection vs Metaprogramming …
• Without dependency injection
class PrintService {
private printer = new PhysicalPrinter('Canon i9900')
}
class PrintServiceTest extends GroovyTestCase {
def testPrintService() {
(c) ASERT 2006-2009
def printService = new PrintService()
printService.print() class PrintService
// go to the printer and fetch the page def initialise
} @printer =
} PhysicalPrinter.new('Canon i9900')
end
end
class PrintServiceTest < Test::Unit
def test_print_service
print_service = PrinterService.new
print_service.print
# go to the printer and fetch the page
end
end
97. … Dependency Injection vs Metaprogramming …
• Improves testability
class TestablePrintService {
def printer
}
class TestablePrintServiceTest extends GroovyTestCase {
def testPrintService() {
def printService = new TestablePrintService()
printService.printer = new StubPrinter()
(c) ASERT 2006-2009
printService.print()
//...
} class TestablePrintService
} attr_accessor :printer
end
class TestablePrintServiceTest < Test::Unit::TestCase
def test_print_service
print_service = TestablePrintService.new
print_service.printer = StubPrinter.new
print_service.print
#...
end
end