Cihat Salik

My Notes from Pro JavaScript Design Patterns

5 mins | July 27, 2023 (1y ago) | 
Learning
104 views

All Example Codes on the Book

Chapter-1: Expressive JavaScript

The flexibility of JavaScript.js
Functions as first-class objects.js
The mutability of objects.js

1
2Function.prototype.method = function(name, fn) { this.prototype[name] = fn; }
3
4var Anim = function() {}
5Anim.method('start', function() {
6  ....
7})
8
9Anim.method('close', function() {
10  ....
11})
12

This is very similar to the Extension feature in Swift.

1
2// This version allows the calls to be chained together
3
4Function.prototype.method = function(name, fn) {
5this.prototype[name] = fn;
6return this;
7}
8

JavaScript has function-level scope, this means that a variable defined within a function is not accessible outside of it. JavaScript is also lexically scoped, which means that functions run in the scope they are deinfed in, not the scope they are executed in.

All objects are mutable. If you have a reference to an object, you can change its properties.

1
2var myObject = {
3value: 0,
4increment: function(inc) {
5this.value += typeof inc === 'number' ? inc : 1;
6}
7}
8
9myObject.increment();
10console.log(myObject.value); // 1
11
1
2function displayError(message) {
3displayError.numTimesExecuted++;
4console.log(message);
5}
6
7displayError.numTimesExecuted = 0;
8
1
2function Person(name, age) {
3this.name = name;
4this.age = age;
5}
6
7Person.prototype.sayName = function() {
8console.log(this.name);
9}
10
11Person.prototype = {
12getName: function() {
13return this.name;
14},
15getAge: function() {
16return this.age;
17}
18}
19
20var alice = new Person('John', 30);
21var bill = new Person('Bill', 40);
22person.sayName();
23
24bill.displayGreeting = function() {
25console.log('Hello, I am ' + this.name);
26}
27
28bill.displayGreeting();
29
30// This example shows that you can add properties to an object after it has been created.
31// Also just bill has the displayGreeting method, alice does not.
32

Chapter-2: Interfaces

Object oriented language have interfaces keyword that easy allows you to define an interface that other objects must implement. JavaScript does not have this feature, but you can simulate it by defining a method that checks for the presence of certain methods and properties.

Three ways to check for the presence of a method or property:

  1. Comments
  2. Duck Typing
  3. Attribute Checking

Comments

1
2
3/*
4interface Composite {
5function add(child);
6function remove(child);
7function getChild(index);
8}
9
10interface FormItem {
11function save();
12}
13
14*/
15
16var CompositeForm = function(id, method, action) { // implements Composite, FormItem
17....
18}
19
20// Implement the Composite interface
21
22CompositeForm.prototype.add = function(child) {
23....
24}
25
26CompositeForm.prototype.remove = function(child) {
27....
28}
29
30CompositeForm.prototype.getChild = function(index) {
31....
32}
33
34// Implement the FormItem interface
35
36CompositeForm.prototype.save = function() {
37....
38}
39
40// This doesn't emulate the interface functionality very well. There is no checking to ensure that CompositeForm implements the methods defined in the interface.
41// No error will be thrown if you forget to implement a method.
42

Emulating Interfaces with Duck Typing

1
2
3/*
4interface Composite {
5function add(child);
6function remove(child);
7function getChild(index);
8}
9
10interface FormItem {
11function save();
12}
13*/
14
15var CompositeForm = function(id, method, action) {
16this.implementsInterfaces = ['Composite', 'FormItem'];
17...
18}
19....
20
21function addForm(formInstance) {
22if (!implements(formInstance, 'Composite', 'FormItem')) {
23throw new Error('Object does not implement a required interface.');
24}
25}
26
27
28
29// This is a better way to emulate interfaces. The addForm function checks to make sure that the object passed in implements the required interfaces.
30// The CompositeForm constructor also checks to make sure that the object implements the required interfaces.
31
32function implements(object) {
33for(var i = 1; i < arguments.length; i++) {
34var interfaceName = arguments[i];
35var interfaceFound = false;
36
37for(var j = 0; j < object.implementsInterfaces.length; j++) {
38  if(object.implementsInterfaces[j] == interfaceName) {
39    interfaceFound = true;
40    break;
41  }
42}
43
44if(!interfaceFound) {
45  return false; // An interface was not found.
46}
47}
48
49return true; // All interface were found.
50}
51

This function checks to make sure that the object passed in implements all of the interfaces passed in. In this exmaple, CompositeForm implements both the Composite and FormItem interfaces. The main drawback to this approach is that you are not ensuring that the object implements the methods defined in the interface. You only know if it says it implements the interface.

Emulating Interfaces with Attribute Checking

1
2
3var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
4var FormItem = new Interface('FormItem', ['save']);
5
6// CompositeForm class
7
8var CompositeForm = function(id, method, action) {
9...
10}
11
12....
13
14fucntion addForm(formInstance) {
15ensureImplements(formInstance, Composite, FormItem);
16// This function will throw an error if the object passed in does not implement the required interfaces.
17...
18}
19

A class never declares which interface it implements, reducing the reusability of the class and not self-documenting like the other approaches. It requires a helper class, Interface, and a helper function, ensureImplemtns. It does not check the nomes or numbers of arguments used in the methods or their types, only that the method has the method has the correct name.

The Interface Inplements for This book

Using a combination of the first(Comments) and third(Attribute Checking) approaches.

1
2
3// Interfaces - Defines interface as commands to check for their existence.
4
5var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
6var FormItem = new Interface('FormItem', ['save']);
7
8// CompositeForm class
9
10var CompositeForm = function(id, method, action) {
11...
12}
13
14....
15
16fucntion addForm(formInstance) {
17Interface.ensureImplements(formInstance, Composite, FormItem);
18// This function will throw an error if the object passed in does not implement the required interfaces.
19// All code beneath this line will be executed only if the checks pass.
20...
21}
22// Interface.ensureImplements provides a strict check. If a problem is found, an error will be thrown, which can either be caught and handled or alllowed to halt execution.
23// Either way, the problem will be found and fixed.
24
25
1
2
3// Interface.js - Defines Interface constructor.
4
5var Interface = function(name, methods) {
6if(arguments.length != 2) {
7throw new Error('Interface constructor called with ' + arguments.length + 'arguments, but expected exactly 2.');
8}
9
10this.name = name;
11this.methods = methods;
12
13for(var i = 0, len = methods.length; i < len; i++) {
14if(tpeof methods[i] !== 'string') {
15  throw new Error('Interface constructor expects method names to be passed in as a string.');
16}
17this.methods.push(methods[i]);
18}
19}
20
21// Static class method.
22
23Interface.ensureImplements = function(object) {
24if(arguments.length < 2) {
25throw new Error("Function Interface.ensureImplemtns called with" + arguments.length + "arguments, but expected at least 2.");
26}
27
28for(var i = 1, len = arguments.length; i < len; i++) {
29var interface = arguments[i];
30if(interface.constructor !== Interface) {
31  throw new Error("Function Interface.ensureImplements expects arguments" + "two and above to be instancs instaces of Interface.");;
32}
33
34for(var j = 0; methodsLen = interface.method.length; j < methodsLen; j++) {
35  var methods = interface.methods[j];
36  if(!object[methods] || typeof object[methods] !== 'function') {
37    throw new Error("Function Interface.ensureImplements: object" + "does not implement the " + interface.name + " interface. Method " + methods + " was not found.");
38  }
39}
40}
41}
42
43

The Interface constructor is used to define an interface. It takes a name and an array of method names. The constructor checks to make sure that the correct number of arguments are passed in and that the arguments are of the correct type. The Interface.ensureImplements method is a static method that is used to check that an object implements the required interface. It takes an object as its first argument and one or more interfaces as subsequent arguments. The method checks to make sure that the correct number of arguments are passed in and that the arguments are of the correct type. The method then loops through each method in the interface and checks to make sure that the object implements that method. If the object does not implement the method, an error is thrown.

It becomes most beneficial when you start implementing complex systems using design patterns. It might seem like interfaces reduce JavaScript's flexibility, but they actually improve it by allowing your objects to be more loosely coupled. Your functions can be more flexible because you can pass in arguments of any type and still ensure that only objects with the needed method will be used.

Chapter-3: Encapsulation


Reading