Download presentation
Presentation is loading. Please wait.
1
Advanced Design Patterns
Builders and PubSub
2
The Builder Pattern
3
“If you have a procedure with ten parameters, you probably missed some
– Alan Perlis st Turing Award winner
4
Example: A Class for “Nutrition Facts”
What fields should a NutritionFacts class have? Salmon Eggs Benedict Kale
5
The NutritionFacts Class
class NutritionFacts { constructor(servingSize, servings, calories, fat, sodium, carbs) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbs = carbs; } Let’s just focus on these six fields. In a real system, we would probably need to address several other kinds of nutrition!
6
The NutritionFacts Class
let coke = new NutritionFacts("1 can", "1 can", 140, 0, 39, 45); Did we get this right? Check the previous slide. Deliberate mistake
7
The Builder Pattern class NutritionFactsBuilder { ... constructor() {
this.servingSize = ""; this.servings = ""; this.calories = 0; this.fat = 0; this.sodium = 0; this.carbs = 0; } setServingSize(s) { this.servingSize = s; setServings(s) { this.servings = s; ... build() { if (this.servingsSize === "") { console.log("ERROR: Must specify serving size"); return; } let servings = this.servings === "" ? this.servingSize : this.servings; return new NutritionFacts( this.servingSize, servings, this.calories, this.fat, this.sodium, this.carbs);
8
Using a Builder let cokeBuilder = new NutritionBuilder();
cokeBuilder.setServing("1 can"); cokeBuilder.setSodium(45); cokeBuilder.setCarbs(39); cokeBuilder.setCalories(140); let coke = cokeBuilder.build(); Obvious what 39, 45, 140 mean Can initialize in any order (e.g., setCalories is called last above) Can use default values (e.g., servingSize) Can create several cokes from the same builder Deliberate mistake is corrected.
9
Structuring Sequential Operations: The Fluent Pattern
10
The Fluent Builder pattern
class NutritionFactsBuilder { ... setServingSize(s) { this.servingSize = s; return this; } setServings(s) { this.servings = s; let coke = new NutritionBuilder() .setServing("1 can") .setSodium(45) .setCarbs(39) .setCalories(140) .build(); A common design pattern. Less typing required. Arjun: I don’t know why it is called “fluent”. If anyone does, please add an explanation to the slide.
11
Fluent Image Processing
Sharp Library:
12
Fluent Array Filtering For Datasets
let a = [{sighted: 1850, name:'J9-P5', city:'Moscow'} ... ]; let f = new FluentMeteorFilter(a); let a2 = f.sightedBefore(1920) .nameStartsWith('J') .filter(function(p) { return p.city === 'New York'; }) .get();
13
Fluent Array Filtering For Datasets
class FluentMeteorFilter { constructor(a) { this.a = a; } sightedBefore(y) { return new FluentMeteorFilter(a.filter(function(m) { m.sighted < 1850; })); nameStartsWith(c) { ??? filter(f) { return new FluentMeteorFilter(a.filter(f)); get() { return this.a; let a = [{sighted: 1850, name:'J9-P5', city:'Moscow'}]; let f = new FluentMeteorFilter(a); let a2 = f.sightedBefore(1920) .nameStartsWith('J') .filter(function(p) { return p.city === 'Moscow'; }) .get(); console.log(a2);
14
The Publish-Subscribe (PubSub) Pattern
15
Problem: Synchronizing an Address Book and a Phone List
class PhoneList { constructor() { this.data = [ ]; } class AddressBook { this.data = [ ] ; insert(name, phone, , ...) { this.data.push({ name: name, phone: phone, , ... }); const phoneList = new PhoneList(); const addressBook = new AddressBook(); We have two objects: an address book that contains names, phone numbers, addresses, birthdays, etc. a phone list that only contains names and phone numbers We want to update the address book and have the phone list update automatically
16
Attempt 1 class PhoneList { constructor() { this.data = [ ]; } onUpdate(entries) { this.data = entries.map(function(entry) { return { name: entry.name, phone: entry.phone }; }); class AddressBook { constructor(phoneList) { this.data = [ ] ; this.phoneList = phoneList; } insert(name, phone, , ...) { this.data.push({ name: name, phone: phone, , ... }); this.phoneList.onUpdate(this.data); const phoneList = new PhoneList(); const addressBook = new AddressBook(phoneList); In this solution, the address book stores a reference to the phone list, and the insert method calls passes the data to the phone list after the update.
17
Attempt 1 tightly couples the AddressBook and PhoneList
What if we want an list instead? What if we want a second phone list?
18
Attempt 2: Observers Key Idea: Instead of passing a phone list to the address book, the address book maintains an array of observers that subscribe to address book updates. class PhoneList { constructor() { this.data = [ ]; } observe(entries) { this.data = entries.map(function(entry) { return { name: entry.name, phone: entry.phone }; }); class List { constructor() { this.data = [ ]; } observe(entries) { this.data = entries.map(function(entry) { return { name: entry.name, entry. }; });
19
Attempt 2: Observers class AddressBook { constructor() {
this.data = [ ] ; this.observers = [ ]; } subscribe(observer) { this.observers.push(observer); insert(name, phone, , ...) { this.data.push({ name: name, phone: phone, , ... }); for (let i = 0; i < this.observers.length; ++i) { this.observers[i].observe(this.data); };
20
Attempt 2 loosely couples the AddressBook and its observers
What if we want to reuse this idea for something that is not an address book? (E.g., synchronize a grade book that has grades for all assignments with a list of final letter grades) Key Idea: Package the code for maintaining a list of observables in a separate class.
21
Attempt 3: Reusable Observers
class Observable { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); update(newValue) { for (let i = 0; i < this.observers.length; ++i) { this.observers[i](newValue); }; There is nothing AddressBook-specific in the Observable class. class AddressBook { constructor() { this.data = [ ] ; this.observable = new Observable(); } insert(name, phone, ,...) { this.data.push({ name: name, phone: phone, ... }); this.observable.update(this.data); Small change that is explained in the next slide: the observer is just a function, instead of an object with an observe method.
22
Attempt 3: Reusable Observers
class Observable { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); update(newValue) { for (let i = 0; i < this.observers.length; ++i) { this.observers[i](newValue); }; This code is the same as the last slide. An observer is a function that receives the new value. const phoneList = new PhoneList(); const addressBook = new AddressBook(); addressBook.observable.subscribe(function(data) { phoneList.observe(data); }); console.log(data.length + " entries available ");
23
Observers are Functions
class Observable { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); update(newValue) { for (let i = 0; i < this.observers.length; ++i) { this.observers[i](newValue); }; This code is the same as the last slide. let obs = new Observable(); obs.subscribe(function(x) { if (x % 2 === 0) { console.log(x); } }); obs.update(1); obs.update(20); obs.update(3); obs.update(44); What does this program display? In general, it displays the even numbers.
24
Advanced PubSub Object-oriented meets functional programming
25
What is an Observable? Low-level explanation: an object that has a list of observers and calls them when it receives a new value High-level explanation: a value that when changed, automatically propagates the change to functions that depend on the value Alternate high-level explanation: a sequence of values with an unknown length. (i.e., we may not know how many times the program will call the update method). When you have a sequence of values, e.g., an Array, you can define higher-order operations, such as map and filter.
26
Map for Observers What does this program display?
class Observable { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); update(newValue) { for (let i = 0; i < this.observers.length; ++i) { this.observers[i](newValue); }; map(f) { const result = new Observable(); this.subscribe(function(x) { result.update(f(x)); }); return result; let obs1 = new Observable(); let obs2 = obs1.map(function(x) { return x * 2; }); obs2.subscribe(function(x) { console.log(x); obs1.update(10); obs1.update(20); What does this program display? 20, 40.
27
Filter for Observers What does this program display?
class Observable { ... filter(f) { const result = new Observable(); this.subscribe(function(x) { if (f(x)) { result.update(x); } }); return result; let obs1 = new Observable(); let obs2 = obs1.filter(function(x) { return x % 2 === 0; }); obs2.subscribe(function(x) { console.log(x); obs1.update(1); obs1.update(2); obs1.update(3); obs1.update(4); What does this program display? 2, 4
28
Reduce for Observers Exercise: Write reduce for observers
It cannot be done.
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.