Created
October 3, 2024 10:16
-
-
Save adriano-di-giovanni/7bee8b9d6e1454a896c0ebb16729e33c to your computer and use it in GitHub Desktop.
Sample code for an Elevator implementation using a Value Object to represent weight. Featured on adrianodigiovanni.com.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const KILOGRAM_TO_POUND = 2.20462; | |
export const POUND_TO_KILOGRAM = 0.453592; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Elevator } from './elevator'; | |
import { Load } from './load'; | |
import { Weight } from './weight'; | |
import { WeightUnit } from './weight-unit.enum'; | |
describe(Elevator.name, () => { | |
it('should successfully create an elevator with a valid capacity', () => { | |
const capacity = new Weight(400, WeightUnit.Kilogram); | |
const elevator = new Elevator(capacity); | |
expect(elevator).toBeDefined(); | |
expect(elevator.capacity.equals(capacity)).toBe(true); | |
}); | |
it('should initialize with a load weight of 0', () => { | |
const elevator = new Elevator(new Weight(400, WeightUnit.Kilogram)); | |
expect(elevator.calculateLoadWeight()).toEqual( | |
new Weight(0, WeightUnit.Kilogram) | |
); | |
}); | |
it('should correctly calculate the load weight after a single load is boarded', () => { | |
const elevator = new Elevator(new Weight(400, WeightUnit.Kilogram)); | |
const load = new Load(new Weight(50, WeightUnit.Kilogram)); | |
elevator.boardLoad(load); | |
expect(elevator.calculateLoadWeight()).toEqual(load.weight); | |
}); | |
it('should correctly calculate the load weight after multiple loads are boarded', () => { | |
const load1 = new Load(new Weight(50, WeightUnit.Kilogram)); | |
const load2 = new Load(new Weight(100, WeightUnit.Kilogram)); | |
const elevator = new Elevator(new Weight(400, WeightUnit.Kilogram)); | |
elevator.boardLoad(load1); | |
elevator.boardLoad(load2); | |
expect(elevator.calculateLoadWeight()).toEqual( | |
load1.weight.add(load2.weight) | |
); | |
}); | |
it('should throw a RangeError when the total load weight exceeds the elevator capacity', () => { | |
expect(() => { | |
const load1 = new Load(new Weight(200, WeightUnit.Kilogram)); | |
const load2 = new Load(new Weight(201, WeightUnit.Kilogram)); | |
const elevator = new Elevator(new Weight(400, WeightUnit.Kilogram)); | |
elevator.boardLoad(load1); | |
elevator.boardLoad(load2); | |
}).toThrow(RangeError); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Load } from './load'; | |
import { Weight } from './weight'; | |
export class Elevator { | |
private readonly loads: Load[] = []; | |
constructor(readonly capacity: Weight) {} | |
boardLoad(load: Load): void { | |
const currentLoadWeight = this.calculateLoadWeight(); | |
const projectedLoadWeight = currentLoadWeight.add(load.weight); | |
if (projectedLoadWeight.isGreaterThan(this.capacity)) { | |
throw new RangeError('Load weight exceeds elevator capacity'); | |
} | |
this.loads.push(load); | |
} | |
calculateLoadWeight(): Weight { | |
return this.loads.reduce( | |
(loadWeight, load) => loadWeight.add(load.weight), | |
this.capacity.zero() | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Weight } from './weight'; | |
export class Load { | |
constructor(readonly weight: Weight) {} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export enum WeightUnit { | |
Kilogram = 'kg', | |
Pound = 'lb', | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { KILOGRAM_TO_POUND, POUND_TO_KILOGRAM } from './constants'; | |
import { Weight } from './weight'; | |
import { WeightUnit } from './weight-unit.enum'; | |
describe.each([ | |
[WeightUnit.Kilogram, WeightUnit.Pound, KILOGRAM_TO_POUND], | |
[WeightUnit.Pound, WeightUnit.Kilogram, POUND_TO_KILOGRAM], | |
])(Weight.name, (unit, otherUnit, conversionFactor) => { | |
it('should throw a RangeError when attempting to create a weight with negative value', () => { | |
expect(() => new Weight(-1, unit)).toThrow(RangeError); | |
}); | |
it.each([0, 50])( | |
'should successfully create a weight with a valid value', | |
() => { | |
const weight = new Weight(50, unit); | |
expect(weight).toBeDefined(); | |
} | |
); | |
describe(Weight.prototype.add.name, () => { | |
it('should add a weight with the same unit', () => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(2, unit); | |
const actualWeight = weight1.add(weight2); | |
const expectedWeight = new Weight(3, unit); | |
expect(actualWeight.equals(expectedWeight)).toBe(true); | |
}); | |
it('should add a weight with a different unit', () => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(1 * conversionFactor, otherUnit); | |
const actualWeight = weight1.add(weight2); | |
const expectedWeight = new Weight(2, unit); | |
expect(actualWeight.equals(expectedWeight)).toBe(true); | |
}); | |
}); | |
describe(Weight.prototype.compareTo.name, () => { | |
it('should return 0 when comparing to itself', () => { | |
const weight = new Weight(50, unit); | |
expect(weight.compareTo(weight)).toBe(0); | |
}); | |
it( | |
'should return 0' + | |
' when comparing to another weight with the same value and unit', | |
() => { | |
const weight1 = new Weight(50, unit); | |
const weight2 = new Weight(50, unit); | |
expect(weight1.compareTo(weight2)).toBe(0); | |
} | |
); | |
it( | |
'should return 0' + | |
' when comparing to another weight with the same value but different unit', | |
() => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(1 * conversionFactor, otherUnit); | |
expect(weight1.compareTo(weight2)).toBe(0); | |
} | |
); | |
it('should return -1 when comparing to a weight with a greater value', () => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(2, unit); | |
expect(weight1.compareTo(weight2)).toBe(-1); | |
}); | |
it('should return 1 when comparing to a weight with a lesser value', () => { | |
const weight1 = new Weight(2, unit); | |
const weight2 = new Weight(1, unit); | |
expect(weight1.compareTo(weight2)).toBe(1); | |
}); | |
it( | |
'should return -1' + | |
' when comparing to a weight with a greater value in a different unit', | |
() => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(2 * conversionFactor, otherUnit); | |
expect(weight1.compareTo(weight2)).toBe(-1); | |
} | |
); | |
it( | |
'should return 1' + | |
' when comparing to a weight with a lesser value in a different unit', | |
() => { | |
const weight1 = new Weight(2 * conversionFactor, otherUnit); | |
const weight2 = new Weight(1, unit); | |
expect(weight1.compareTo(weight2)).toBe(1); | |
} | |
); | |
}); | |
describe(Weight.prototype.convertTo.name, () => { | |
it('should convert weight to the same unit', () => { | |
const weight = new Weight(50, unit); | |
const convertedWeight = weight.convertTo(unit); | |
expect(weight.equals(convertedWeight)).toBe(true); | |
}); | |
it('should convert between units', () => { | |
const weight = new Weight(1, unit); | |
const convertedWeight = weight.convertTo(otherUnit); | |
const expectedWeight = new Weight(1 * conversionFactor, otherUnit); | |
expect(convertedWeight.equals(expectedWeight)).toBe(true); | |
}); | |
}); | |
describe(Weight.prototype.equals.name, () => { | |
it('should equal itself', () => { | |
const weight = new Weight(1, unit); | |
expect(weight.equals(weight)).toBe(true); | |
}); | |
it('should equal another weight with the same value and unit', () => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(1, unit); | |
expect(weight1.equals(weight2)).toBe(true); | |
}); | |
it('should equal another weight with the same value but different unit', () => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(1 * conversionFactor, otherUnit); | |
expect(weight1.equals(weight2)).toBe(true); | |
}); | |
it('should not equal another weight with the same unit but different value', () => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(2, unit); | |
expect(weight1.equals(weight2)).toBe(false); | |
}); | |
it('should not equal another weight with the same value but different unit', () => { | |
const weight1 = new Weight(1, unit); | |
const weight2 = new Weight(1, otherUnit); | |
expect(weight1.equals(weight2)).toBe(false); | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { KILOGRAM_TO_POUND, POUND_TO_KILOGRAM } from './constants'; | |
import { WeightUnit } from './weight-unit.enum'; | |
export class Weight { | |
private static readonly Epsilon = 1e-5; | |
constructor( | |
private readonly value: number, | |
private readonly unit: WeightUnit | |
) { | |
if (value < 0) { | |
throw new RangeError('Weight must be greater than or equal to 0'); | |
} | |
} | |
add(other: Weight): Weight { | |
const convertedOther = other.convertTo(this.unit); | |
return new Weight(this.value + convertedOther.value, this.unit); | |
} | |
compareTo(other: Weight): number { | |
const convertedOther = other.convertTo(this.unit); | |
const difference = this.value - convertedOther.value; | |
return Math.abs(difference) < Weight.Epsilon ? 0 : Math.sign(difference); | |
} | |
convertTo(unit: WeightUnit): Weight { | |
if (unit === this.unit) { | |
return this; | |
} | |
const coefficient = | |
this.unit === WeightUnit.Kilogram ? KILOGRAM_TO_POUND : POUND_TO_KILOGRAM; | |
const newValue = this.value * coefficient; | |
return new Weight(newValue, unit); | |
} | |
equals(other: Weight): boolean { | |
const convertedOther = other.convertTo(this.unit); | |
return Math.abs(this.value - convertedOther.value) < Weight.Epsilon; | |
} | |
isGreaterThan(other: Weight): boolean { | |
return this.compareTo(other) > 0; | |
} | |
zero(): Weight { | |
return new Weight(0, this.unit); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment