This is an example from the Practical Object-Oriented Design in Ruby book. I am interested in translating this ruby code into javascript to have a better understanding of duck-typing in JS. Can anyone help translate this code to clarify how best to write this in Javascript or at least help get me started?
The trip class here is acting as the interface to the other classes (Mechanic, Trip_Coordinator, Driver). Where the prepare method in the class Trip uses the Duck type preparer.
class Trip
attr_reader :bicycles, :customers, :vehicle
def prepare(preparers)
preparers.each {|preparer|
preparer.prepare_trip(self)}
end
end
class Mechanic
def prepare_trip(trip)
# Does something with trip.bicycles
end
end
class Trip_Coordinator
def prepare_trip(trip)
# Does something with trip.customers
end
end
class Driver
def prepare_trip(trip)
# Does something with trip.vehicle
end
end
Update:
I have added code that I believe is a possible translation of the Ruby code above. However, when I run the code I get the following output and error:
mountain
2
jeep
/home/ubuntu/user/tests/trip.js:3
preparer.prepare_trip(this)
TypeError: Object [object Object] has no method 'prepare_trip'
Advice or suggestions would be appreciated to further improve the understanding of duck-typing in JS.
Javascript Code:
var Trip = function(preparers){
return preparers.forEach(function(preparer){
preparer.prepare_trip(this)
}
)};
var Mechanic = function(trip){
// does something with trip.bicycles
prepare_trip: console.log(trip.bicycles);
};
var TripCoordinator = function(trip){
//does something with trip.customers
prepare_trip: console.log(trip.customers);
};
var Driver = function(trip){
//does something with trip.vehicle
prepare_trip: console.log(trip.vehicle);
};
// customer wants to go on a trip for two and needs a car
var planA = {
bicycles: "mountain",
customers: 2,
vehicle: "jeep"
};
//customer wants to go a trip for one and only ride a bike
var planB = {
bicycles: "road",
customers: 1
};
Trip([new Mechanic(planA), new TripCoordinator(planA), new Driver(planA)]);
Trip([new Mechanic(planB), new TripCoordinator(planB)]);
UPDATE 2
Per the solution and advice from fgb below, I have place the final solution for my question below. I added an agent to remove the dependency of the caller having to know what prepares they would need when create a trip plan.
var Agent = function(plan){
if("bicycles" && "customers" && "vehicle" in plan){
Trip([new Mechanic(plan), new TripCoordinator(plan), new Driver(plan)]);
}
else if(!("vehicle" in plan) && "bicycles" && "customers" in plan){ //A driver is not needed
Trip([new Mechanic(plan), new TripCoordinator(plan)]);
}
};
var Trip = function(preparers){
return preparers.forEach(function(preparer){
preparer.prepare_trip(this)
}
)};
var Mechanic = function(trip){
// does something with trip.bicycles
this.prepare_trip = function() {
console.log(trip.bicycles);
}
};
var TripCoordinator = function(trip){
//does something with trip.customers
this.prepare_trip = function() {
console.log(trip.customers);
}
};
var Driver = function(trip){
//does something with trip.vehicle
this.prepare_trip = function() {
console.log(trip.vehicle);
}
};
// customer wants to go on a trip for two and needs a car
var planA = {
bicycles: "mountain",
customers: 2,
vehicle: "jeep"
};
//customer wants to go a trip for one and only ride a bike
var planB = {
bicycles: "road",
customers: 1
};
Agent(planB);
Related SO discussion Example of Javascript Duck Typing?
The code is almost right, but you're mixing up the syntax for constructors and object literals. When you do:
var Driver = function(trip){
//does something with trip.vehicle
prepare_trip: console.log(trip.vehicle);
};
The 'prepare_trip:' is actually a label, and doesn't define a property of an object.
One way of fixing it would be:
var Driver = function(trip){
//does something with trip.vehicle
this.prepare_trip = function() {
console.log(trip.vehicle);
};
};
Also note the code isn't quite the same as the Ruby code:
preparer.prepare_trip(this);
Here, this
is the global object instead of the trip object, and the parameter isn't used in the method. In your code the trip parameter is passed in the construstor of the preparer instead.