Advertisement
  1. Code
  2. JavaScript

What's a Configuration Object, and Why Bother Using It?

Scroll to top
Read Time: 9 min

It's a pain to have to change the parameters of a function; you have to change every other call to that function in order to avoid errors. But you can get around this by using just one parameter: a configuration object.


What It Looks Like

Here's a silly example of a function for creating a robot:

1
2
function generateRobot(arms:int, personality:String):Robot {
3
	var robot:Robot = new Robot();
4
	
5
	for (var i:int = 0; i < arms; i++) {
6
		//create arm and add it to robot

7
	}
8
9
	if (personality == "evil") {
10
		robot.commands = "Destroy mankind.";
11
	}
12
	else {
13
		robot.commands = "Bake cookies."
14
	}
15
	return robot;
16
}
17
18
generateRobot(2, "evil");

Now, here's the same example, using a configuration object:

1
2
function generateRobot(conf:Object):Robot {
3
	var robot:Robot = new Robot();
4
	
5
	for (var i:int = 0; i < conf.arms; i++) {
6
		//create arm and add it to robot

7
	}
8
9
	if (conf.personality == "evil") {
10
		robot.commands = "Destroy mankind.";
11
	}
12
	else {
13
		robot.commands = "Bake cookies."
14
	}
15
	return robot;
16
}
17
18
generateRobot({arms:2, personality:"evil"});

I've highlighted the lines that require changing; you can see that there's not much difference.


Why Bother?

So if there's hardly any difference, why would we bother doing it the second way? After all, it actually makes the function a little harder to use; whereas before our IDE would be able to give us this information about the parameters the function expected:

...now it can only give us this:

Suppose you want to add a couple more parameters: one specifying the material to use and another to specify what color its laser should be. That's not too hard, in either case:

1
2
function generateRobot(arms:int, personality:String, material:String, laserColor:String):Robot {
3
	var robot:Robot = new Robot();
4
	
5
	for (var i:int = 0; i < arms; i++) {
6
		//create arm and add it to robot

7
	}
8
9
	if (personality == "evil") {
10
		robot.commands = "Destroy mankind.";
11
	}
12
	else {
13
		robot.commands = "Bake cookies."
14
	}
15
16
	switch (material) {
17
		case "wood":
18
			//wooden robot

19
		break;
20
		case "steel":
21
		default:
22
			//steel robot

23
		break;
24
	}
25
26
	robot.laser = new Laser();
27
	robot.laser.color = laserColor;
28
29
	return robot;
30
}
31
32
generateRobot(2, "evil", "steel", "red");
1
2
function generateRobot(conf:Object):Robot {
3
	var robot:Robot = new Robot();
4
	
5
	for (var i:int = 0; i < conf.arms; i++) {
6
		//create arm and add it to robot

7
	}
8
9
	if (conf.personality == "evil") {
10
		robot.commands = "Destroy mankind.";
11
	}
12
	else {
13
		robot.commands = "Bake cookies."
14
	}
15
16
	switch (conf.material) {
17
		case "wood":
18
			//wooden robot

19
		break;
20
		case "steel":
21
		default:
22
			//steel robot

23
		break;
24
	}
25
26
	robot.laser = new Laser();
27
	robot.laser.color = conf.laserColor;
28
29
	return robot;
30
}
31
32
generateRobot({arms:2, personality:"evil", material:"steel", laserColor:"red"});

So far, still not much of a difference. What if you want your robots to all have red lasers by default? Simple again. Without a configuration object, you just need to change the method signature (the function line), and then you can remove the last argument from the function call:

1
2
function generateRobot(arms:int, personality:String, material:String, laserColor:String = "red"):Robot {
3
    //this is all the same

4
}
5
6
generateRobot(2, true, "steel");    //I removed the last argument

With a configuration object, it's a little trickier - though not much:

1
2
function generateRobot(conf:Object):Robot {
3
	if (!conf.laserColor) {
4
		conf.laserColor = "red";
5
	}
6
7
	var robot:Robot = new Robot();
8
	
9
	for (var i:int = 0; i < conf.arms; i++) {
10
		//create arm and add it to robot

11
	}
12
13
	if (conf.personality == "evil") {
14
		robot.commands = "Destroy mankind.";
15
	}
16
	else {
17
		robot.commands = "Bake cookies."
18
	}
19
20
	switch (conf.material) {
21
		case "wood":
22
			//wooden robot

23
		break;
24
		case "steel":
25
		default:
26
			//steel robot

27
		break;
28
	}
29
30
	robot.laser = new Laser();
31
	robot.laser.color = conf.laserColor;
32
33
	return robot;
34
}
35
36
generateRobot({arms:2, personality:"evil", material:"steel"});   //I removed the last argument

Okay. Now suppose you find that you're setting almost all of your robots to be evil (I mean, why not?), so it's actually kind of a pain to write "evil" as a parameter every time. Naturally, you want to set "evil" as the default - but you don't want to set a default material.

The only way you can do this, with a regular set of function parameters, is to switch the order of the personality and material parameters:

1
2
function generateRobot(arms:int, material:String, personality:String = "evil", laserColor:String = "red"):Robot {

Ah, but now you have to switch the order of the arguments round on every single function call!

1
2
generateRobot(2, "evil", "steel");    //no longer works

A configuration object doesn't give you this problem. Check it out:

1
2
function generateRobot(conf:Object):Robot {
3
	if (!conf.laserColor) {
4
		conf.laserColor = "red";
5
	}
6
	if (!conf.personality) {
7
		conf.personality = "evil"
8
	}
9
10
	//this is all the same

11
}
12
13
generateRobot({arms:2, material:"steel"});  //no "personality" parameter? no problem!

Neat! All your old generateRobot() function calls will continue to work, but you can create new calls that don't bother specifying personality.

You can even decide to get rid of the personality parameter altogether:

1
2
function generateRobot(conf:Object):Robot {
3
	if (!conf.laserColor) {
4
		conf.laserColor = "red";
5
	}
6
	if (!conf.personality) {
7
		conf.personality = "evil"
8
	}
9
10
	var robot:Robot = new Robot();
11
	
12
	for (var i:int = 0; i < conf.arms; i++) {
13
		//create arm and add it to robot

14
	}
15
16
	robot.commands = "Destroy mankind.";
17
18
	switch (conf.material) {
19
		case "wood":
20
			//wooden robot

21
		break;
22
		case "steel":
23
		default:
24
			//steel robot

25
		break;
26
	}
27
28
	robot.laser = new Laser();
29
	robot.laser.color = conf.laserColor;
30
31
	return robot;
32
}

The above version of the function doesn't refer to conf.personality at all - but you won't get an error if you still have calls like this:

1
2
generateRobot({arms:2, personality:"evil", material:"steel"});

Of course, you might get a few confused users if you have calls like this:

1
2
generateRobot({arms:2, personality:"good", material:"steel"});

...since all robots are now evil. But at least the code will compile.

For the same reason, you can change the order of the arguments without it mattering at all, and even add in new parameters that don't do anything yet:

1
2
generateRobot({material:"steel", laserColor:"green", arms:2, voice:"Mr. T"});

Making It Easier to Set Defaults

The code for setting the defaults is easy to understand so far, but is going to be very annoying to extend if we need to have lots of parameters:

1
2
	if (!conf.laserColor) {
3
		conf.laserColor = "red";
4
	}
5
	if (!conf.personality) {
6
		conf.personality = "evil"
7
	}

Let's write some more general code to cope with it:

1
2
	var defaults:Object = {
3
		laserColor:red,
4
		personality: "evil"
5
	}
6
	
7
	for (var key:String in defaults){
8
		if (!conf[key]) {
9
			conf[key] = defaults[key];
10
		}
11
	}

That for loop may be a little confusing, so I'll break it down. First, look at this:

1
2
	for (var key:String in defaults){
3
		trace(key);
4
	}

This is a for...in loop, which will output the names of the keys inside the default object:

1
2
laserColor
3
personality

Next, look at this line:

1
2
trace(defaults["laserColor"]);

This will output red - it's the same as writing trace(defaults.laserColor).

Following on from that, look at this example:

1
2
var example:Object = { demo: "test" };
3
trace(example["demo"]);
4
trace(example["foo"]);

What do you think this will output?

Well, example["demo"] is the same as example.demo, which equals "test". But example.foo does not exist, so example["foo"] will return null. This means that !example["foo"] (note the exclamation mark) will be equivalent to true.

Put that all together, and you should be able to understand why this code works:

1
2
	var defaults:Object = {
3
		laserColor:red,
4
		personality: "evil"
5
	}
6
	
7
	for (var key:String in defaults){
8
		if (!conf[key]) {
9
			conf[key] = defaults[key];
10
		}
11
	}

Give me a shout in the comments if you need a hand!

I Want More!

For an even quicker version, try this:

1
2
function generateRobot(conf:Object = null):Robot {
3
	var conf:Object = conf || {};
4
	var defaults:Object = {
5
		laserColor:red,
6
		personality: "evil"
7
	}
8
	
9
	for (var key:String in defaults){
10
		conf[key] = conf[key] || defaults[key];
11
	}

The change in Line 1 (and new Line 2) means that even the conf object itself is optional, so you can just call generateRobot(). (Of course, you'll need to change the code to deal with the values that don't currently have defaults.)


Helping the IDE Help You

As I mentioned above, the IDE can't give you any tips about what parameters a function is expecting, if that function uses a configuration object. This is a major drawback, as it can make your code really hard to use; you have to remember which parameters go in the conf object, as well as all of their names and types.

But we can still display this information to the coder when it's needed; we just have to do so manually, like so:

1
2
			/**

3
			 * Generate a robot, based on the parameters given.

4
			 * @param	conf	Configuration object. Expects:

5
				 * arms 		(int)		Number of arms robot should have.

6
				 * personality 	(String)	Personality of robot. Can be "evil" or "good". Defaults to "evil".

7
				 * material		(String)	What the robot should be made out of. Can be "steel" or "wood" at this time.

8
				 * laserColor	(String)	Color of the robot's laser. Defaults to "red".

9
				 * voice		(String)	Vocal stylings of robot. Currently not implemented.

10
			 * @return	The finished robot.

11
			 */
12
			function generateRobot(conf:Object):Robot {
13
				//

14
			}

Now, if I start to write a call to this function in FlashDevelop (my IDE of choice), I see this:

Sure, it's a bit of a pain to keep this manually updated, but in many cases it's worth it.


Conclusion

I'm not claiming that you should use a configuration object for every single function you create from now on; just think of it as another useful tool in your arsenal.

Personally, I find it a particularly useful pattern whenever I'm building the first draft of some set of classes that all need to work together. The added flexibility of a conf gives me so much more flexibility, freeing me up to zip around all the different functions and changing how they call one another, without worrying about breaking the code by inserting or removing a parameter.

Remember the benefits:

  • It's easy to add and remove parameters (at either end).
  • It's easy to set default values.
  • You don't have to worry about the order of the parameters.

There are drawbacks to using simple objects like I have, though - especially if you do so in a project that's past the prototyping stage. Check out the great comments below for more details!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.