Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Duck Typing, Compatibility, and the Adaptor Pat...
Search
Reg Braithwaite
September 12, 2014
Programming
7
10k
Duck Typing, Compatibility, and the Adaptor Pattern
Nordic JS, 2014
Reg Braithwaite
September 12, 2014
Tweet
Share
More Decks by Reg Braithwaite
See All by Reg Braithwaite
Courage
raganwald
0
100
Waste in Software Development
raganwald
0
160
First-Class Commands, the 2017 Edition
raganwald
1
160
Optimism and the Growth Mindset
raganwald
0
260
ember-concurrency: an experience report
raganwald
1
120
Optimism II
raganwald
0
370
Optimism
raganwald
0
1.9k
First-Class Commands: an unexpectedly fertile design pattern
raganwald
4
2.7k
JavaScript Combinators, the “six” edition
raganwald
8
1.3k
Other Decks in Programming
See All in Programming
Enabling DevOps and Team Topologies Through Architecture: Architecting for Fast Flow
cer
PRO
0
340
ヤプリ新卒SREの オンボーディング
masaki12
0
130
Remix on Hono on Cloudflare Workers
yusukebe
1
300
PHP でアセンブリ言語のように書く技術
memory1994
PRO
1
170
CSC509 Lecture 12
javiergs
PRO
0
160
ペアーズにおけるAmazon Bedrockを⽤いた障害対応⽀援 ⽣成AIツールの導⼊事例 @ 20241115配信AWSウェビナー登壇
fukubaka0825
6
2k
Tauriでネイティブアプリを作りたい
tsucchinoko
0
370
型付き API リクエストを実現するいくつかの手法とその選択 / Typed API Request
euxn23
8
2.3k
AWS Lambdaから始まった Serverlessの「熱」とキャリアパス / It started with AWS Lambda Serverless “fever” and career path
seike460
PRO
1
260
as(型アサーション)を書く前にできること
marokanatani
10
2.7k
『ドメイン駆動設計をはじめよう』のモデリングアプローチ
masuda220
PRO
8
540
よくできたテンプレート言語として TypeScript + JSX を利用する試み / Using TypeScript + JSX outside of Web Frontend #TSKaigiKansai
izumin5210
6
1.8k
Featured
See All Featured
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.3k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
27
840
The Cult of Friendly URLs
andyhume
78
6k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
6.8k
Teambox: Starting and Learning
jrom
133
8.8k
Bash Introduction
62gerente
608
210k
The World Runs on Bad Software
bkeepers
PRO
65
11k
GitHub's CSS Performance
jonrohan
1030
460k
Documentation Writing (for coders)
carmenintech
65
4.4k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
Transcript
None
the$art$of$the$javascript$metaobject$protocol Duck%Typing,%Compa1bility,%and The$Adaptor$Pa-ern
None
Duck%typing%is%a%style%of%typing%in% which%an%object's%methods%and% proper:es%determine%the%valid% seman:cs,
...rather'than'its'inheritance'from'a' par0cular'class'or'implementa0on'of' an'explicit'interface.
None
Tastes&Like&Fruit
None
Also%Tastes%Like%Fruit
"Write'code'that'depends'upon' tastes1like(fruit)'rather'than' depending'upon'is1a(fruit)."
None
duck%typing%is%a Compa&bility,Technique
None
A"Problem:
None
Conway's)Game)of)Life
"Conway's*Game*of*Life*is*a*two2 state*cellular*automata."
An#Implementa+on 1. Cells,
An#Implementa+on 1. Cells, 2. Universe,
An#Implementa+on 1. Cells, 2. Universe,.and 3. View.
Cell 1. Neighbours,
Cell 1. Neighbours,.and 2. State.
function StandardCell () { this._neighbours = []; this._alive = false;
}
StandardCell.prototype.neighbours = function neighbours (neighbours) { return this._neighbours; }; StandardCell.prototype.setNeighbours
= function setNeighbours (neighbours) { this._neighbours = neighbours.slice(0); return this; };
StandardCell.prototype.alive = function alive () { return this._alive; }; StandardCell.prototype.setAlive
= function setAlive (alive) { this._alive = alive; return this; };
None
moving'through Time
Cell 1. Neighbours, 2. State,2and 3. Transforma9on2of2State.
Universe 1. Neighbourhoods.(not.shown),
Universe 1. Neighbourhoods.(not.shown),.and 2. Transforma:on.of.States.
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; } };
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); }); };
None
drawing Life
View 1. Drawing*a*Cell:
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; };
View.prototype.cellColour = function cellColour (cell) { return cell.alive() ? WHITE
: BLACK; };
None
None
Ch#ch#ch#changes!
None
redesigning(for Colour
function ColourCell () { this._neighbours = []; this._age = 0;
} ColourCell.prototype.neighbours = StandardCell.prototype.neighbours; ColourCell.prototype.setNeighbours = StandardCell.prototype.setNeighbours;
ColourCell.prototype.age = function age () { return this._age; }; ColourCell.prototype.setAge
= function setAge (age) { this._age = age; return this; };
None
moving'through',me in#Colour
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; } };
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); }); };
None
drawing(life in#Colour
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() ]; }; // ...
None
something Doesn't(Fit
Object.keys(StandardCell.prototype) // => [ 'neighbours', 'setNeighbours', 'alive', 'setAlive', 'nextAlive' ]
Object.keys(ColourCell.prototype) // => [ 'neighbours', 'setNeighbours', 'age', 'setAge', 'nextAge' ]
The$state$of$a$cell$is$now$"age"$ instead$of$"aliveness."
None
These%changes%will Ripple&Through&Universe&and&View
our$problem$is$that Universe(is(coupled(to(Cell's( Interface
None
None
how$smart$people Solve&the&Problem
"Make&a&coloured&cell&behave&like&a& standard&cell."
ColourCell.prototype.alive = function alive () { return this._age > 0;
};
ColourCell.prototype.setAlive = function setAlive (alive) { if (alive) { this.setAge(this.age()
+ 1); } else this.setAge(0); return this; };
ColourCell.prototype.nextAlive = StandardCell.prototype.nextAlive;
Object.keys(ColourCell.prototype) // => [ 'neighbours', 'setNeighbours', 'age', 'setAge', 'nextAge', 'alive',
'setAlive', 'nextAlive' ]
Presto!(Backwards(Compa2bility. (it$"tastes$like$a$standard$cell")
None
universe(and(cell(are No#Longer#Coupled
None
At#What#Cost?
None
Increasing*Flexibility Increases(Complexity
None
None
there%is Another(Way
"Make&a&cell&that&behaves&like&a& Standard&Cell,&but&is&implemented&in& terms&of&a&Coloured&Cell."
function AsStandard (colouredCell) { this._colouredCell = colouredCell; } var tastesLikeAStandardCell
= new AsStandard(aColourCell);
AsStandard.prototype.neighbours = function neighbours () { return this._colouredCell.neighbours(); }; AsStandard.prototype.setNeighbours
= function setNeighbours (neighbours) { this._colouredCell.setNeighbours(neighbours); return this; };
AsStandard.prototype.alive = function alive () { return this._colouredCell.age() > 0;
};
AsStandard.prototype.setAlive = function setAlive (alive) { if (alive) { this._colouredCell.setAge(this._colouredCell.age()
+ 1); } else this._colouredCell.setAge(0); return this; };
AsStandard.prototype.nextAlive = function nextAlive () { return this._colouredCell.nextAge() > 0;
}
Object.keys(AsStandard.prototype) // => [ 'neighbours', 'setNeighbours', 'alive', 'setAlive', 'nextAlive' ]
When%we%Upgrade%to%ColourCell 1. View'can'depend'upon'ColourCell,
When%we%Upgrade%to%ColourCell 1. View'can'depend'upon'ColourCell,'and 2. Universe'can'depend'upon'AsStandard.
Sweet! 1. Universe*is*decoupled*from*changes*to*Cell,
Sweet! 1. Universe*is*decoupled*from*changes*to*Cell,*and 2. ColourCell*isn't*burdened*with*backwards*compa>bility.
None
AsStandard)is)an)Adaptor
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...
It#is#o'en#used#to#make#exis0ng# classes#work#with#others#without# modifying#their#source#code.
TIMTOWTDI
None
good$programmers$borrow$from$smalltalk, Great&Programmers&Steal&from&C++
function colourFromStandard (standard) { return new ColourCell() .setNeighbours(standard.neighbours()) .setAge(standard.alive() ?
1 : 0); }
function standardFromColour (colour) { return new StandardCell() .setNeighbours(colour.neighbours()) .setAlive(colour.age() >
0); }
None
None
Coproxies
None
None
Approaching+the+End
None
adaptors Separate'Concerns
None
and$thus,$adaptors Isolate(Change
None
and$they Decouple(Modules
None
adaptors Solve&Compa+bility&Problems
None
but$they$also$gives$us$the$freedom$to Make%Changes,%Safely
None
when%we're%using%teams%to%build%so3ware%at%scale Adaptors)are)an)Important)Tool
None
None
one$more$thing:
None
None
when%you're%holding%a Version(Shaped.Hammer
None
all#changes#look#like Migra&on)Shaped/Nails
None
Packages(Have(Interlocking( Dependencies
None
Migra&ons*Can*Cascade you$never$know$how$much$work$a$single$upgrade$can$cause
None
migra&on)is)expensive,)so)semver)values Backwards)Compa.bility
None
"duck&typed"&backwards&compa2bility Holds&Progress&Back
None
breaking)compa.bilty Exposes'Clients'to'Bugs
None
adaptors(decouple Compa&bility,from,Progress
None
adaptors(provide Safe%Compa*bility
None
What%would%an%adaptor.aware% package%manager%look%like?
An#Adaptor*Aware#Package#Manager 1. Interfaces,and,Implementa1ons,would,be,independantly, versioned,
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,
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,
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.
None
an#adaptor)aware#package#manager#could Change'the'Way'We'Grow'So0ware
None
and$that$provides Food$for$Thought for$a$very$long$,me$indeed
None
Tack%Så%Mycket!
Reg$Braithwaite,$ GitHub,$Inc. raganwald.com,•,@raganwald Ar#pelag,*Stockholm,* September*19,*2014