Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Duck Typing, Compatibility, and the Adaptor Pattern

Duck Typing, Compatibility, and the Adaptor Pattern

Nordic JS, 2014

Reg Braithwaite

September 12, 2014
Tweet

More Decks by Reg Braithwaite

Other Decks in Programming

Transcript

  1. None
  2. the$art$of$the$javascript$metaobject$protocol Duck%Typing,%Compa1bility,%and The$Adaptor$Pa-ern

  3. None
  4. Duck%typing%is%a%style%of%typing%in% which%an%object's%methods%and% proper:es%determine%the%valid% seman:cs,

  5. ...rather'than'its'inheritance'from'a' par0cular'class'or'implementa0on'of' an'explicit'interface.

  6. None
  7. Tastes&Like&Fruit

  8. None
  9. Also%Tastes%Like%Fruit

  10. "Write'code'that'depends'upon' tastes1like(fruit)'rather'than' depending'upon'is1a(fruit)."

  11. None
  12. duck%typing%is%a Compa&bility,Technique

  13. None
  14. A"Problem:

  15. None
  16. Conway's)Game)of)Life

  17. "Conway's*Game*of*Life*is*a*two2 state*cellular*automata."

  18. An#Implementa+on 1. Cells,

  19. An#Implementa+on 1. Cells, 2. Universe,

  20. An#Implementa+on 1. Cells, 2. Universe,.and 3. View.

  21. Cell 1. Neighbours,

  22. Cell 1. Neighbours,.and 2. State.

  23. function StandardCell () { this._neighbours = []; this._alive = false;

    }
  24. StandardCell.prototype.neighbours = function neighbours (neighbours) { return this._neighbours; }; StandardCell.prototype.setNeighbours

    = function setNeighbours (neighbours) { this._neighbours = neighbours.slice(0); return this; };
  25. StandardCell.prototype.alive = function alive () { return this._alive; }; StandardCell.prototype.setAlive

    = function setAlive (alive) { this._alive = alive; return this; };
  26. None
  27. moving'through Time

  28. Cell 1. Neighbours, 2. State,2and 3. Transforma9on2of2State.

  29. Universe 1. Neighbourhoods.(not.shown),

  30. Universe 1. Neighbourhoods.(not.shown),.and 2. Transforma:on.of.States.

  31. StandardCell.prototype.nextAlive = function nextAlive () { var alives = this._neighbours.filter(function

    (n) { return n.alive(); }).length; if (this.alive()) { return alives === 2 || alives == 3; } else { return alives == 3; } };
  32. Universe.prototype.iterate = function iterate () { var aliveInNextGeneration = this.cells().map(

    function (c) { return [c, c.nextAlive()]; } ); aliveInNextGeneration.forEach(function (a) { var cell = a[0], next = a[1]; cell.setAlive(next); }); };
  33. None
  34. drawing Life

  35. View 1. Drawing*a*Cell:

  36. View.prototype.drawCell = function drawCell (cell, x, y) { var xPlus

    = x + this.cellSize(), yPlus = y + this.cellSize() this._canvasContext.clearRect(x, y, xPlus, yPlus); this._canvasContext.fillStyle = this.cellColour(cell); this._canvasContext.fillRect(x, y, xPlus, yPlus); return self; };
  37. View.prototype.cellColour = function cellColour (cell) { return cell.alive() ? WHITE

    : BLACK; };
  38. None
  39. None
  40. Ch#ch#ch#changes!

  41. None
  42. redesigning(for Colour

  43. function ColourCell () { this._neighbours = []; this._age = 0;

    } ColourCell.prototype.neighbours = StandardCell.prototype.neighbours; ColourCell.prototype.setNeighbours = StandardCell.prototype.setNeighbours;
  44. ColourCell.prototype.age = function age () { return this._age; }; ColourCell.prototype.setAge

    = function setAge (age) { this._age = age; return this; };
  45. None
  46. moving'through',me in#Colour

  47. ColourCell.prototype.nextAge = function next () { var alives = this._neighbours.filter(function

    (n) { return n.age() > 0; }).length; if (this.age() > 0) { return (alives === 2 || alives == 3) ? (this.age() + 1) : 0; } else { return (alives == 3) ? (this.age() + 1) : 0; } };
  48. Universe.prototype.iterate = function iterate () { var ageInNextGeneration = this.cells().map(

    function (c) { return [c, c.nextAge()]; } ); ageInNextGeneration.forEach(function (a) { var cell = a[0], next = a[1]; cell.setAge(next); }); };
  49. None
  50. drawing(life in#Colour

  51. var COLOURS = [ BLACK, GREEN, BLUE, YELLOW, WHITE, RED

    ]; View.prototype.cellColour = function cellColour (cell) { return COLORS[ (cell.age() >= COLOURS.length) ? (COLOURS.length - 1) : cell.age() ]; }; // ...
  52. None
  53. something Doesn't(Fit

  54. Object.keys(StandardCell.prototype) // => [ 'neighbours', 'setNeighbours', 'alive', 'setAlive', 'nextAlive' ]

  55. Object.keys(ColourCell.prototype) // => [ 'neighbours', 'setNeighbours', 'age', 'setAge', 'nextAge' ]

  56. The$state$of$a$cell$is$now$"age"$ instead$of$"aliveness."

  57. None
  58. These%changes%will Ripple&Through&Universe&and&View

  59. our$problem$is$that Universe(is(coupled(to(Cell's( Interface

  60. None
  61. None
  62. how$smart$people Solve&the&Problem

  63. "Make&a&coloured&cell&behave&like&a& standard&cell."

  64. ColourCell.prototype.alive = function alive () { return this._age > 0;

    };
  65. ColourCell.prototype.setAlive = function setAlive (alive) { if (alive) { this.setAge(this.age()

    + 1); } else this.setAge(0); return this; };
  66. ColourCell.prototype.nextAlive = StandardCell.prototype.nextAlive;

  67. Object.keys(ColourCell.prototype) // => [ 'neighbours', 'setNeighbours', 'age', 'setAge', 'nextAge', 'alive',

    'setAlive', 'nextAlive' ]
  68. Presto!(Backwards(Compa2bility. (it$"tastes$like$a$standard$cell")

  69. None
  70. universe(and(cell(are No#Longer#Coupled

  71. None
  72. At#What#Cost?

  73. None
  74. Increasing*Flexibility Increases(Complexity

  75. None
  76. None
  77. there%is Another(Way

  78. "Make&a&cell&that&behaves&like&a& Standard&Cell,&but&is&implemented&in& terms&of&a&Coloured&Cell."

  79. function AsStandard (colouredCell) { this._colouredCell = colouredCell; } var tastesLikeAStandardCell

    = new AsStandard(aColourCell);
  80. AsStandard.prototype.neighbours = function neighbours () { return this._colouredCell.neighbours(); }; AsStandard.prototype.setNeighbours

    = function setNeighbours (neighbours) { this._colouredCell.setNeighbours(neighbours); return this; };
  81. AsStandard.prototype.alive = function alive () { return this._colouredCell.age() > 0;

    };
  82. AsStandard.prototype.setAlive = function setAlive (alive) { if (alive) { this._colouredCell.setAge(this._colouredCell.age()

    + 1); } else this._colouredCell.setAge(0); return this; };
  83. AsStandard.prototype.nextAlive = function nextAlive () { return this._colouredCell.nextAge() > 0;

    }
  84. Object.keys(AsStandard.prototype) // => [ 'neighbours', 'setNeighbours', 'alive', 'setAlive', 'nextAlive' ]

  85. When%we%Upgrade%to%ColourCell 1. View'can'depend'upon'ColourCell,

  86. When%we%Upgrade%to%ColourCell 1. View'can'depend'upon'ColourCell,'and 2. Universe'can'depend'upon'AsStandard.

  87. Sweet! 1. Universe*is*decoupled*from*changes*to*Cell,

  88. Sweet! 1. Universe*is*decoupled*from*changes*to*Cell,*and 2. ColourCell*isn't*burdened*with*backwards*compa>bility.

  89. None
  90. AsStandard)is)an)Adaptor

  91. The$adapter$(sic)$pa/ern$is$a$ so2ware$design$pa/ern$that$allows$ the$interface$of$an$exis8ng$class$to$ be$used$from$another$interface...

  92. It#is#o'en#used#to#make#exis0ng# classes#work#with#others#without# modifying#their#source#code.

  93. TIMTOWTDI

  94. None
  95. good$programmers$borrow$from$smalltalk, Great&Programmers&Steal&from&C++

  96. function colourFromStandard (standard) { return new ColourCell() .setNeighbours(standard.neighbours()) .setAge(standard.alive() ?

    1 : 0); }
  97. function standardFromColour (colour) { return new StandardCell() .setNeighbours(colour.neighbours()) .setAlive(colour.age() >

    0); }
  98. None
  99. None
  100. Coproxies

  101. None
  102. None
  103. Approaching+the+End

  104. None
  105. adaptors Separate'Concerns

  106. None
  107. and$thus,$adaptors Isolate(Change

  108. None
  109. and$they Decouple(Modules

  110. None
  111. adaptors Solve&Compa+bility&Problems

  112. None
  113. but$they$also$gives$us$the$freedom$to Make%Changes,%Safely

  114. None
  115. when%we're%using%teams%to%build%so3ware%at%scale Adaptors)are)an)Important)Tool

  116. None
  117. None
  118. one$more$thing:

  119. None
  120. None
  121. when%you're%holding%a Version(Shaped.Hammer

  122. None
  123. all#changes#look#like Migra&on)Shaped/Nails

  124. None
  125. Packages(Have(Interlocking( Dependencies

  126. None
  127. Migra&ons*Can*Cascade you$never$know$how$much$work$a$single$upgrade$can$cause

  128. None
  129. migra&on)is)expensive,)so)semver)values Backwards)Compa.bility

  130. None
  131. "duck&typed"&backwards&compa2bility Holds&Progress&Back

  132. None
  133. breaking)compa.bilty Exposes'Clients'to'Bugs

  134. None
  135. adaptors(decouple Compa&bility,from,Progress

  136. None
  137. adaptors(provide Safe%Compa*bility

  138. None
  139. What%would%an%adaptor.aware% package%manager%look%like?

  140. An#Adaptor*Aware#Package#Manager 1. Interfaces,and,Implementa1ons,would,be,independantly, versioned,

  141. An#Adaptor*Aware#Package#Manager 1. Interfaces,and,Implementa1ons,would,be,independantly, versioned, 2. Interfaces,would,have,a,many<to<many,rela1onship,with, Implementa1ons,,mediated,by,Adaptors,

  142. An#Adaptor*Aware#Package#Manager 1. Interfaces,and,Implementa1ons,would,be,independantly, versioned, 2. Interfaces,would,have,a,many<to<many,rela1onship,with, implementa1ons,,mediated,by,adaptors, 3. Clients,would,specifiy,the,required,interface,and, implementa1on,

  143. An#Adaptor*Aware#Package#Manager 1. Interfaces,and,Implementa1ons,would,be,independantly, versioned, 2. Interfaces,would,have,a,many<to<many,rela1onship,with, implementa1ons,,mediated,by,adaptors, 3. Clients,would,specifiy,the,required,interface,and, implementa1on,,and

    4. Old,clients,can,benefit,from,bug,fixes,without,rewrites.
  144. None
  145. an#adaptor)aware#package#manager#could Change'the'Way'We'Grow'So0ware

  146. None
  147. and$that$provides Food$for$Thought for$a$very$long$,me$indeed

  148. None
  149. Tack%Så%Mycket!

  150. Reg$Braithwaite,$ GitHub,$Inc. raganwald.com,•,@raganwald Ar#pelag,*Stockholm,* September*19,*2014