Duck Typing, Compatibility, and the Adaptor Pattern

Duck Typing, Compatibility, and the Adaptor Pattern

Nordic JS, 2014

F8f7496052d3bf856e944aec64cfbb99?s=128

Reg Braithwaite

September 12, 2014
Tweet

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