Test, beauty, cleanness        Alexandre Bergel      abergel@dcc.uchile.cl           16/06/2011
Problem description  We would like to build a two-dimensional graphicsframework for structured drawing editors Widgets suc...
What are the responsibilities?Containing the widgetsModeling the widgetsApplying the operations
Version 1Creating a canvas to contains the widgets
Testing the canvaspublic class HotDrawTest {	 @Test public void testEmptyCanvas() {	 	 Canvas canvas = new Canvas ();	 	 a...
Creating the class Canvaspublic class Canvas {	 public Object getNumberOfElements() {	 	 return 0;	 }}
Introducing the containment	   @Test public void testCanvas() {	   	 Canvas canvas = new Canvas ();	   		   	 canvas.add(n...
Revising the definition of Canvaspublic class Canvas {	 private ArrayList<Object> elements =	 	 	 	 new ArrayList<Object>()...
Revising the definition of Canvaspublic class Canvas {	 private ArrayList<Object> elements =	 	 	 	 new ArrayList<Object>()...
Version 2Introducing some widgets
We revise our testCanvas	   @Test public void testCanvas() {	   	 Canvas canvas = new Canvas ();	   		   	 canvas.add(new ...
Circle and Rectanglepublic class Circle {}public class Rectangle {}
Adding position to circle and               rectangle	   @Test public void testCanvas() {	   	 Canvas canvas = new Canvas ...
Generated templatepublic class Circle {	   public Circle(int i, int j, int k) {	   	 // TODO Auto-generated constructor st...
Filling the template templatepublic class Circle {	 private int x, y, radius;		 public Circle() {	 	 this(5, 5, 10);	 }		 ...
Generated templatepublic class Rectangle {	   public Rectangle(int i, int j, int k, int l) {	   	 // TODO Auto-generated c...
Filling the template templatepublic class Rectangle {	 private int x1, y1, x2, y2;	   public Rectangle() {	   	 this(2, 3,...
Version 3 Before moving on, lets step back on what we wroteto see whether there are opportunities for cleaning abit the code
HotDrawTestpublic class HotDrawTest {	   @Test public void testEmptyCanvas() {	   	 Canvas canvas = new Canvas ();        ...
HotDrawTestpublic class HotDrawTest {	   @Test public void testEmptyCanvas() {	   	 Canvas canvas = new Canvas ();        ...
Refactoring our testpublic class HotDrawTest {	 private Canvas canvas;		 @Before public void initializingFixture() {	 	 ca...
Giving a better name to the variable
canvas -> emptyCanvaspublic class HotDrawTest {	 private Canvas emptyCanvas;		 @Before public void initializingFixture() {...
canvas -> emptyCanvaspublic class HotDrawTest {	 private Canvas emptyCanvas;		 @Before public void initializingFixture() {...
Version 4 Applying the operations on the widget Note that at that point, we have not seen the need tohave a common supercl...
Adding an operation Let’s translate our objects  Each widget should now understand the messagetranslate(deltaX, deltaY) Le...
Testing circle first	   @Test public void translatingCircle() {	   	 Circle circle = new Circle();	   	 int oldX = circle.g...
Modifying Circlepublic class Circle {	 private int x, y, radius;		 public int getX() {	 	 return x;	 }		 public int getY()...
Translating Circlepublic class Circle {   ...	 public void translate(int dx, int dy) {	 	 x = x + dx;	 	 y = y + dy;	 }   ...
Translating Circlepublic class Circle {   ...	 public void translate(int dx, int dy) {	 	 x = x + dx;	 	 y = y + dy;	 }   ...
Translating the rectangle	   @Test public void translatingRectangle() {	   	 Rectangle rectangle = new Rectangle();	   	 i...
Updating Rectanglepublic class Rectangle {   ...	 public int getX1() {...	                         }	 public int getY1() {...
Important Note that we have not still see the need to have acommon interface and a common superclass If you doing it upfro...
Version 5 It is a bit cumbersome to have to translate eachelement one by one Let’s ask the canvas to translate all the nodes
Translating the canvas	 @Test public void translatingTheCanvas() {	   	   Rectangle rectangle = new Rectangle();	   	   in...
Translating the canvas        ...	   		   	   assertEquals(rectangle.getX1(), rectangleOldX1   +   2);	   	   assertEquals...
Updating Canvas - what we would               like to dopublic class Canvas {	   private ArrayList<Object> elements =	   	...
Updating Canvas - what we would               like to dopublic class Canvas {	   private ArrayList<Object> elements =	   	...
What is happening?  Only now we see the need to introduce a commoninterface that the object have to fulfill This interface ...
Let’s introduce the Widget interfacepublic interface Widget {	 public void translate(int dx, int dy);}public class Rectang...
Updating Canvaspublic class Canvas {	 private ArrayList<Widget> elements =	 	 	 	 new ArrayList<Widget>();	   public void ...
Updating Canvaspublic class Canvas {	 private ArrayList<Widget> elements =	 	 	 	 new ArrayList<Widget>();	   public void ...
Version 6 We are doing a pretty good job so far Let’s add a group of widgets that can be commonlymanipulated
Testing Group	   @Test public void groupingWidgets() {	   	 Group group = new Group();	   	 assertEquals(group.getNumberOf...
Defining Grouppublic class Group {	 private ArrayList<Object> elements =              new ArrayList<Object>();		 public voi...
Defining Grouppublic class Group {	 private ArrayList<Object> elements =              new ArrayList<Object>();		 public voi...
Defining Group  public class Group {  	 private ArrayList<Object> elements =                new ArrayList<Object>();  	  	 ...
Translating a group - what we could write,    but it contains a lot of duplication  	   @Test public void translatingGroup...
But let’s refactor firstpublic class HotDrawTest {	 private Canvas emptyCanvas;	 private Group emptyGroup, group;	 private ...
But let’s refactor first	   @Test public void groupingWidgets() {	   	 assertEquals(emptyGroup.getNumberOfElements(), 0);	 ...
Translating a group	   @Test public void translatingGroup() {	   	 int circleOldX = circle.getX();	   	 int circleOldY = c...
Translating a grouppublic class Group {	 private ArrayList<Widget> elements =	 	 	 	 new ArrayList<Widget>();		 public voi...
Translating a grouppublic class Group {	 private ArrayList<Widget> elements =	 	 	 	 new ArrayList<Widget>();		 public voi...
Translating a grouppublic class Group {	 private ArrayList<Widget> elements =	 	 	 	 new ArrayList<Widget>();		 public voi...
Version 7Let’s refactor Canvas  instead of containing a list of elements, it will solely contains a group
Canvas is getting simplerpublic class Canvas {	 private Group group = new Group();	   public void add(Widget widget) {	   ...
Canvas is getting simplerpublic class Canvas {	 private Group group = new Group();	   public void add(Widget widget) {	   ...
Version 8Adding a new operationWe will now scale objects                         scale x2
Adding a test for scalability	   @Test public void scalingGroup() {	   	 int oldRadius = circle.radius();	   	 int rectang...
Adding a test for scalability                                            Accessing                                        ...
Updating Circlepublic class Circle implements Widget {	 private int x, y, radius;     public int radius() {	   	 return ra...
Updating Rectanglepublic class Rectangle implements Widget {	 public int width() {	 	 return Math.abs(x2 - x1);	 }		 publi...
Scalabilitypublic class Group {	   public void scale(double s) {	   	   for(Widget w : elements)	   	   	   w.scale(s);	  ...
Scalabilitypublic class Group {	   public void scale(double s) {	   	   for(Widget w : elements)	   	   	   w.scale(s);	  ...
Cost of adding a Scalability                operation As you can see, there is a high cost of adding anoperation Adding th...
Cost of adding a Scalability                  operation In addition, there is some code duplication betweenscale() and (tr...
Cost of adding a Scalability                operation Ok, you may argue that’s not so bad. Tests are green! In fact, yes, ...
Why a Visitor pattern Let’s refactor all that by introducing a visitor  Remember two of the three responsibilities weident...
Why a Visitor pattern A well modularized software should let you add newoperations without touching your widgets Ok, there...
Updating with a visitorpublic interface Widget {	 public void translate(int dx, int dy);	 public void scale(double s);	 pu...
Updating with a visitorpublic class Group {   ...	 public void accept(Visitor visitor) {	 	 visitor.visitGroup(this);	 	 f...
Creating the visitorpublic class Visitor {	 public void visitCircle(Circle circle) { }	 public void visitGroup(Group group...
Trying out our visitor - let’s introduce      a visitor to count objects	   @Test public void countingObject() {	   	 Coun...
Countingpublic class CountingVisitor extends Visitor {	 private int result = 0;		 public void visitCircle(Circle circle) {...
Trying out our visitor - let’s introduce      a visitor to count objects	   @Test public void countingObject() {	   	 Coun...
Version 9Let’s refactoring and clean!
Using the visitor to countpublic class Canvas {	 private Group group = new Group();	   public int getNumberOfElements() {	...
Using Grouppublic class Group {   ...	 public void translate(int dx, int dy) {	 	 this.accept(new TranslatingVisitor(dx,dy...
Scalingpublic class ScalingVisitor extends Visitor {	 private double scale;		 public ScalingVisitor(double scale) {	 	 thi...
Scalingpublic class ScalingVisitor extends Visitor {	 private double scale;		 public ScalingVisitor(double scale) {	 	 thi...
Version 10Making group recursiveA group can now contains a group
Recursive group	   @Test public void recursiveGroup() {	   	 Group group = new Group();	   	 emptyGroup.add(group);	   	 a...
Group implement Widgetpublic class Group implements Widget {   ...}
Test beautycleanness
Upcoming SlideShare
Loading in...5
×

Test beautycleanness

489

Published on

a code oriented illustration of test-driven development, incremental software change

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
489
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Test beautycleanness

  1. 1. Test, beauty, cleanness Alexandre Bergel abergel@dcc.uchile.cl 16/06/2011
  2. 2. Problem description We would like to build a two-dimensional graphicsframework for structured drawing editors Widgets such as circle and rectanglehave to besupported Operations like translating, scaling have to be offered
  3. 3. What are the responsibilities?Containing the widgetsModeling the widgetsApplying the operations
  4. 4. Version 1Creating a canvas to contains the widgets
  5. 5. Testing the canvaspublic class HotDrawTest { @Test public void testEmptyCanvas() { Canvas canvas = new Canvas (); assertEquals(canvas.getNumberOfElements(), 0); }} The class Canvas is not created yet!
  6. 6. Creating the class Canvaspublic class Canvas { public Object getNumberOfElements() { return 0; }}
  7. 7. Introducing the containment @Test public void testCanvas() { Canvas canvas = new Canvas (); canvas.add(new Object()); assertEquals(1, canvas.getNumberOfElements()); canvas.add(new Object()); canvas.add(new Object()); assertEquals(3, canvas.getNumberOfElements()); } We need to be able to add objects in a canvas!
  8. 8. Revising the definition of Canvaspublic class Canvas { private ArrayList<Object> elements = new ArrayList<Object>(); public int getNumberOfElements() { return elements.size(); } public void add(Object object) { elements.add(object); }}
  9. 9. Revising the definition of Canvaspublic class Canvas { private ArrayList<Object> elements = new ArrayList<Object>(); public int getNumberOfElements() { return elements.size(); } Tests are green! public void add(Object object) { elements.add(object); }}
  10. 10. Version 2Introducing some widgets
  11. 11. We revise our testCanvas @Test public void testCanvas() { Canvas canvas = new Canvas (); canvas.add(new Circle()); assertEquals(1, canvas.getNumberOfElements()); canvas.add(new Circle()); canvas.add(new Rectangle()); assertEquals(3, canvas.getNumberOfElements()); }
  12. 12. Circle and Rectanglepublic class Circle {}public class Rectangle {}
  13. 13. Adding position to circle and rectangle @Test public void testCanvas() { Canvas canvas = new Canvas (); //(10, 20), radius 5 canvas.add(new Circle(10,20, 5)); assertEquals(1, canvas.getNumberOfElements()); canvas.add(new Circle()); //(5,6) -> (10,8) canvas.add(new Rectangle(5, 6, 10, 8)); assertEquals(3, canvas.getNumberOfElements()); }
  14. 14. Generated templatepublic class Circle { public Circle(int i, int j, int k) { // TODO Auto-generated constructor stub }}
  15. 15. Filling the template templatepublic class Circle { private int x, y, radius; public Circle() { this(5, 5, 10); } public Circle(int x, int y, int radius) { this.x = x; this.y = y; this.radius = radius; }}
  16. 16. Generated templatepublic class Rectangle { public Rectangle(int i, int j, int k, int l) { // TODO Auto-generated constructor stub }}
  17. 17. Filling the template templatepublic class Rectangle { private int x1, y1, x2, y2; public Rectangle() { this(2, 3, 5, 6); } public Rectangle(int x1, int y1, int x2, int y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; }}
  18. 18. Version 3 Before moving on, lets step back on what we wroteto see whether there are opportunities for cleaning abit the code
  19. 19. HotDrawTestpublic class HotDrawTest { @Test public void testEmptyCanvas() { Canvas canvas = new Canvas (); ... } @Test public void testCanvas() { Canvas canvas = new Canvas (); ... }}
  20. 20. HotDrawTestpublic class HotDrawTest { @Test public void testEmptyCanvas() { Canvas canvas = new Canvas (); ... } Duplication! @Test public void testCanvas() { Canvas canvas = new Canvas (); ... }}
  21. 21. Refactoring our testpublic class HotDrawTest { private Canvas canvas; @Before public void initializingFixture() { canvas = new Canvas (); } @Test public void testEmptyCanvas() { assertEquals(canvas.getNumberOfElements(), 0); } @Test public void testCanvas() { //(10, 20), radius 5 canvas.add(new Circle(10,20, 5)); ... }}
  22. 22. Giving a better name to the variable
  23. 23. canvas -> emptyCanvaspublic class HotDrawTest { private Canvas emptyCanvas; @Before public void initializingFixture() { emptyCanvas = new Canvas (); } @Test public void testEmptyCanvas() { assertEquals(emptyCanvas.getNumberOfElements(), 0); } @Test public void testCanvas() { //(10, 20), radius 5 emptyCanvas.add(new Circle(10,20, 5)); ... }}
  24. 24. canvas -> emptyCanvaspublic class HotDrawTest { private Canvas emptyCanvas; @Before public void initializingFixture() { emptyCanvas = new Canvas (); } @Test public void testEmptyCanvas() { assertEquals(emptyCanvas.getNumberOfElements(), 0); } @Test public void testCanvas() { //(10, 20), radius 5 emptyCanvas.add(new Circle(10,20, 5)); ... }}
  25. 25. Version 4 Applying the operations on the widget Note that at that point, we have not seen the need tohave a common superclass for Circle and Rectangle As well we have not seen the need to have acommon interface We should be test driven, else it is too easy to gowrong The class canvas also contains a list of objects
  26. 26. Adding an operation Let’s translate our objects Each widget should now understand the messagetranslate(deltaX, deltaY) Let’s write some test first
  27. 27. Testing circle first @Test public void translatingCircle() { Circle circle = new Circle(); int oldX = circle.getX(); int oldY = circle.getY(); circle.translate(2, 3); assertEquals(circle.getX(), oldX + 2); assertEquals(circle.getY(), oldY + 3); }
  28. 28. Modifying Circlepublic class Circle { private int x, y, radius; public int getX() { return x; } public int getY() { return y; } ...}// Note that there is no accessor for radius, we have notseen the need of it!
  29. 29. Translating Circlepublic class Circle { ... public void translate(int dx, int dy) { x = x + dx; y = y + dy; } ...}
  30. 30. Translating Circlepublic class Circle { ... public void translate(int dx, int dy) { x = x + dx; y = y + dy; } ...}
  31. 31. Translating the rectangle @Test public void translatingRectangle() { Rectangle rectangle = new Rectangle(); int oldX1 = rectangle.getX1(); int oldY1 = rectangle.getY1(); int oldX2 = rectangle.getX2(); int oldY2 = rectangle.getY2(); rectangle.translate(2, 3); assertEquals(rectangle.getX1(), oldX1 + 2); assertEquals(rectangle.getX2(), oldX2 + 2); assertEquals(rectangle.getY1(), oldY1 + 3); assertEquals(rectangle.getY2(), oldY2 + 3); }
  32. 32. Updating Rectanglepublic class Rectangle { ... public int getX1() {... } public int getY1() {...} public int getX2() {...} public int getY2() { return y2; } public void translate(int dx, int dy) { x1 = x1 + dx; x2 = x2 + dx; y1 = y1 + dy; y2 = y2 + dy; }}
  33. 33. Important Note that we have not still see the need to have acommon interface and a common superclass If you doing it upfront, when your design will look likewhat you want it to be, and not what it has to be
  34. 34. Version 5 It is a bit cumbersome to have to translate eachelement one by one Let’s ask the canvas to translate all the nodes
  35. 35. Translating the canvas @Test public void translatingTheCanvas() { Rectangle rectangle = new Rectangle(); int rectangleOldX1 = rectangle.getX1(); int rectangleOldY1 = rectangle.getY1(); int rectangleOldX2 = rectangle.getX2(); int rectangleOldY2 = rectangle.getY2(); Circle circle = new Circle(); int circleOldX = circle.getX(); int circleOldY = circle.getY(); emptyCanvas.add(rectangle); emptyCanvas.add(circle); emptyCanvas.translate(2, 3); ...
  36. 36. Translating the canvas ... assertEquals(rectangle.getX1(), rectangleOldX1 + 2); assertEquals(rectangle.getX2(), rectangleOldX2 + 2); assertEquals(rectangle.getY1(), rectangleOldY1 + 3); assertEquals(rectangle.getY2(), rectangleOldY2 + 3); assertEquals(circle.getX(), circleOldX + 2); assertEquals(circle.getY(), circleOldY + 3); }
  37. 37. Updating Canvas - what we would like to dopublic class Canvas { private ArrayList<Object> elements = new ArrayList<Object>(); public void add(Object object) { elements.add(object); } public void translate(int dx, int dy) { for(Object o : elements) o.translate(dx, dy); } ...}
  38. 38. Updating Canvas - what we would like to dopublic class Canvas { private ArrayList<Object> elements = new ArrayList<Object>(); public void add(Object object) { elements.add(object); } public void translate(int dx, int dy) { for(Object o : elements) o.translate(dx, dy); } ...} The compiler will not be happy with this
  39. 39. What is happening? Only now we see the need to introduce a commoninterface that the object have to fulfill This interface will only be aware of the translate(dx,dy)method
  40. 40. Let’s introduce the Widget interfacepublic interface Widget { public void translate(int dx, int dy);}public class Rectangle implements Widget { ...}public class Circle implements Widget { ...}
  41. 41. Updating Canvaspublic class Canvas { private ArrayList<Widget> elements = new ArrayList<Widget>(); public void add(Widget widget) { elements.add(widget); } public void translate(int dx, int dy) { for(Widget o : elements) o.translate(dx, dy); } ...}
  42. 42. Updating Canvaspublic class Canvas { private ArrayList<Widget> elements = new ArrayList<Widget>(); public void add(Widget widget) { elements.add(widget); } public void translate(int dx, int dy) { for(Widget o : elements) o.translate(dx, dy); } ...}
  43. 43. Version 6 We are doing a pretty good job so far Let’s add a group of widgets that can be commonlymanipulated
  44. 44. Testing Group @Test public void groupingWidgets() { Group group = new Group(); assertEquals(group.getNumberOfElements(), 0); group.add(new Circle()); group.add(new Rectangle()); assertEquals(group.getNumberOfElements(), 2); }
  45. 45. Defining Grouppublic class Group { private ArrayList<Object> elements = new ArrayList<Object>(); public void add(Object w) { elements.add(w); } public int getNumberOfElements() { return elements.size(); }}
  46. 46. Defining Grouppublic class Group { private ArrayList<Object> elements = new ArrayList<Object>(); public void add(Object w) { elements.add(w); } Yes! We haven’t seen the public int getNumberOfElements() { return elements.size(); to have Widget here need }}
  47. 47. Defining Group public class Group { private ArrayList<Object> elements = new ArrayList<Object>(); public void add(Object w) { elements.add(w); } Yes! We haven’t seen the public int getNumberOfElements() { return elements.size(); to have Widget here need } }This is the proof that we do not need it!
  48. 48. Translating a group - what we could write, but it contains a lot of duplication @Test public void translatingGroup() { Group group = new Group(); group.add(new Circle()); group.add(new Rectangle()); group.translate(...) }
  49. 49. But let’s refactor firstpublic class HotDrawTest { private Canvas emptyCanvas; private Group emptyGroup, group; private Circle circle; private Rectangle rectangle; @Before public void initializingFixture() { emptyCanvas = new Canvas (); emptyGroup = new Group(); group = new Group(); group.add(circle = new Circle()); group.add(rectangle = new Rectangle()); }
  50. 50. But let’s refactor first @Test public void groupingWidgets() { assertEquals(emptyGroup.getNumberOfElements(), 0); assertEquals(group.getNumberOfElements(), 2); }
  51. 51. Translating a group @Test public void translatingGroup() { int circleOldX = circle.getX(); int circleOldY = circle.getY(); int rOldX1 = rectangle.getX1(); int rOldY1 = rectangle.getY1(); group.translate(2, 3); assertEquals(rectangle.getX1(), rOldX1 + 2); assertEquals(rectangle.getY1(), rOldY1 + 3); assertEquals(circle.getX(), circleOldX + 2); assertEquals(circle.getY(), circleOldY + 3); }
  52. 52. Translating a grouppublic class Group { private ArrayList<Widget> elements = new ArrayList<Widget>(); public void add(Widget w) { elements.add(w); } public int getNumberOfElements() { return elements.size(); } public void translate(int i, int j) { for(Widget w : elements) w.translate(i, j); }}
  53. 53. Translating a grouppublic class Group { private ArrayList<Widget> elements = new ArrayList<Widget>(); public void add(Widget w) { elements.add(w); } Yes, we need an array of Widgets public int getNumberOfElements() { return elements.size(); } public void translate(int i, int j) { for(Widget w : elements) w.translate(i, j); }}
  54. 54. Translating a grouppublic class Group { private ArrayList<Widget> elements = new ArrayList<Widget>(); public void add(Widget w) { elements.add(w); } public int getNumberOfElements() { return elements.size(); } public void translate(int i, int j) { for(Widget w : elements) w.translate(i, j); }}
  55. 55. Version 7Let’s refactor Canvas instead of containing a list of elements, it will solely contains a group
  56. 56. Canvas is getting simplerpublic class Canvas { private Group group = new Group(); public void add(Widget widget) { group.add(widget); } public void translate(int dx, int dy) { group.translate(dx, dy); } public int getNumberOfElements() { return group.getNumberOfElements(); }}
  57. 57. Canvas is getting simplerpublic class Canvas { private Group group = new Group(); public void add(Widget widget) { group.add(widget); } public void translate(int dx, int dy) { group.translate(dx, dy); } public int getNumberOfElements() { return group.getNumberOfElements(); }}
  58. 58. Version 8Adding a new operationWe will now scale objects scale x2
  59. 59. Adding a test for scalability @Test public void scalingGroup() { int oldRadius = circle.radius(); int rectangleWidth = rectangle.width(); int rectangleHeight = rectangle.height(); group.scale(2); assertEquals(circle.radius(), 2 * oldRadius); assertEquals(rectangle.width(), 2 * rectangleWidth); assertEquals(rectangle.height(), 2 * rectangleHeight); }
  60. 60. Adding a test for scalability Accessing radius @Test public void scalingGroup() { int oldRadius = circle.radius(); int rectangleWidth = rectangle.width(); int rectangleHeight = rectangle.height(); group.scale(2); Accessing width and height assertEquals(circle.radius(), 2 * oldRadius); assertEquals(rectangle.width(), 2 * rectangleWidth); assertEquals(rectangle.height(), 2 * rectangleHeight); }
  61. 61. Updating Circlepublic class Circle implements Widget { private int x, y, radius; public int radius() { return radius; } ...}
  62. 62. Updating Rectanglepublic class Rectangle implements Widget { public int width() { return Math.abs(x2 - x1); } public int height() { return Math.abs(y2 - y1); } ...}
  63. 63. Scalabilitypublic class Group { public void scale(double s) { for(Widget w : elements) w.scale(s); } ... }public interface Widget { ... public void scale(double s); }public class Circle implements Widget { public void scale(double s) { radius *= s; }}public class Rectangle implements Widget { public void scale(double s) { x1 *= s; y1 *= s; x2 *= s; y2 *= s; }}
  64. 64. Scalabilitypublic class Group { public void scale(double s) { for(Widget w : elements) w.scale(s); } ... }public interface Widget { ... public void scale(double s); }public class Circle implements Widget { public void scale(double s) { radius *= s; }}public class Rectangle implements Widget { public void scale(double s) { x1 *= s; y1 *= s; x2 *= s; y2 *= s; }}
  65. 65. Cost of adding a Scalability operation As you can see, there is a high cost of adding anoperation Adding the scalability implies touching 4 types Circle, Rectangle, Widget, Group
  66. 66. Cost of adding a Scalability operation In addition, there is some code duplication betweenscale() and (translate). In the class Group public void translate(int i, int j) { for(Widget w : elements) w.translate(i, j); } public void scale(double s) { for(Widget w : elements) w.scale(s); }
  67. 67. Cost of adding a Scalability operation Ok, you may argue that’s not so bad. Tests are green! In fact, yes, it is bad: our code is getting bad People who authored Swing did not expect to end upwith a JComponent long of 5471 lines of code Swing is becoming so complex, that no much ishappening on it now. It is slowly missing theput-smoothly-your-GUI-on-the-web trend thatframework like GWT is offering So yes, this is bad what we have to touch 4 classesand introduce duplication
  68. 68. Why a Visitor pattern Let’s refactor all that by introducing a visitor Remember two of the three responsibilities weidentified: Modeling the widgets Applying the operations The problem we are facing is that widgets and theoperations you can do on them are mixed We are mixing responsibilities!
  69. 69. Why a Visitor pattern A well modularized software should let you add newoperations without touching your widgets Ok, there is a cheat here A visitor makes introduction of new operation verycheap, but the cost of extending the domain is high What we are implicitly implying here, it is more likelywe will have new operations to add than extending thedomain Is that reasonable? Difficult to answer. But try this:How many times do you add a new field in adatabase? How many times you perform a differentquery on them?
  70. 70. Updating with a visitorpublic interface Widget { public void translate(int dx, int dy); public void scale(double s); public void accept(Visitor visitor);}public class Circle implements Widget { public void accept(Visitor visitor) { visitor.visitCircle(this); } ...}public class Rectangle implements Widget { public void accept(Visitor visitor) { visitor.visitRectangle(this); } ...}
  71. 71. Updating with a visitorpublic class Group { ... public void accept(Visitor visitor) { visitor.visitGroup(this); for(Widget w : elements) w.accept(visitor); }}// Yes, Group does not implement Widget, we havefelt the need so far.// Currently, a group cannot contains anothergroup// Note that Group does the recursion. It couldwell be in the visitor itself, in that case, theelements have to be accessible from outside.Which may not be what we want
  72. 72. Creating the visitorpublic class Visitor { public void visitCircle(Circle circle) { } public void visitGroup(Group group) { } public void visitRectangle(Rectangle rectangle) { }}
  73. 73. Trying out our visitor - let’s introduce a visitor to count objects @Test public void countingObject() { CountingVisitor visitor = new CountingVisitor(); assertEquals(0, visitor.result()); visitor = new CountingVisitor(); emptyGroup.accept(visitor); assertEquals(0, visitor.result()); visitor = new CountingVisitor(); group.accept(visitor); assertEquals(2, visitor.result()); }
  74. 74. Countingpublic class CountingVisitor extends Visitor { private int result = 0; public void visitCircle(Circle circle) { result++; } public void visitGroup(Group group) { result++; } public void visitRectangle(Rectangle rectangle) { result++; } public int result() { return result; }}
  75. 75. Trying out our visitor - let’s introduce a visitor to count objects @Test public void countingObject() { CountingVisitor visitor = new CountingVisitor(); assertEquals(0, visitor.result()); visitor = new CountingVisitor(); emptyGroup.accept(visitor); assertEquals(0, visitor.result()); visitor = new CountingVisitor(); group.accept(visitor); assertEquals(2, visitor.result()); }
  76. 76. Version 9Let’s refactoring and clean!
  77. 77. Using the visitor to countpublic class Canvas { private Group group = new Group(); public int getNumberOfElements() { CountingVisitor visitor = new CountingVisitor(); group.accept(visitor); return visitor.result() - 1; } ...}
  78. 78. Using Grouppublic class Group { ... public void translate(int dx, int dy) { this.accept(new TranslatingVisitor(dx,dy)); } public void scale(double s) { this.accept(new ScalingVisitor(s)); }}
  79. 79. Scalingpublic class ScalingVisitor extends Visitor { private double scale; public ScalingVisitor(double scale) { this.scale = scale; } @Override public void visitCircle(Circle circle) { circle.scale(scale); } @Override public void visitRectangle(Rectangle rectangle) { rectangle.scale(scale); }}
  80. 80. Scalingpublic class ScalingVisitor extends Visitor { private double scale; public ScalingVisitor(double scale) { this.scale = scale; } @Override public void visitCircle(Circle circle) { circle.scale(scale); } @Override public void visitRectangle(Rectangle rectangle) { rectangle.scale(scale); }}
  81. 81. Version 10Making group recursiveA group can now contains a group
  82. 82. Recursive group @Test public void recursiveGroup() { Group group = new Group(); emptyGroup.add(group); assertEquals(group.getNumberOfElements(), 1); }
  83. 83. Group implement Widgetpublic class Group implements Widget { ...}
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×