In a project for fun/practice, I made a few builder classes that all started to feel the same, so I decided to generalize them by creating a factory that would automatically assign the similarities that they all shared.
- Each of my builders had a private variable,
data
, which would eventually become the built class. - Each builder had a
reset
method that would "clear" thedata
object. - And each builder had a
create
method that would return thedata
object and call thereset
method so that the builder could then be used again (they're kind of more like some breed of assembly line, mixing qualities from both builders and factories at this point).
Here's my generalized factory for making the builders:
function createBuilder(settersMap) {
var data = {};
return Object.keys(settersMap).reduce(function(builder, name) {
builder[name] = function() {
settersMap[name].apply(data, arguments);
}
return builder;
}, {
reset: function() { // so users can clear the builder
data = {};
},
create: function() {
var created = data;
data = {};
return created;
}
});
}
I use the factory to create driverBuilder
and a carBuilder
like so:
// Drivers
var driverBuilder = createBuilder({
setName: function(first, last) {
this.name = first + " " + last;
},
setAge: function(age) {
this.age = age;
},
setSkillLevel: function(skillLevel) {
this.skillLevel = skillLevel;
}
});
var drivers = [];
driverBuilder.setName("Joe", "McMullen");
driverBuilder.setAge("26");
driverBuilder.setSkillLevel(6);
drivers.push(driverBuilder.create());
// Cars
var carBuilder = createBuilder({
setMake: function(make) {
this.make = make;
},
setModel: function(model) {
this.model = model;
},
setTopSpeed: function(topSpeed) {
this.topSpeed = this.topSpeed === undefined ? topSpeed : this.topSpeed + topSpeed;
},
addTurboBoost: function(boost) {
this.topSpeed = this.topSpeed === undefined ? boost : this.topSpeed + boost;
}
});
var cars = [];
carBuilder.setMake("Volkswagen");
carBuilder.setModel("Beetle");
carBuilder.setTopSpeed(150);
carBuilder.addTurboBoost(30);
cars.push(carBuilder.create());
I then spent the next billion years programming up a gui using jquery which you can mess around with if you'd like to check it out:
var RacingLeague = (function() {
// Utility
function createBuilder(settersMap) {
var data = {};
return Object.keys(settersMap).reduce(function(builder, name) {
builder[name] = function() {
settersMap[name].apply(data, arguments);
}
return builder;
}, {
reset: function() {
data = {};
},
create: function() {
var created = data;
data = {};
return created;
}
});
}
// Drivers
var driverBuilder = createBuilder({
setName: function(first, last) {
this.name = first + " " + last;
},
setAge: function(age) {
this.age = age;
},
setSkillLevel: function(skillLevel) {
this.skillLevel = skillLevel;
}
});
var drivers = [];
driverBuilder.setName("Joe", "McMullen");
driverBuilder.setAge("26");
driverBuilder.setSkillLevel(6);
drivers.push(driverBuilder.create());
driverBuilder.setName("Sam", "Benito");
driverBuilder.setAge("21");
driverBuilder.setSkillLevel(3);
drivers.push(driverBuilder.create());
driverBuilder.setName("David Jorgensen"); // whoops!
driverBuilder.reset();
driverBuilder.setName("David", "Jorgensen");
driverBuilder.setSkillLevel(5);
drivers.push(driverBuilder.create());
// Cars
var carBuilder = createBuilder({
setMake: function(make) {
this.make = make;
},
setModel: function(model) {
this.model = model;
},
setTopSpeed: function(topSpeed) {
this.topSpeed = this.topSpeed === undefined ? topSpeed : this.topSpeed + topSpeed;
},
addTurboBoost: function(boost) {
this.topSpeed = this.topSpeed === undefined ? boost : this.topSpeed + boost;
}
});
var cars = [];
carBuilder.setMake("Volkswagen");
carBuilder.setModel("Beetle");
carBuilder.setTopSpeed(150);
carBuilder.addTurboBoost(30);
cars.push(carBuilder.create());
carBuilder.setMake("Dodge");
carBuilder.setModel("Charger");
carBuilder.setTopSpeed(190);
cars.push(carBuilder.create());
carBuilder.setMake("Pontiac");
carBuilder.setModel("Firebird");
carBuilder.setTopSpeed(180);
carBuilder.addTurboBoost(20);
cars.push(carBuilder.create());
return {
cars: cars,
drivers: drivers,
determineWinner: function(pair1, pair2) {
var odds = 0.5;
odds += pair1.driver.skillLevel > pair2.driver.skillLevel ? 0.15 : 0.00;
odds -= pair1.driver.skillLevel < pair2.driver.skillLevel ? 0.15 : 0.00;
odds += pair1.car.topSpeed > pair2.car.topSpeed ? 0.15 : 0.00;
odds -= pair1.car.topSpeed < pair2.car.topSpeed ? 0.15 : 0.00;
return Math.random() <= odds ? pair1 : pair2;
}
};
})();
// DOCUMENT READY
$(document).ready(function() {
function getDriverId(driver) {
return driver.name;
}
function getDriver(driverId) {
return RacingLeague.drivers.find(function(driver) {
return driver.name === driverId;
});
}
function getCarId(car) {
return `${car.make} ${car.model}`;
}
function getCar(carId) {
carId = carId.split(" ");
return RacingLeague.cars.find(function(car) {
return car.make === carId[0] && car.model === carId[1];
});
}
var winsMap = {};
RacingLeague.drivers.map(getDriverId).forEach(function(driverId) {
winsMap[driverId] = 0
$("#score-board").append($(`<span>${driverId} Wins: <span id="${driverId}"></span></span><br>`));
});
RacingLeague.cars.map(getCarId).forEach(function(carId) {
winsMap[carId] = 0
$("#score-board").append($(`<br><span>${carId} Wins: <span id="${carId}"></span></span>`));
});
function refreshWins(winners) {
if (winners) {
winsMap[getDriverId(winners.driver)]++;
winsMap[getCarId(winners.car)]++;
}
Object.keys(winsMap).forEach(function(key) {
$(`[id='${key}']`).html(winsMap[key]);
});
}
refreshWins();
(function addSelectOptions() {
RacingLeague.drivers.map(getDriverId).forEach(function(driverId) {
$('.select-driver').each(function() {
$(this).append($("<option></option>")
.attr("value", driverId)
.text(driverId));
});
});
RacingLeague.cars.map(getCarId).forEach(function(carId) {
$('.select-car').each(function() {
$(this).append($("<option></option>")
.attr("value", carId)
.text(carId));
});
});
})();
function declareWinner(obj) {
refreshWins(obj);
$("#winner-output").html(`${obj.driver.name} in the ${obj.car.make} ${obj.car.model}`);
}
$("#race-button").click(function() {
declareWinner(RacingLeague.determineWinner({
driver: getDriver($("#driver1").val()),
car: getCar($("#car1").val())
}, {
driver: getDriver($("#driver2").val()),
car: getCar($("#car2").val())
}));
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div id="score-board">
</div>
<hr>
<div id="select-racers">
<select class="select-driver" id="driver1">
</select>
in the
<select class="select-car" id="car1">
</select>
<p>VS</p>
<select class="select-driver" id="driver2">
</select>
in the
<select class="select-car" id="car2">
</select>
</div>
<br>
<button id="race-button">Race!</button>
<br>
<br>
<span>The winner is: </span><span id="winner-output"></span>
</body>
If you have any suggestions for code included in the snippet, I would sincerely appreciate it--I'm not the best front end developer; however, I am quite interested in improving my createBuilder
factory method and in hearing discussion involving its pros, cons, and alternatives as well.
driverBuilder
have over simply creating an array of the object literals you need? \$\endgroup\$