design-patterns-refactoring-guru on github

Builder Pattern

without Director

ConcreteBuilder1 calls reset() after getProduct()
builder.producePartA();
const product = builder.getProduct();
chai_1.expect(product).to.deep.equal({
    parts: ['PartA1'],
});
chai_1.expect(builder.getProduct()).to.deep.equal({
    parts: [],
});
can build a custom project
builder.producePartA();
builder.producePartC();
const product = builder.getProduct();
chai_1.expect(product).to.deep.equal({
    parts: ['PartA1', 'PartC1'],
});
product.listParts();
chai_1.expect(this.logStub).to.have.been.calledOnceWithExactly('Product parts: PartA1, PartC1\n');

with Director

builds Standard MVP
director.buildMinimalViableProduct();
const product = builder.getProduct();
chai_1.expect(product).to.deep.equal({
    parts: ['PartA1'],
});
product.listParts();
chai_1.expect(this.logStub).to.have.been.calledOnceWithExactly('Product parts: PartA1\n');
builds Standard full featured product
director.buildFullFeaturedProduct();
const product = builder.getProduct();
chai_1.expect(product).to.deep.equal({
    parts: ['PartA1', 'PartB1', 'PartC1'],
});
product.listParts();
chai_1.expect(this.logStub).to.have.been.calledOnceWithExactly('Product parts: PartA1, PartB1, PartC1\n');

index.ts

runs default command
if (context.timeout)
    this.timeout(context.timeout);
return run.call(this);
runs hello --name builder
if (context.timeout)
    this.timeout(context.timeout);
return run.call(this);

Iterator-Visitor Combination

WordsDisplayComponent

Component.display returns empty string if nothing to display
this.component.display();
chai_1.expect(this.logStub).to.have.been.calledWith('');
Component.display returns html string of words list
this.component.accept(new iterator_visitor_1.HTMLDisplayVisitor());
this.component.display();
chai_1.expect(this.logStub).to.have.been.calledWith(`<html><body><ul>
<li>${wordsArr[0]}</li>
<li>${wordsArr[1]}</li>
<li>${wordsArr[2]}</li>
<li>${wordsArr[3]}</li>
</ul></body></html>`);
Component.display returns html string of reversed words list
this.component.accept(new iterator_visitor_1.HTMLReverseDisplayVisitor());
this.component.display();
chai_1.expect(this.logStub).to.have.been.calledWith(`<html><body><ul>
<li>${wordsArr[3]}</li>
<li>${wordsArr[2]}</li>
<li>${wordsArr[1]}</li>
<li>${wordsArr[0]}</li>
</ul></body></html>`);
Component.display returns json string of words list
this.component.accept(new iterator_visitor_1.JSONDisplayVisitor());
this.component.display();
chai_1.expect(this.logStub).to.have.been.calledWith(`{"items":["${wordsArr[0]}","${wordsArr[1]}","${wordsArr[2]}","${wordsArr[3]}"]}`);

Iterator Pattern

Collection

getItems() works after addItem()
chai_1.expect(this.words.getItems()).to.deep.equal([]);
addFour(this.words);
chai_1.expect(this.words.getItems()).to.deep.equal(wordsArr);
getCount() works after addItems()
chai_1.expect(this.words.getCount()).to.equal(0);
addFour(this.words);
chai_1.expect(this.words.getCount()).to.equal(4);

Iterator

valid() is true before end of collection, false at end
addFour(this.words);
const iterator = this.words.getIterator();
const collection = [];
while (iterator.valid()) {
    iterator.next();
    collection.push(iterator.valid());
}
chai_1.expect(collection).to.deep.equal([true, true, true, false]);
valid() is true before end of collection, false at end for reverse iterator
addFour(this.words);
const reverseIterator = this.words.getReverseIterator();
const collection = [];
while (reverseIterator.valid()) {
    reverseIterator.next();
    collection.push(reverseIterator.valid());
}
chai_1.expect(collection).to.deep.equal([true, true, true, false]);
iterates in insertion order
const iterator = this.words.getIterator();
let collection = [];
while (iterator.valid()) {
    collection.push(iterator.next());
}
chai_1.expect(collection.length).to.equal(0);
addFour(this.words);
collection = [];
while (iterator.valid()) {
    collection.push(iterator.next());
}
chai_1.expect(collection).to.deep.equal(wordsArr);
iterates in reverse order
addFour(this.words);
const reverseIterator = this.words.getReverseIterator();
const collection = [];
while (reverseIterator.valid()) {
    collection.push(reverseIterator.next());
}
chai_1.expect(collection).to.deep.equal(reversedWordsArr);
gets current item when current() is called
addFour(this.words);
const iterator = this.words.getIterator();
chai_1.expect(iterator.current()).to.equal(wordsArr[0]);
iterator.next();
chai_1.expect(iterator.current()).to.equal(wordsArr[1]);
iterator.next();
chai_1.expect(iterator.current()).to.equal(wordsArr[2]);
iterator.next();
chai_1.expect(iterator.current()).to.equal(wordsArr[3]);
gets key (index) when key() is called
addFour(this.words);
const iterator = this.words.getIterator();
chai_1.expect(iterator.key()).to.equal(0);
iterator.next();
chai_1.expect(iterator.key()).to.equal(1);
iterator.next();
chai_1.expect(iterator.key()).to.equal(2);
iterator.next();
chai_1.expect(iterator.key()).to.equal(3);
rewind() moves position to beginning of collection
addFour(this.words);
const iterator = this.words.getIterator();
// move to end of collection
while (iterator.valid()) {
    iterator.next();
}
chai_1.expect(iterator.valid()).to.be.false;
iterator.rewind();
chai_1.expect(iterator.current()).to.equal(wordsArr[0]);
rewind() moves position to end of collection if reversed
addFour(this.words);
const reverseIterator = this.words.getReverseIterator();
// move to beginning of collection
while (reverseIterator.valid()) {
    reverseIterator.next();
}
chai_1.expect(reverseIterator.valid()).to.be.false;
reverseIterator.rewind();
chai_1.expect(reverseIterator.current()).to.equal(wordsArr[3]);

Observer

ConcreteSubject

notify() calls no observers if none set
this.subject.notify();
chai_1.expect(this.logStub).to.have.been.calledOnceWithExactly('Subject: Notifying observers...');
attach() logs a failure if observer already attached
this.subject.attach(this.observerA);
this.subject.attach(this.observerA);
chai_1.expect(this.logStub).to.have.callCount(2);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 0)).to.deep.equal([
    'Subject: Attached an observer.',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 1)).to.deep.equal([
    'Subject: Observer has been attached already.',
]);
notify() calls one observer if attached
this.subject.attach(this.observerA);
this.subject.notify();
chai_1.expect(this.logStub).to.have.callCount(3);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 0)).to.deep.equal([
    'Subject: Attached an observer.',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 1)).to.deep.equal([
    'Subject: Notifying observers...',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 2)).to.deep.equal([
    'ConcreteObserverA: Reacted to the event.',
]);
notify() calls two observers if attached
this.subject.attach(this.observerA);
this.subject.attach(this.observerB);
this.subject.notify();
chai_1.expect(this.logStub).to.have.callCount(5);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 0)).to.deep.equal([
    'Subject: Attached an observer.',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 1)).to.deep.equal([
    'Subject: Attached an observer.',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 2)).to.deep.equal([
    'Subject: Notifying observers...',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 3)).to.deep.equal([
    'ConcreteObserverA: Reacted to the event.',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 4)).to.deep.equal([
    'ConcreteObserverB: Reacted to the event.',
]);
Changes state and notifies on someBusinessLogic()
this.subject.attach(this.observerA);
this.subject.someBusinessLogic();
chai_1.expect(this.logStub).to.have.callCount(4);
chai_1.expect(this.logStub).to.have.been.calledWith("\nSubject: I'm doing something important.");
chai_1.expect(this.logStub).to.have.been.calledWith('Subject: My state has just changed to: 11');
Detaches an observer
this.subject.attach(this.observerA);
this.subject.detach(this.observerA);
chai_1.expect(this.logStub).to.have.been.calledWith('Subject: Detached an observer.');
Logs a failure message if a non-attached observer is tried to detach
this.subject.attach(this.observerA);
this.subject.detach(this.observerB);
chai_1.expect(this.logStub).to.have.been.calledWith('Subject: Nonexistent observer.');

ConcreteObserverA

logs on update() if component.state < 3
this.subject.attach(this.observerA);
this.subject.notify();
chai_1.expect(this.logStub).to.have.callCount(3);
chai_1.expect(this.logStub).to.have.been.calledWith('ConcreteObserverA: Reacted to the event.');
does not log on update() if component.state >= 3
this.subject.attach(this.observerA);
this.subject.someBusinessLogic();
this.subject.notify();
chai_1.expect(this.logStub).not.to.have.been.calledWith('ConcreteObserverA: Reacted to the event.');

ConcreteObserverB

logs on update() if component.state === 0
this.subject.attach(this.observerB);
this.subject.notify();
chai_1.expect(this.logStub).to.have.callCount(3);
chai_1.expect(this.logStub).to.have.been.calledWith('ConcreteObserverB: Reacted to the event.');
logs on update() if component.state >=2
this.subject.attach(this.observerB);
this.subject.someBusinessLogic();
this.subject.notify();
chai_1.expect(this.logStub).to.have.callCount(7);
chai_1.expect(this.logStub).to.have.been.calledWith('ConcreteObserverB: Reacted to the event.');
does not log on update() if component.state === 1
this.mathRandomStub.returns(1 / 11);
this.subject.attach(this.observerB);
this.subject.someBusinessLogic();
this.subject.notify();
chai_1.expect(this.logStub).to.have.callCount(5);
chai_1.expect(this.logStub).not.to.have.been.calledWith('ConcreteObserverB: Reacted to the event.');

Visitor Pattern

acceptVisitorForAll() accepts visitor1 for both components
const visitor1 = new visitor_1.ConcreteVisitor1();
visitor_1.acceptVisitorForAll(components, visitor1);
chai_1.expect(this.logStub).to.have.been.calledTwice;
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 0)).to.deep.equal([
    'A + ConcreteVisitor1',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 1)).to.deep.equal([
    'B + ConcreteVisitor1',
]);
acceptVisitorForAll() accepts visitor2 for both components
const visitor2 = new visitor_1.ConcreteVisitor2();
visitor_1.acceptVisitorForAll(components, visitor2);
chai_1.expect(this.logStub).to.have.been.calledTwice;
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 0)).to.deep.equal([
    'A + ConcreteVisitor2',
]);
chai_1.expect(utils_test_1.getArgsForCall(this.logStub, 1)).to.deep.equal([
    'B + ConcreteVisitor2',
]);