Search code examples
javascriptrubyoopduck-typing

Duck Typing: Translating Ruby Code into Javascript Utilizing Duck Typing


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?


Solution

  • 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.