DEV Community

Elegant patterns in modern JavaScript: Ice Factory

Bill Sourour on May 06, 2018

Photo by Demi DeHerrera on Unsplash I’ve been working with JavaScript on and off since the late nineties. I didn’t really like it at first, but...
Collapse
 
michaeljota profile image
Michael De Abreu • Edited

There is a series of books you (and anyone actually) should read about JS, to proper understand JS. For example, this is already a Pattern in that book, without the object.froze. You should consider as well understand what does froze, and what it does not.

I also start into programming with C++, and Java, and when I started with JS I wanted JS to behave like a proper language. Having more than 3 years working with JS the one think I can tell you is, hug JS. It have it weirdness, but it will surprise you if you let him.

If you want some kind of static type check, I strongly suggest you to use Typescript. But also, you will need a proper level of understanding about what Typescript does, because it will compile to JS at the end.

The books are You don't know JS. I think they cover most of the design patterns used in JS.

Collapse
 
billsourour profile image
Bill Sourour • Edited

Thanks for the feedback.

YDKJS is a great series of books which I have read and would recommend for sure. The author has generously made the entire thing available for free on github.

Hopefully, it comes through in this article (and its predecessor) that I am not claiming to have "invented" these patterns.

I give credit here to Crockford on the Object.freeze formulation but, of course, the idea of a Factory goes back to the original Gang of Four patterns and the idea of a Factory Function has been prominent in JavaScript programming for as long as I can remember. We are all standing on the shoulders of giants.

As for static type checking and Typescript. While it can be helpful at design time, it's completely absent at runtime because – as you pointed out – it just transpiles into JavaScript. So, it doesn't absolve you from having to code defensively for runtime input.

There is also – as I think you eluded to – a great deal of misunderstanding about what TypeScript is and how it works. TypeScript does not give you static types in JavaScript because JavaScript is not capable of static typing. So, TypeScript uses structural types which is just a formalization of duck typing.

Erirc Elliot's article on TypeScript is worth a gander for an interesting discussion of the cost/benefit of TypeScript and similar tools.

Collapse
 
xowap profile image
Rémy 🤖

The only issue with this pattern though is unit testing, private members are just completely private which can be a little bit annoying for unit testing (versus other languages where private members can be accessed through special means)

Collapse
 
billsourour profile image
Bill Sourour • Edited

In general, we should not be unit testing private members regardless of the programming language.

If we find ourselves needing to access a private member during a unit test, it is likely because the private member is pointing to something we want to mock. In that case, we should be injecting it on object creation so that it can easily be substituted when testing (consider the db parameter from the sample code in this article, for example).

As a side note, if we find ourselves doing a lot of mocking and dependency injection during unit testing, it is likely because we have not properly segregated our integration logic from our business logic. But that could be the subject of a whole other article.

Collapse
 
xowap profile image
Rémy 🤖

I've been using the Ice Factory pattern for years and even taught it to hundreds students so I quite like it (at least in ES5).

However, I've taken the example of testing but there is other ones. Suppose that there's an object Foo with an internal state bar which is a variable scoped to the constructor. For some reason, you don't want to expose bar to the world, yet you want to have a Foo.hasSameState(otherFoo) method. Since you can't access the private member, you're kinda screwed.

In short, I don't really recommend to create private members using this pattern or if you do you need to be really careful because you might very easily end up refactoring code.

Thread Thread
 
billsourour profile image
Bill Sourour

Thanks for the clarification.

If I understand correctly, what you're getting at is that – unlike some other languages – JavaScript doesn't yet support the notion of semi-private members (e.g. "protected" in Java or "friend" in C++). So, using a private member when what we really need is a semi-private member, will get us into trouble.

I definitely agree with that.

Thread Thread
 
xowap profile image
Rémy 🤖

Yes exactly :)

Collapse
 
erebos-manannan profile image
Erebos Manannán

I don't get this fear people have about their code being magically changed via prototypes, or by someone overriding the method.

Why is the acceptable solution not simply: "don't"?

Do you just accept anyone to edit your codebase as they wish and release everything to production without looking? Of course not.

On the other hand, overriding methods and magically changing your code can be super helpful in e.g. unit testing - e.g. make an internal method return a static response to trigger a special case that you might want to test.

So to me this stuff always smells a bit, and sounds like you're trying super hard to make your code more "safe" for literally 0 gains, while shooting yourself in the foot.

E.g. in Python convention is pretty much all that's needed - prefix your "private" methods with _ and people know they shouldn't be calling them if they don't know what they're doing. If they DO know what they're doing better than you did when you wrote the code (nobody is perfect, so this is bound to happen every now and then), they're going to be super annoyed if you went through massive amounts of effort to block them from doing so in your paranoia.

There is basically one case where I would consider an approach like that - when providing libraries to 3rd parties. Even then I'd have to have a pretty good justification beyond just "it's a library" to go through the effort.

Collapse
 
billsourour profile image
Bill Sourour

Thanks for taking the time to read and respond.

In my experience, when it comes to a codebase that is intended to last and to grow; to paraphrase Murphy's law, whatever can happen will happen. This tends to be true regardless of what standards, conventions, or policies are put in place.

I would also argue that overriding methods and magically changing behaviour is an anti-pattern. When it comes to unit testing, if we need to change the behaviour of some dependency we should use a mock. If there is no way to inject the mock, we probably have a design problem.

I also don't see how making objects less mutable provides "0 gains". In fact, I see our entire industry moving toward immutability as a virtue because the greatest source of bugs is unexpected and uncontrolled change.

I'm also not sure I understand what you mean by:

people know they shouldn't be calling them if they don't know what they're doing. If they DO know what they're doing better than you did when you wrote the code (nobody is perfect, so this is bound to happen every now and then), they're going to be super annoyed if you went through massive amounts of effort to block them from doing so in your paranoia.

If an object's source needs to change, then it is the authors' responsibility to change it (and by author I mean the team responsible, not necessarily the exact individual who originally wore the code). A developer who is consuming an object and does not have access to, or the ability to, alter the source of that object should never be altering its behaviour; that's just a recipe for disaster.

There are many patterns for extending an object's behaviour that do not require changing the object itself.

Collapse
 
erebos-manannan profile image
Erebos Manannán

I would also argue that overriding methods and magically changing behaviour is an anti-pattern.

Patterns and anti-patterns are mostly fads anyway, there are places for everything. The most important thing is what makes you efficient as a developer and your code better.

If your code becomes clearer, better, and you use less time by calling a "non-public" method, or by replacing a method in a class or similar, you should absolutely do so. I'll even go through a bit of effort with reflection etc. to make it possible to implement an easy and clear solution to the problem.

In fact, I see our entire industry moving toward immutability as a virtue

Well, this generally speaking is for data, and more specifically application state. Not your application code, libraries, and so on.

A developer who is consuming an object and does not have access to, or the ability to, alter the source of that object should never be altering its behaviour; that's just a recipe for disaster.

To me that sounds like you're the kind of person who likes languages with the private keyword. I don't. I hate languages with the private keyword (or really even protected).

I've found so many examples of libraries, frameworks, and just application code where someone used protected or private and that meant I had to write a massive hack, or copy & paste massive amounts of code to create a replacement, instead of just doing the thing that the developer initially didn't think was necessary.

One of the most common examples is just having a private cache, that you can't clear when you, as the user of the library/framework/code in general, know better than the original developer.

I've lost count of the amount of times I've cursed the developers who used private seem to have thought "I know better than anyone else who will ever use this code, so I'm going to stop them from doing these things".

I've similarly lost count of how many times I've seen a Python library use _ prefix on things that I actually needed so I can call them and I've thought "thanks for making that possible".

If an object's source needs to change, then it is the authors' responsibility to change it (and by author I mean the team responsible, not necessarily the exact individual who originally wore the code).

It's not quite often quite so easy. Some library published on GitHub, PyPI, NPM, etc. is not often quite as easy for you to patch (the developer just started a 3 year walkabout, but the library is used somewhere by your framework of choice, etc.) as it would be for you to patch the code if it's been properly written - with clear hints as to "if you touch these things you might break something", and for that the _ prefix to the name tends to be enough.

When it's your own code it's usually just a matter of a simple refactor to make a private method non-private to call it, extending to change the behavior, etc. .. your own code is rarely the problem, it's the other people who think they're better than anyone else ever can be and lock down their code as much as they can.

Thread Thread
 
billsourour profile image
Bill Sourour

Thanks for taking the time to reply.

It's always instructive to read a different point of view. I think we may just have to agree to disagree on pretty much all of these points.

The behaviour you seem to be describing is exactly the sort of thing that I have seen lead to serious problems in many codebases over the years. Having said that, I can appreciate that my experience is certainly not the be-all and end-all when it comes to coding, and I have been around long enough to know that there is no such thing as never and always when it comes to coding and design, it's just a matter of trade-offs.

Thanks again for your insights.

Collapse
 
eiquu0jet profile image
eiQuu0jet

Unfortunately Mattias Petter Johansson got composition completely, entirely wrong, and since you learned from him, you got composition wrong too. What he mistook for composition is actually multiple inheritance.

Collapse
 
billsourour profile image
Bill Sourour

I didn't learn about composition or inheritance from MPJ, he just provided a fitting quote about new and this that I incorporated.

Having said that, the thread you linked to makes a valid point; by destructuring the ProductList into the ShoppingCart and subsuming its methods we do, in a way, "inherit" them rather than simply make them a part of our composition. And this does lead to some of the problems we see with inheritance in general (especially, the "fragile base class" problem).

I will update the post and sample code to better reflect this. Thank you for your input.

Collapse
 
nickwalt profile image
nick walton

I really like this way of doing things (the Ice Factory Function). Thanks for writing a great article.

There seems to be a fundamental difference between the OOP way of reusing methods and properties from one object by calling them in another (object.method), and the FP way of just passing data from one function to another via arguments.

Does FP have objects with methods and properties? Could the Ice Factory Function be considered a "functionalised" OOP pattern?

I'm trying to understand how these two paradigms can be mixed together in JavaScript. What would the architecture of the application look like?

Also, the idea that an object can have properties seems to me to potentially inhibit composition because there is a tight coupling between object and data (or state).

Varun Vachar demonstrated stateless UI objects being passed state by another object. This loose coupling and the way he did that feels nicer to me than putting the state in the UI object.

Thread Thread
 
billsourour profile image
Bill Sourour • Edited

Hi Nick.

Thanks for the feedback and the question.

The Ice Factory pattern takes a decidedly OOP view of the world. It just sidesteps some of the drawbacks of inheritance and a lot of the quirkiness around JavaScript's class syntax.

It's easy to feel like anything that doesn't have the word "class" in it must be something other than OOP, but as Dave Thomas once said:

"Design with Objects, not Classes... It's OO Programming, not CO [Class-Oriented] Programming".

Having said that, FP is great. There is a lot we can learn from FP, and JavaScript is a fascinating language in that it gives us both Objects and Functions as "first class" citizens of the language, so we can take advantage of both paradigms and match the approach to the problem we are trying to solve.

In FP, we separate the data from the code that transforms the data. In our shopping cart example, the data is a list of items and the transformations include adding and removing items from the list.

An FP-style shopping cart could look something like this:

// shopping-cart-fp.js

import freezeMap from './utils/freeze-map.js';

export function addProductToCart(product, cart) {
    const newItem = Object.assign({}, product);
    newItem.qty = newItem.qty || 1;

    if (cart.has(newItem.id)) {
        newItem.qty = cart.get(newItem.id).qty + 1;
    }

    const newCart = new Map(cart);
    newCart.set(newItem.id, newItem);

    return freezeMap(newCart);
}

export function removeProductFromCart(productId, cart) {
    const newCart = new Map(cart);

    if (newCart.has(productId)) {
        const product = Object.assign({}, newCart.get(productId));

        if (product.qty < 2) {
            newCart.delete(productId);
        } else {
            --product.qty;
            newCart.set(productId, product);
        }
    }
    return freezeMap(newCart);
}

I'm using a Map here – which can't be frozen – so we need a little utility function to freeze it to ensures that no one messes with our cart data. Like so:

// freeze-map.js
export default function freezeMap(map) {
    map.set = map.clear = map.delete = () => {
        throw new Error('Attempted to change a frozen Map object.');
    };

    return map
}

Hope that's helpful.

Regards,
Bill

Collapse
 
brittanmcg profile image
Brittan McGinnis

Really great article. Definitely some food for thought. I will keep this in the forefront of my mind the next time I'm in need of a factory.