Who here has seen something like this before?
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`I am ${this.name}.`);
}
}
class Cat extends Animal {
speak() {
console.log(`I like to meow and poop on your bed.`);
}
}
“The goal of design is to arrange your software so that it does what it is supposed to right now and is also easy to change later.
Creating this quality of easy changeability reveals the craft of programming.” - Sandi Metz
This is a bad name.
“I'm sorry that I long ago coined the term 'objects' for this topic because it gets many people to focus on the lesser idea.” - Alan Kay
cat // object
.speak() // message
“You don’t send messages because you have objects, you have objects because you send messages”
- Sandi Metz
Production code ahead.
Ish might get real.
class EmailForm {
render() { ... }
submit() { ... }
validate() { ... }
trackUserDataWithoutConsent() { ... }
}
class EmailForm {
// Mr. EmailForm how do you look in the DOM?
// Pretty good thanks
render() { ... }
// Mr. EmailForm can you please submit yourself?
// Yep.
submit() { ... }
// Mr. EmailForm can you validate something?
// Hmm, maybe. Depends what I'm validating.
validate() { ... }
// Mr. EmailForm can you please track everything?
// Maybe not, ask Mr. Tracky
trackUserDataWithoutConsent() { ... }
}
function printReport(report) {
// our printer can't handle emojis so we need to remove them
report = report.replace(/!&!(%^%^#*)/g, '');
console.log(report);
}
function printReport(report) {
console.log(prepareForPrint(report));
}
function prepareForPrint(printable) {
const BLACKLIST = /!&!(%^%^#*)/g;
return printable.replace(BLACKLIST, '');
}
* note that my made up regex might not work as advertised
Recognition is your most important first step! An object has a dependency when it knows:
this
import Turkey from './Turkey.js';
import Soup from './Soup.js';
class Dinner {
serve() {
new Turkey().serve();
new Soup().serve(3, true); // 3 scoops with garnish
}
}
import Turkey from './Turkey.js';
class Dinner {
constructor(turkey) {
this.turkey = turkey;
}
serve() {
this.turkey.serve();
}
}
import Turkey from './Turkey.js';
class Dinner {
serve() {
this.turkey.serve();
}
// Isolate the evil invader
get turkey() {
this.turkeyCache = this.turkeyCache || new Turkey();
return this.turkeyCache;
}
}
// Dependency madness
soup.serve(3, true) // 3 scoops with garnish added
// So clear
soup.serve({scoops: 3, withGarnish: true})
// Might be cool?
soup.serve({scoops: 3, extras: ['garnish']})
“Think about interfaces. Create them intentionally. It is your interfaces more than all of your tests and any of your code, that define your application and determine its future.”
- Sandi, of course
Public interfaces should:
// Developers use underscores
class Spaceship {
// public
fly() { ... }
refuel() { ... }
// private
_calculateRemainingFuel() { ... }
_preflightMusicCheck() { ... }
}
// Revealing module pattern
const spaceship = (() => {
const fly = () => { ... }
const refuel = () => { ... }
// these two functions are only available in closure
const calculateRemainingFuel = () => { ... }
const preflightMusicCheck = () => { ... }
// the IIFE returns the public API
return {
fly,
refuel
};
})();
// The future
// https://github.com/tc39/proposal-private-methods
class Spaceship {
// public
fly() { ... }
refuel() { ... }
// private
#calculateRemainingFuel() { ... }
#preflightMusicCheck() { ... }
}
Evolution goes from procedural to OO as trust improves:
import Spaceship from './Spaceship.js';
class Astronaut {
constructor() {
this.spaceship = new Spaceship();
}
// I know what I want and I know how you do it
goToSpaceNow() {
if (this.spaceship.fuel < 10) {
this.spaceship.refuel();
}
if (this.spaceship.flightReady) {
this.spaceship.fly = true;
} else (
this.spaceship.preflightMusicCheck();
this.spaceship.fly = true;
)
}
}
import Spaceship from './Spaceship.js';
class Astronaut {
constructor() {
this.spaceship = new Spaceship();
}
// I know what I want and I know what you do
goToSpaceNow() {
this.spaceship.refuel();
this.spaceship.preflightMusicCheck();
this.spaceship.startEngine();
this.spaceship.takeOff();
}
}
import Spaceship from './Spaceship.js';
class Astronaut {
constructor() {
this.spaceship = new Spaceship();
}
// I know what I want and I trust you to do your part
// Notice the shrinking public API
goToSpaceNow() {
this.spaceship.fly({astronaut: this});
}
}