Problem overview
A shop tracks how many pieces of each item it has. The stock can never go below 0, and it changes only when pieces are sold or new ones arrive. So the stock is a private field, #stock, changed only through two methods that check the rules. The item's name has no rules to protect, so it stays an ordinary field that any code can read.
new InventoryItem(name, stock)creates an item with that name and that many pieces.sell(n)sellsnpieces. Ifnis at least 1 and at most the stock, it lowers the stock bynand returnstrue. Otherwise it changes nothing and returnsfalse: selling 0, a negative number, or more than is in stock is refused.restock(n)addsnpieces whennis at least 1 and ignores other values. It returns nothing.getStock()returns the stock, and is the only way to read it.
For a pencil item with stock 5: sell(3) works and leaves 2, sell(5) is refused because only 2 are left, and restock(10) brings the stock to 12.
Approach
Each method guards the stock before touching it. In sell, the check comes first and the change second, so a refused sale leaves everything as it was. Because the check says n is at most the stock, the stock after a sale is at least 0. In restock, a delivery of 0 or less is not a delivery, so it is skipped. Everything runs in O(1) time.
Step by step
- Declare the private field. At the top of the class, write
#stock;. - Store both values. In the constructor,
this.namegets the name andthis.#stockgets the stock. The name is a normal public field. - Refuse bad sales. In
sell, anifforn < 1 || n > this.#stockreturnsfalse. The value ofnequal to the stock is allowed: the shop may sell everything it has. - Make the sale. After the
if, subtractnfromthis.#stockand returntrue. Only sales that passed the check reach this line. - Restock. If
n >= 1, addnto the stock. Otherwise do nothing. - Read.
getStock()returnsthis.#stock.
The solution in JavaScript
class InventoryItem {
#stock;
constructor(name, stock) {
this.name = name;
this.#stock = stock;
}
sell(n) {
if (n < 1 || n > this.#stock) {
return false;
}
this.#stock -= n;
return true;
}
restock(n) {
if (n >= 1) {
this.#stock += n;
}
}
getStock() {
return this.#stock;
}
}
Using the class
let item = new InventoryItem("Pencil", 5);
console.log(item.name + ":", item.sell(3), item.getStock(), item.sell(5), item.getStock());
item.restock(10);
console.log(item.getStock());
item = new InventoryItem("Mug", 4);
console.log(item.name + ":", item.sell(4), item.getStock(), item.sell(1), item.getStock());
item = new InventoryItem("Lamp", 2);
console.log(item.sell(0), item.sell(-3));
item.restock(0);
item.restock(-5);
console.log(item.getStock());
item.restock(1);
console.log(item.sell(3), item.getStock());
console.log(item.stock, item.name);
This prints:
Pencil: true 2 false 2
12
Mug: true 0 false 0
false false
2
true 0
undefined Lamp
The lesson's tests print the booleans as True and False; JavaScript itself prints true and false. The last line shows the difference between the two fields: item.name is readable, while item.stock is undefined, because the stock is stored under the private name #stock and the only way to see it is getStock().
Why the check is written that way
The condition has two halves, joined with ||: the sale is refused if n < 1 or n > this.#stock. Both halves are needed. Without the first, sell(-2) would pass the second and add two pieces, because subtracting a negative number adds. A version that only checks n > this.#stock shows it: on an item with stock 3, sell(-2) returns true and the stock becomes 5. Without the second half, selling 4 pieces of 3 would push the stock to -1.
Common mistakes
- Changing the stock before checking. The refusal must come first, or a refused sale would still change the stock.
- Using
>=for the upper limit. Selling exactly the stock is allowed. Withn >= this.#stock, selling the last pieces would be refused. - Letting
restocktake 0 or negative values.restock(-5)would remove pieces without any ofsell's checks. - Returning something from
restock. It returns nothing. A caller that prints its result seesundefined. - Making the name private as well. The statement keeps the name readable. Private fields are for values with rules to protect.
Testing the edges
On an item with stock 3 (a cup), these calls run in order:
| Call | Result | Stock after |
|---|---|---|
| sell(4) | false | 3 |
| sell(3) | true | 0 |
| sell(0) | false | 0 |
| sell(-1) | false | 0 |
| restock(0) | nothing | 0 |
| restock(-4) | nothing | 0 |
| restock(7) | nothing | 7 |
Selling more than is in stock, selling exactly what is in stock, and selling nothing each behave differently, so they are worth a test of their own. A new item can also start with a stock of 0. Then every sell is refused until a restock brings the stock to 1 or more.
Public name, private stock
The class has two kinds of data on purpose. The name can be anything and nothing depends on it, so a plain this.name is enough, and code outside can read it or even change it. The stock has a rule: it must never go below 0. A private field makes that rule enforceable, because every change has to go through sell or restock. If the stock were public, any line of code could write item.stock = -50 and the class could do nothing about it. With #stock, the same line does not touch the real stock, and the item keeps answering with the correct number.
Use a private field when a value has a rule to protect, and keep the rest public. A class where everything is private is harder to use for no benefit.
