Skip to content

Instantly share code, notes, and snippets.

@adriano-di-giovanni
Created October 3, 2024 10:16
Show Gist options
  • Save adriano-di-giovanni/7bee8b9d6e1454a896c0ebb16729e33c to your computer and use it in GitHub Desktop.
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.
export const KILOGRAM_TO_POUND = 2.20462;
export const POUND_TO_KILOGRAM = 0.453592;
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);
});
});
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()
);
}
}
import { Weight } from './weight';
export class Load {
constructor(readonly weight: Weight) {}
}
export enum WeightUnit {
Kilogram = 'kg',
Pound = 'lb',
}
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);
});
});
});
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