From e19ed402901d3727dc2fbda5ed18b068ef1998f1 Mon Sep 17 00:00:00 2001 From: Tae-geon Choi Date: Mon, 20 Jun 2022 22:22:07 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=2013~16=EC=9E=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chapters/chapter12.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chapters/chapter12.ts b/src/chapters/chapter12.ts index f5e6faa..d771515 100644 --- a/src/chapters/chapter12.ts +++ b/src/chapters/chapter12.ts @@ -36,8 +36,8 @@ export class Money implements Expression { return `${this.amount} ${this._currency}`; } - public plus(added: Money): Expression { - return new Money(this.amount + added.amount, this._currency); + public plus(addend: Money): Expression { + return new Money(this.amount + addend.amount, this._currency); } } From 3d5ad334039ec6b1fd521f634628e9a46495c7de Mon Sep 17 00:00:00 2001 From: Tae-geon Choi Date: Mon, 20 Jun 2022 22:22:42 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=2013~16=EC=9E=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chapters/chapter13.test.ts | 111 ++++++++++++++ src/chapters/chapter13.ts | 216 ++++++++++++++++++++++++++ src/chapters/chapter14.test.ts | 135 ++++++++++++++++ src/chapters/chapter14.ts | 271 +++++++++++++++++++++++++++++++++ src/chapters/chapter15.test.ts | 79 ++++++++++ src/chapters/chapter15.ts | 225 +++++++++++++++++++++++++++ src/chapters/chapter16.test.ts | 99 ++++++++++++ src/chapters/chapter16.ts | 119 +++++++++++++++ 8 files changed, 1255 insertions(+) create mode 100644 src/chapters/chapter13.test.ts create mode 100644 src/chapters/chapter13.ts create mode 100644 src/chapters/chapter14.test.ts create mode 100644 src/chapters/chapter14.ts create mode 100644 src/chapters/chapter15.test.ts create mode 100644 src/chapters/chapter15.ts create mode 100644 src/chapters/chapter16.test.ts create mode 100644 src/chapters/chapter16.ts diff --git a/src/chapters/chapter13.test.ts b/src/chapters/chapter13.test.ts new file mode 100644 index 0000000..24c64c7 --- /dev/null +++ b/src/chapters/chapter13.test.ts @@ -0,0 +1,111 @@ +import { Bank, Money } from "./chapter13"; +import type { Expression } from "./chapter13"; +import { Sum } from "./chapter13"; + +/* +// attempt 1 +describe("chapter 13", () => { + test("multiplication", () => { + const five: Money = Money.dollar(5); + expect(Money.dollar(10)).toEqual(five.times(2)); + expect(Money.dollar(15)).toEqual(five.times(3)); + }); + + test("francMultiplication", () => { + const five = Money.franc(5); + expect(Money.franc(10)).toEqual(five.times(2)); + expect(Money.franc(15)).toEqual(five.times(3)); + }); + + test("equality", () => { + expect(Money.dollar(5).equals(Money.dollar(5))).toBe(true); + expect(Money.dollar(5).equals(Money.dollar(6))).toBe(false); + expect(Money.franc(5).equals(Money.dollar(5))).toBe(false); + }); + + test("currency", () => { + expect(Money.dollar(1).currency()).toBe("USD"); + expect(Money.franc(1).currency()).toBe("CHF"); + }); + + test("simpleAddition", () => { + const five = Money.dollar(5); + const sum: Expression = five.plus(five); + const bank = new Bank(); + const reduced = bank.reduce(sum, "USD"); + expect(Money.dollar(10)).toEqual(reduced); + }); + + test("plusReturnsSum", () => { + const five = Money.dollar(5); + const result = five.plus(five); + const sum: Sum = result; + + expect(five).toEqual(sum.augend); + expect(five).toEqual(sum.addend); + }); + + test("reduceSum", () => { + const sum = new Sum(Money.dollar(3), Money.dollar(4)); + const bank = new Bank(); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(7)).toEqual(result); + }); +}); +*/ + +// attempt 2 +describe("chapter 13", () => { + test("multiplication", () => { + const five: Money = Money.dollar(5); + expect(Money.dollar(10)).toEqual(five.times(2)); + expect(Money.dollar(15)).toEqual(five.times(3)); + }); + + test("francMultiplication", () => { + const five = Money.franc(5); + expect(Money.franc(10)).toEqual(five.times(2)); + expect(Money.franc(15)).toEqual(five.times(3)); + }); + + test("equality", () => { + expect(Money.dollar(5).equals(Money.dollar(5))).toBe(true); + expect(Money.dollar(5).equals(Money.dollar(6))).toBe(false); + expect(Money.franc(5).equals(Money.dollar(5))).toBe(false); + }); + + test("currency", () => { + expect(Money.dollar(1).currency()).toBe("USD"); + expect(Money.franc(1).currency()).toBe("CHF"); + }); + + test("simpleAddition", () => { + const five = Money.dollar(5); + const sum: Expression = five.plus(five); + const bank = new Bank(); + const reduced = bank.reduce(sum, "USD"); + expect(Money.dollar(10)).toEqual(reduced); + }); + + test("plusReturnsSum", () => { + const five = Money.dollar(5); + const result = five.plus(five); + const sum: Sum = result; + + expect(five).toEqual(sum.augend); + expect(five).toEqual(sum.addend); + }); + + test("reduceSum", () => { + const sum = new Sum(Money.dollar(3), Money.dollar(4)); + const bank = new Bank(); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(7)).toEqual(result); + }); + + test("reduceMoney", () => { + const bank = new Bank(); + const result = bank.reduce(Money.dollar(1), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); +}); diff --git a/src/chapters/chapter13.ts b/src/chapters/chapter13.ts new file mode 100644 index 0000000..d9a8d11 --- /dev/null +++ b/src/chapters/chapter13.ts @@ -0,0 +1,216 @@ +/* +// attempt 1 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + }; + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return this._amount === money._amount && this._currency === money.currency(); + } + + public times(multiplier: number): Money { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Money): Sum { + return new Sum(this, addend); + } +} + +export interface Expression {} + +export class Bank { + public reduce(source: Expression, to: string): Money { + const sum = source as Sum; + return sum.reduce(to); + } +} + +export class Sum implements Expression { + public augend: Money; + public addend: Money; + + constructor(augend: Money, addend: Money) { + this.augend = augend; + this.addend = addend; + } + + public reduce(to: string) { + const amount = this.augend.amount + this.addend.amount; + return new Money(amount, to); + } +} +*/ + +/* +// attempt 2 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + }; + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return this._amount === money._amount && this._currency === money.currency(); + } + + public times(multiplier: number): Money { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Money): Sum { + return new Sum(this, addend); + } +} + +export interface Expression {} + +export class Bank { + public reduce(source: Expression, to: string): Money { + if (source instanceof Money) { + return source; + } + + const sum = source as Sum; + return sum.reduce(to); + } +} + +export class Sum implements Expression { + public augend: Money; + public addend: Money; + + constructor(augend: Money, addend: Money) { + this.augend = augend; + this.addend = addend; + } + + public reduce(to: string) { + const amount = this.augend.amount + this.addend.amount; + return new Money(amount, to); + } +} +*/ + +// attempt 3 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + }; + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return this._amount === money._amount && this._currency === money.currency(); + } + + public times(multiplier: number): Money { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Money): Sum { + return new Sum(this, addend); + } + + public reduce(to: string) { + return this; + } +} + +export interface Expression { + reduce(to: string): Money; +} + +export class Bank { + public reduce(source: Expression, to: string): Money { + return source.reduce(to); + } +} + +export class Sum implements Expression { + public augend: Money; + public addend: Money; + + constructor(augend: Money, addend: Money) { + this.augend = augend; + this.addend = addend; + } + + public reduce(to: string) { + const amount = this.augend.amount + this.addend.amount; + return new Money(amount, to); + } +} diff --git a/src/chapters/chapter14.test.ts b/src/chapters/chapter14.test.ts new file mode 100644 index 0000000..5ff5041 --- /dev/null +++ b/src/chapters/chapter14.test.ts @@ -0,0 +1,135 @@ +import { Bank, Money } from "./chapter14"; +import type { Expression } from "./chapter14"; +import { Sum } from "./chapter14"; + +/* +// attempt 1 ~ 2 +describe("chapter 14", () => { + test("multiplication", () => { + const five: Money = Money.dollar(5); + expect(Money.dollar(10)).toEqual(five.times(2)); + expect(Money.dollar(15)).toEqual(five.times(3)); + }); + + test("francMultiplication", () => { + const five = Money.franc(5); + expect(Money.franc(10)).toEqual(five.times(2)); + expect(Money.franc(15)).toEqual(five.times(3)); + }); + + test("equality", () => { + expect(Money.dollar(5).equals(Money.dollar(5))).toBe(true); + expect(Money.dollar(5).equals(Money.dollar(6))).toBe(false); + expect(Money.franc(5).equals(Money.dollar(5))).toBe(false); + }); + + test("currency", () => { + expect(Money.dollar(1).currency()).toBe("USD"); + expect(Money.franc(1).currency()).toBe("CHF"); + }); + + test("simpleAddition", () => { + const five = Money.dollar(5); + const sum: Expression = five.plus(five); + const bank = new Bank(); + const reduced = bank.reduce(sum, "USD"); + expect(Money.dollar(10)).toEqual(reduced); + }); + + test("plusReturnsSum", () => { + const five = Money.dollar(5); + const result = five.plus(five); + const sum: Sum = result; + + expect(five).toEqual(sum.augend); + expect(five).toEqual(sum.addend); + }); + + test("reduceSum", () => { + const sum = new Sum(Money.dollar(3), Money.dollar(4)); + const bank = new Bank(); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(7)).toEqual(result); + }); + + test("reduceMoney", () => { + const bank = new Bank(); + const result = bank.reduce(Money.dollar(1), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); + + test("reduceMoneyDifferentCurrency", () => { + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const result = bank.reduce(Money.franc(2), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); +}); +*/ + +// attempt 3 +describe("chapter 14", () => { + test("multiplication", () => { + const five: Money = Money.dollar(5); + expect(Money.dollar(10)).toEqual(five.times(2)); + expect(Money.dollar(15)).toEqual(five.times(3)); + }); + + test("francMultiplication", () => { + const five = Money.franc(5); + expect(Money.franc(10)).toEqual(five.times(2)); + expect(Money.franc(15)).toEqual(five.times(3)); + }); + + test("equality", () => { + expect(Money.dollar(5).equals(Money.dollar(5))).toBe(true); + expect(Money.dollar(5).equals(Money.dollar(6))).toBe(false); + expect(Money.franc(5).equals(Money.dollar(5))).toBe(false); + }); + + test("currency", () => { + expect(Money.dollar(1).currency()).toBe("USD"); + expect(Money.franc(1).currency()).toBe("CHF"); + }); + + test("simpleAddition", () => { + const five = Money.dollar(5); + const sum: Expression = five.plus(five); + const bank = new Bank(); + const reduced = bank.reduce(sum, "USD"); + expect(Money.dollar(10)).toEqual(reduced); + }); + + test("plusReturnsSum", () => { + const five = Money.dollar(5); + const result = five.plus(five); + const sum: Sum = result; + + expect(five).toEqual(sum.augend); + expect(five).toEqual(sum.addend); + }); + + test("reduceSum", () => { + const sum = new Sum(Money.dollar(3), Money.dollar(4)); + const bank = new Bank(); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(7)).toEqual(result); + }); + + test("reduceMoney", () => { + const bank = new Bank(); + const result = bank.reduce(Money.dollar(1), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); + + test("reduceMoneyDifferentCurrency", () => { + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const result = bank.reduce(Money.franc(2), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); + + test("identityRate", () => { + expect(new Bank().rate("USD", "USD")).toBe(1); + }); +}); diff --git a/src/chapters/chapter14.ts b/src/chapters/chapter14.ts new file mode 100644 index 0000000..92b9778 --- /dev/null +++ b/src/chapters/chapter14.ts @@ -0,0 +1,271 @@ +/* +// attempt 1 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + } + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return ( + this._amount === money._amount && this._currency === money.currency() + ); + } + + public times(multiplier: number): Money { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Money): Sum { + return new Sum(this, addend); + } + + public reduce(to: string) { + const rate = this._currency === "CHF" && to === "USD" ? 2 : 1; + return new Money(this._amount / rate, to); + } +} + +export interface Expression { + reduce(to: string): Money; +} + +export class Bank { + public reduce(source: Expression, to: string): Money { + return source.reduce(to); + } + + public addRate(from: string, to: string, rate: number) {} +} + +export class Sum implements Expression { + public augend: Money; + public addend: Money; + + constructor(augend: Money, addend: Money) { + this.augend = augend; + this.addend = addend; + } + + public reduce(to: string) { + const amount = this.augend.amount + this.addend.amount; + return new Money(amount, to); + } +} +*/ + +/* +// attempt 2 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + } + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return ( + this._amount === money._amount && this._currency === money.currency() + ); + } + + public times(multiplier: number): Money { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Money): Sum { + return new Sum(this, addend); + } + + public reduce(bank: Bank, to: string) { + const rate = bank.rate(this._currency, to); + return new Money(this._amount / rate, to); + } +} + +export interface Expression { + reduce(bank: Bank, to: string): Money; +} + +export class Bank { + public reduce(source: Expression, to: string): Money { + return source.reduce(this, to); + } + + public rate(from: string, to: string): number { + return from === "CHF" && to === "USD" ? 2 : 1; + } + + public addRate(from: string, to: string, rate: number) {} +} + +export class Sum implements Expression { + public augend: Money; + public addend: Money; + + constructor(augend: Money, addend: Money) { + this.augend = augend; + this.addend = addend; + } + + public reduce(bank: Bank, to: string) { + const amount = this.augend.amount + this.addend.amount; + return new Money(amount, to); + } +} +*/ + +// attempt 3 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + } + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return ( + this._amount === money._amount && this._currency === money.currency() + ); + } + + public times(multiplier: number): Money { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Money): Sum { + return new Sum(this, addend); + } + + public reduce(bank: Bank, to: string) { + const rate = bank.rate(this._currency, to); + return new Money(this._amount / rate, to); + } +} + +export interface Expression { + reduce(bank: Bank, to: string): Money; +} + +export class Bank { + private rates: Map = new Map(); + + public reduce(source: Expression, to: string): Money { + return source.reduce(this, to); + } + + public rate(from: string, to: string): number { + if (from === to) { + return 1; + } + return this.rates.get(new Pair(from, to).hashCode()); + } + + public addRate(from: string, to: string, rate: number) { + this.rates.set(new Pair(from, to).hashCode(), rate); + } +} + +export class Sum implements Expression { + public augend: Money; + public addend: Money; + + constructor(augend: Money, addend: Money) { + this.augend = augend; + this.addend = addend; + } + + public reduce(bank: Bank, to: string) { + const amount = this.augend.amount + this.addend.amount; + return new Money(amount, to); + } +} + +class Pair { + private from: string; + private to: string; + + constructor(from: string, to: string) { + this.from = from; + this.to = to; + } + + public equals(object: Object) { + const pair = object as Pair; + return this.from === pair.from && this.to === pair.to; + } + + public hashCode(): number { + return 0; + } +} diff --git a/src/chapters/chapter15.test.ts b/src/chapters/chapter15.test.ts new file mode 100644 index 0000000..42fe57e --- /dev/null +++ b/src/chapters/chapter15.test.ts @@ -0,0 +1,79 @@ +import { Bank, Money } from "./chapter15"; +import type { Expression } from "./chapter15"; +import { Sum } from "./chapter15"; + +// attempt 1 +describe("chapter 15", () => { + test("multiplication", () => { + const five: Money = Money.dollar(5); + expect(Money.dollar(10)).toEqual(five.times(2)); + expect(Money.dollar(15)).toEqual(five.times(3)); + }); + + test("francMultiplication", () => { + const five = Money.franc(5); + expect(Money.franc(10)).toEqual(five.times(2)); + expect(Money.franc(15)).toEqual(five.times(3)); + }); + + test("equality", () => { + expect(Money.dollar(5).equals(Money.dollar(5))).toBe(true); + expect(Money.dollar(5).equals(Money.dollar(6))).toBe(false); + expect(Money.franc(5).equals(Money.dollar(5))).toBe(false); + }); + + test("currency", () => { + expect(Money.dollar(1).currency()).toBe("USD"); + expect(Money.franc(1).currency()).toBe("CHF"); + }); + + test("simpleAddition", () => { + const five = Money.dollar(5); + const sum: Expression = five.plus(five); + const bank = new Bank(); + const reduced = bank.reduce(sum, "USD"); + expect(Money.dollar(10)).toEqual(reduced); + }); + + test("plusReturnsSum", () => { + const five = Money.dollar(5); + const result = five.plus(five); + const sum: Sum = result as Sum; + + expect(five).toEqual(sum.augend); + expect(five).toEqual(sum.addend); + }); + + test("reduceSum", () => { + const sum = new Sum(Money.dollar(3), Money.dollar(4)); + const bank = new Bank(); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(7)).toEqual(result); + }); + + test("reduceMoney", () => { + const bank = new Bank(); + const result = bank.reduce(Money.dollar(1), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); + + test("reduceMoneyDifferentCurrency", () => { + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const result = bank.reduce(Money.franc(2), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); + + test("identityRate", () => { + expect(new Bank().rate("USD", "USD")).toBe(1); + }); + + test("mixedAddition", () => { + const fiveBucks: Expression = Money.dollar(5); + const tenFrancs: Expression = Money.franc(10); + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const result = bank.reduce(fiveBucks.plus(tenFrancs), "USD"); + expect(Money.dollar(10)).toEqual(result); + }); +}); diff --git a/src/chapters/chapter15.ts b/src/chapters/chapter15.ts new file mode 100644 index 0000000..4e49405 --- /dev/null +++ b/src/chapters/chapter15.ts @@ -0,0 +1,225 @@ +/* +// attempt 1 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + } + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return ( + this._amount === money._amount && this._currency === money.currency() + ); + } + + public times(multiplier: number): Money { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Money): Sum { + return new Sum(this, addend); + } + + public reduce(bank: Bank, to: string) { + const rate = bank.rate(this._currency, to); + return new Money(this._amount / rate, to); + } +} + +export interface Expression { + reduce(bank: Bank, to: string): Money; +} + +export class Bank { + private rates: Map = new Map(); + + public reduce(source: Expression, to: string): Money { + return source.reduce(this, to); + } + + public rate(from: string, to: string): number { + if (from === to) { + return 1; + } + return this.rates.get(new Pair(from, to).hashCode()); + } + + public addRate(from: string, to: string, rate: number) { + this.rates.set(new Pair(from, to).hashCode(), rate); + } +} + +export class Sum implements Expression { + public augend: Money; + public addend: Money; + + constructor(augend: Money, addend: Money) { + this.augend = augend; + this.addend = addend; + } + + public reduce(bank: Bank, to: string) { + const amount = + this.augend.reduce(bank, to).amount + this.addend.reduce(bank, to).amount; + return new Money(amount, to); + } +} + +class Pair { + private from: string; + private to: string; + + constructor(from: string, to: string) { + this.from = from; + this.to = to; + } + + public equals(object: Object) { + const pair = object as Pair; + return this.from === pair.from && this.to === pair.to; + } + + public hashCode(): number { + return 0; + } +} +*/ + +// attempt 2 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + } + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return ( + this._amount === money._amount && this._currency === money.currency() + ); + } + + public times(multiplier: number): Expression { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Expression): Expression { + return new Sum(this, addend); + } + + public reduce(bank: Bank, to: string) { + const rate = bank.rate(this._currency, to); + return new Money(this._amount / rate, to); + } +} + +export interface Expression { + reduce(bank: Bank, to: string): Money; + plus(addend: Expression): Expression; +} + +export class Bank { + private rates: Map = new Map(); + + public reduce(source: Expression, to: string): Money { + return source.reduce(this, to); + } + + public rate(from: string, to: string): number { + if (from === to) { + return 1; + } + return this.rates.get(new Pair(from, to).hashCode()); + } + + public addRate(from: string, to: string, rate: number) { + this.rates.set(new Pair(from, to).hashCode(), rate); + } +} + +export class Sum implements Expression { + public augend: Expression; + public addend: Expression; + + constructor(augend: Expression, addend: Expression) { + this.augend = augend; + this.addend = addend; + } + + public reduce(bank: Bank, to: string) { + const amount = + this.augend.reduce(bank, to).amount + this.addend.reduce(bank, to).amount; + return new Money(amount, to); + } + + public plus(addend: Expression): Expression { + return null; + } +} + +class Pair { + private from: string; + private to: string; + + constructor(from: string, to: string) { + this.from = from; + this.to = to; + } + + public equals(object: Object) { + const pair = object as Pair; + return this.from === pair.from && this.to === pair.to; + } + + public hashCode(): number { + return 0; + } +} + diff --git a/src/chapters/chapter16.test.ts b/src/chapters/chapter16.test.ts new file mode 100644 index 0000000..78fc89f --- /dev/null +++ b/src/chapters/chapter16.test.ts @@ -0,0 +1,99 @@ +import { Bank, Money } from "./chapter16"; +import type { Expression } from "./chapter16"; +import { Sum } from "./chapter16"; + +// attempt 1 +describe("chapter 16", () => { + test("multiplication", () => { + const five: Money = Money.dollar(5); + expect(Money.dollar(10)).toEqual(five.times(2)); + expect(Money.dollar(15)).toEqual(five.times(3)); + }); + + test("francMultiplication", () => { + const five = Money.franc(5); + expect(Money.franc(10)).toEqual(five.times(2)); + expect(Money.franc(15)).toEqual(five.times(3)); + }); + + test("equality", () => { + expect(Money.dollar(5).equals(Money.dollar(5))).toBe(true); + expect(Money.dollar(5).equals(Money.dollar(6))).toBe(false); + expect(Money.franc(5).equals(Money.dollar(5))).toBe(false); + }); + + test("currency", () => { + expect(Money.dollar(1).currency()).toBe("USD"); + expect(Money.franc(1).currency()).toBe("CHF"); + }); + + test("simpleAddition", () => { + const five = Money.dollar(5); + const sum: Expression = five.plus(five); + const bank = new Bank(); + const reduced = bank.reduce(sum, "USD"); + expect(Money.dollar(10)).toEqual(reduced); + }); + + test("plusReturnsSum", () => { + const five = Money.dollar(5); + const result = five.plus(five); + const sum: Sum = result as Sum; + + expect(five).toEqual(sum.augend); + expect(five).toEqual(sum.addend); + }); + + test("reduceSum", () => { + const sum = new Sum(Money.dollar(3), Money.dollar(4)); + const bank = new Bank(); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(7)).toEqual(result); + }); + + test("reduceMoney", () => { + const bank = new Bank(); + const result = bank.reduce(Money.dollar(1), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); + + test("reduceMoneyDifferentCurrency", () => { + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const result = bank.reduce(Money.franc(2), "USD"); + expect(Money.dollar(1)).toEqual(result); + }); + + test("identityRate", () => { + expect(new Bank().rate("USD", "USD")).toBe(1); + }); + + test("mixedAddition", () => { + const fiveBucks: Expression = Money.dollar(5); + const tenFrancs: Expression = Money.franc(10); + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const result = bank.reduce(fiveBucks.plus(tenFrancs), "USD"); + expect(Money.dollar(10)).toEqual(result); + }); + + test("sumPlusMoney", () => { + const fiveBucks: Expression = Money.dollar(5); + const tenFrancs: Expression = Money.franc(10); + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const sum: Expression = new Sum(fiveBucks, tenFrancs).plus(fiveBucks); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(15)).toEqual(result); + }); + + test("sumTimes", () => { + const fiveBucks: Expression = Money.dollar(5); + const tenFrancs: Expression = Money.franc(10); + const bank = new Bank(); + bank.addRate("CHF", "USD", 2); + const sum: Expression = new Sum(fiveBucks, tenFrancs).times(2); + const result = bank.reduce(sum, "USD"); + expect(Money.dollar(20)).toEqual(result); + }); +}); diff --git a/src/chapters/chapter16.ts b/src/chapters/chapter16.ts new file mode 100644 index 0000000..eef4625 --- /dev/null +++ b/src/chapters/chapter16.ts @@ -0,0 +1,119 @@ +// attempt 1 +export class Money implements Expression { + protected _amount: number; + protected _currency: string; + + constructor(amount: number, currency: string) { + this._amount = amount; + this._currency = currency; + } + + public get amount(): number { + return this._amount; + } + + public static dollar(amount: number): Money { + return new Money(amount, "USD"); + } + + public static franc(amount: number): Money { + return new Money(amount, "CHF"); + } + + public equals(object: Object) { + const money = object as Money; + return ( + this._amount === money._amount && this._currency === money.currency() + ); + } + + public times(multiplier: number): Expression { + return new Money(this._amount * multiplier, this._currency); + } + + public currency(): string { + return this._currency; + } + + public toString() { + return `${this._amount} ${this._currency}`; + } + + public plus(addend: Expression): Expression { + return new Sum(this, addend); + } + + public reduce(bank: Bank, to: string) { + const rate = bank.rate(this._currency, to); + return new Money(this._amount / rate, to); + } +} + +export interface Expression { + reduce(bank: Bank, to: string): Money; + plus(addend: Expression): Expression; + times(multiplier: number): Expression; +} + +export class Bank { + private rates: Map = new Map(); + + public reduce(source: Expression, to: string): Money { + return source.reduce(this, to); + } + + public rate(from: string, to: string): number { + if (from === to) { + return 1; + } + return this.rates.get(new Pair(from, to).hashCode()); + } + + public addRate(from: string, to: string, rate: number) { + this.rates.set(new Pair(from, to).hashCode(), rate); + } +} + +export class Sum implements Expression { + public augend: Expression; + public addend: Expression; + + constructor(augend: Expression, addend: Expression) { + this.augend = augend; + this.addend = addend; + } + + public reduce(bank: Bank, to: string) { + const amount = + this.augend.reduce(bank, to).amount + this.addend.reduce(bank, to).amount; + return new Money(amount, to); + } + + public plus(addend: Expression): Expression { + return new Sum(this, addend); + } + + public times(multiplier: number): Expression { + return new Sum(this.augend.times(multiplier), this.addend.times(multiplier)); + } +} + +class Pair { + private from: string; + private to: string; + + constructor(from: string, to: string) { + this.from = from; + this.to = to; + } + + public equals(object: Object) { + const pair = object as Pair; + return this.from === pair.from && this.to === pair.to; + } + + public hashCode(): number { + return 0; + } +} +