Maybe you don’t need classes at all!
Alan Campora

In the context of the shopping cart example in this article, that approach introduces an interesting challenge.

The db parameter would have to be injected into each function and we would need a way to ensure that the caller couldn’t alter the state of the db outside of the context of the mutation functions (addProduct, removeProduct)

One way to do that is to use bind

Something like :

const db = []
const addProductToCart = addProduct.bind(this, db)
const removeProductFromCart = removeProduct.bind(this, db)
export {addProductToCart, removeProductFromCart}
function addProduct(db, product) {
// business logic goes here
return Object.freeze([...db])
function removeProduct(db, productId) {
// business logic goes here
return Object.freeze(
[...db.filter(product => != productId)]

That’s certainly valid if you prefer it.

Martin Fowler wrote an extensive exploration of 4 different ways to handle these kinds of scenarios in JavaScript. It’s a good read if you haven’t already come across it.