Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Builder pattern needed? #6

Open
jruts opened this issue Mar 7, 2017 · 11 comments
Open

Builder pattern needed? #6

jruts opened this issue Mar 7, 2017 · 11 comments

Comments

@jruts
Copy link

jruts commented Mar 7, 2017

Hi,

Since node/javascript does not have the problem that a builder pattern fixes, since you can just pass along an object literal, why would you want to apply all this overhead?

If we take a look at the example: https://github.com/torokmark/design_patterns_in_typescript/blob/master/builder/builder.ts

We could just use an object literal instead:

// user.js
const create = args => {
  const options = Object.assign({}, defaultOptions, args); // Normalize the options
  return {
    name: options.name,
    age: options.age,
    phone: options.phone, 
    address: options.address
  }
}

module.exports.create = create;

// test.js
const user = require('user.js');

const myUser = user.create({
  name: 'a name',
  age: 30
});

This achieves the same thing as the builder pattern. The main difference here is that user.js is a lot smaller than the builder.ts example. (10 lines vs 60+)
Passing along an object literal, which is very common in node, is just as readable as with a builder, but with a lot less overhead or boilerplate.

So my question is: why would you do this?

@TomSeldon
Copy link

One use case I've come across (I'm going to try and dig up a really good blog post about this as well), is for when creating mock data in unit tests.

I used to do this very much like your above example.

e.g.

let createUser;

beforeEach(() => {
    createUser = () => {
        return {
            username: 'bob.smith',
            email: 'bob.smith@aol.com',
            accountType: ACCOUNT_TYPES.DEVELOPER
       };
    };  
}));

// some spec

it('should do something', () => {
    const user = createUser();
});

This is all well and good, but what if we don't always want to test against a "developer" user. Well, we could do something like you suggested and pass in an object to override the parameters, or use individual parameters (e.g. createUser => (username) => {}).

This is fine, but if you often find yourself having to modify the default/generic object in order to obtain a useful object to test against, you can end up with lots of:

const user = createUser({
    accountType: ACCOUNT_TYPES.ADMIN,
    username: 'some.user'
});

// ...

const user = createUser({
    accountType: ACCOUNT_TYPES.ADMIN,
    username: 'some.user'
});

// ...etc...

That seems fine in isolation, but if each of those definitions is really saying "create me an admin user", then that would be better abstracted to a createAdminUser function, which sets the admin defaults and allows overrides, similar to createUser.

In a simple example like this, it's probably more overhead that it's worth to do anything more complicated than that.

If, such as in a real, complicated application, you're needing to create users in a lot of different places (read: a lot of different tests), the above can quickly get repetitive and you can end up with lots of these types of functions littered through the test suite. In such a situation, having a generalised "userBuilder" can help reduce duplication through the tests.

Much of this can be applicable to non-test code, too, but I'm specifically focussing on that as it's what I found most useful.

Apologies for the slight incoherence, trying to type out this quickly during the downtime of waiting for something to compile...

A really good blog post on this is: http://davedevelopment.co.uk/2015/01/28/test-data-builders.html

The examples in that blog post are all PHP, but it's just as relevant here. Especially if you want to work with immutable data (though not a prerequisite to this being useful).


TL;DR: For a very small or very simple use cases, it introduces overhead that almost certainly isn't worth it. But the same can be said of most abstractions. (One of?) the benefits comes with reducing complexity and repetitiveness in larger and more complicated use cases.

@jruts
Copy link
Author

jruts commented Mar 7, 2017

Thanks for the quick response!

I am kinda struggling to see where a builder would be easier for testing.
Similar to where I have to create my object to create a user, you would also have to build your object with the builder, which is just an abstraction of creating the object anyway.

so instead of:

const user = createUser({
    accountType: ACCOUNT_TYPES.ADMIN,
    username: 'some.user'
});

you would need to do something like:

const user = User(User.Builder('some.user').withAccountType(ACCOUNT_TYPES.ADMIN))

So I do not see the benefit of that in testing.
If I needed a specific admin user for multiple tests, I would just create an admin user with the correct options.
You would also need to create this admin user if you were to use the builder pattern.

So yes, I am still struggling to see 'why' :)

Btw small addition to your test code to make it more flexible:

let createUser;

beforeEach(() => {
    createUser = options => {
        const defaultUser = {
           username: 'bob.smith',
           email: 'bob.smith@aol.com',
           accountType: ACCOUNT_TYPES.DEVELOPER
        }

        return Object.assign({}, defaultUser, options);
    };  
}));

// some spec

it('now you can do stuff like this:', () => {
  // default user
  const user = createUser();

  // different email
  const otherUser = createUser({email: 'another email'})
});

@jruts
Copy link
Author

jruts commented Mar 7, 2017

I would never use a class in the first place when a simple object literal and simple functions can do the job. hence I do not need to create anything with the new keywords. Don't forget this is JavaScript. It does not have to be or look like Java

@jruts
Copy link
Author

jruts commented Mar 8, 2017

@msqaddura , in javascript you have the choice to do OOP or functional. And I think you are missing the point. I was trying to find an answer to the following question:

Why would you use a builder pattern when using plain javascript object literals gives you the same readability and the same functionality, but without any of the overhead or boilerplate.

I am not trying to attack any patterns here, I just do not think it belongs in every language. And since TypeScript is merely a superset of javascript, it is still javascript.

And as per definition of the builder pattern, the problem it is trying to solve is not present in javascript.

@msqaddura
Copy link

msqaddura commented Mar 8, 2017

man I believe you are completely out of the context between Prototyping(apparently your style), Object Oriented(apparently not your style), Functional(everyone's style that doesnt really matter)... what am saying is just that for you and the way you look at javascript (over a typescript code) you do not need any pattern it wont help you anyway... unless you are hardcore Object Oriented with javascript/typescript then you can use a pattern but for you it is absolutely no point to bother about a pattern.

just because there is a pattern it doesnt really mean that we have to use it everywhere... it can only make things worse if we are forcing it into something that is not meant for it.

@msqaddura
Copy link

msqaddura commented Mar 8, 2017

https://github.com/joelpalmer/Typescript--Builder-Pattern
please look at this example it should completely answer your original question that we drifted from.
var myRequest = new scope.Utils.RequestBuilder("get").setScheme("http").setPath("mypath").setHost("localhost").setPort("9999").build();

without a builder it would be
var myRequest = new Get("http","myPath","localHost",9999)
and cards constructor would be constructor(Scheme="http",path="myPath"......)

we can add destructing pattern over here for scrambled values where order doesnt matter
constructor({Scheme="http",...})//ntices we wrapped it weith json tab

so yes you can fiddle it down to literal objects but once change in superior constructor will have to be done to all constructors super() call... but with builder you would just need to change the method in the superior class... again you can find a way to simplify so it is always like this... you can always find your way out

@jruts
Copy link
Author

jruts commented Mar 8, 2017

@msqaddura you lost me at 'superior constructor'.

Favor composition over class inheritance

Never would I fall in the trap of extending, so never would I have the issue you just described.

And your example is again an example of how a Java person would write node. Just take a look at the 'request' module and see how it is done generally in node: https://github.com/request/request

// You would do:
request.get({
  host: 'localhost',
  port: 9999,
  path: 'mypaeh'
}, callback)

// not
new Get("http","myPath","localHost",9999)

The only reason why you are finding these issues is because you are not coming up with common nodejs/javascript examples. You find the problems by using javascript as if it was java.

@Kumjami
Copy link

Kumjami commented Mar 8, 2017

typescript adds types to javascript, it doesn't mean you have to forget about javascript essence

the same question can be done in a javascript page where described a builder pattern, or typescript, because the issue is the same, and the problem it faces/solves is the same

@jruts
Copy link
Author

jruts commented Mar 8, 2017

Typescript is a superset of javascript. Which means plain javascript is valid typescript. The benefit that typescript gives you is nice IDE support like auto-completion and compile errors. You could easily write a definition for the object literal you want to pass along and it would compile to valid javascript with the typescript benefits.

But let's make it very clear, adding typescript does not mean this language becomes Java and it does not mean you should treat it in the same way.

By adding typescript for it's benefits to then add all the complexity of design patterns you do not need kinda defeats the purpose for me.

The builder pattern does not solve any problems that exist in javascript or typescript for that matter.

@jruts
Copy link
Author

jruts commented Mar 8, 2017

@msqaddura really... all I want to do is open a discussion where we could all learn from, yet you fail to address anything I mentioned in my comment and post a picture of what AtScript is?

I do sense you are being very defensive but fail to come up with real examples or benefits. Even the examples you post in pure javascript clearly indicate that you have no great experience with pure javascript at all.

All I was hoping for was a conclusion to my question as to why would I ever use a builder pattern as a javascript developer. There is no need for childish discussion or immature responses.

@0xphilipp
Copy link

Even though the last comment was quite long ago, but I just stumbled across this question.
In my opinion it also adds a lot of value in the javascript/typescript world. Of course it is dependent on the use case if it is usefull or not.

Here are the advantages:

Decouples the model of the creation of the model.

If your model changes often, you don't need to change as many places. -> Clearer git history on business files, less conflicts due to refactorings.

Radability is improved

Readability is especially improved, if you have a huge object with a lot of properties and when you create a lot of elements through the builder:

If you take a look at the example, that this dense code would need for each of the method chains at least 1-2 more lines, it is harder too read.
image

Context

A real builder can have multiple sub builders, that depend on the called method. In the example above for the first call you have different possibilities of initialization, which will change some fundamental things.

Extensibility / Transformations

The builder can add very usefull utilities to make the usage of the model a lot easier. A junior dev might not know dependencies between different properties of the model. But they can be encapsulated into a nice api using a builder.

They can set multiple properties (also with good default values specially for these calls)
image

They can set pre defined values
image

They can even extend the usages, by allowing transformation of inputs. This way the model does not need to be extended, as long as we can map to the result in some way:

image


I hope this makes it clear, that there are indeed use cases for such a pattern.

Btw. jQuery is a huge builder pattern. (https://www.w3schools.com/jquery/jquery_chaining.asp)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants