2

In my project I am using JavaScript prototypes to achieve inheritance. I have a super type (like car) and many subtypes (audi/mercedes/...). Every car brand is using 'Horn' which is a quite huge type and should not be duplicated. One method of this type should be overwritten by each car brand, but should only persist in the scope of the specific subtype. Is this possible without creating a Horn subtype for every car subtype? Thank you!

http://codepen.io/trek7/pen/QGYaBW

function Horn() {
  this.level = '123dB';
}

Horn.prototype.doTutut = function() {
  return "Düdelüüüü!";
};

/********************************************/

function Car() {
  this.horn = new Horn();
}

Car.prototype.honk = function() {
  return this.horn.doTutut();
};

/********************************************/

Mercedes.prototype = new Car();

function Mercedes() {
  this.color = 'blue';
}

Mercedes.prototype.drive = function() {
  return "...BrumBrum...";
};

/********************************************/

Audi.prototype = new Car();

function Audi() {
  this.color = 'red';

  /* BAD Overwrite, but how can I achieve this functionality
   without creating another Horn subtype? */
  Horn.prototype.doTutut = function() {
    return "Tütütütütü!";
  };
}

Audi.prototype.honk = function() {
  return this.horn.doTutut();
};

/********************************************/

var car = new Car();      
car.honk();  // Düdelüüüü! - Right!

var mercedes = new Mercedes();      
mercedes.honk();  // Düdelüüüü! - Right!

var audi = new Audi();      
audi.honk();  // Tütütütütü! - Right!

mercedes.honk();  // Tütütütütü! - Wrong!

1 Answer 1

1

The usual thing here would be to parameterize the Horn class so that it accepts the sound to make:

function Horn(sound) {
  this.sound = sound !== undefined ? sound : "Düdelüüüü!";
  this.level = '123dB';
}

Horn.prototype.doTutut = function() {
  return this.sound;
};

Then within, say, Mercedes, you'd either pass the sound to the Car super constructor or set it afterward on this.horn.


If you have an entire method you need to rewrite, you can just assign it to this.horn.theMethod:

function Mercedes() {
  Car.call(this); // See below, important to do this first
  this.horn.theMethod = function() {
    // New implementation here
  };
}

...but it's important that you fix the inheritance stuff below first.

Of course, if you want all Mercedes instances (for instance) to share a single Horn instance, just put the horn with the appropriate method on Mercedes.prototype.


Side note: Your inheritance setup is using an oft-repeated anti-pattern. Don't do this:

Mercedes.prototype = new Car();

If you do that, there are two problems:

  1. Car can't accept arguments, and

  2. Mercedes.prototype.constructor is wrong

  3. Because Car assigns an object (new Horn) to this, all your instances of each subclass (Mercedes, Audi, etc.) end up sharing the same object (one horn). If that object is stateless, that's great, but it should have been on Car.prototype. If it isn't stateless, that creates hard-to-diagnose cross-talk between Car instances.

Also, Mercedes is never calling Car, which it should be doing to give Car the chance to initialize its parts of the instance.

In ES5, this is the correct way to do it:

function Mercedes() {
  // Gives `Car` its chance to do init; could pass on args here if appropriate
  Car.call(this);
  this.color = 'blue';
}

// Set up the prototype chain    
Mercedes.prototype = Object.create(Car.prototype);
Mercedes.prototype.constructor = Mercedes;

Mercedes.prototype.drive = function() {
  return "...BrumBrum...";
};

Of course, in ES2015 and above (which you can use now with transpiling), it's just:

class Mercedes extends Car {
    drive() {
      return "...BrumBrum...";
    }
}

...unless the Mercedes constructor accepts parameters Car doesn't need/use.


Here's an example with updated inheritance and a shared horn across all instances of subclasses:

// Horn with generic default
function Horn(sound) {
    this.sound = sound !== undefined ? sound : "Düdelüüüü!";
    this.level = '123dB';
}

Horn.prototype.doTutut = function() {
  return this.sound;
};

// Car using a single generic horn across all Car instances
function Car() {
}
Car.prototype.honk = function() {
    return this.horn.doTutut();
};
Car.prototype.horn = new Horn();

// Mercedes with its own special horn
function Mercedes() {
    Car.call(this);
}
Mercedes.prototype = Object.create(Car.prototype);
Mercedes.prototype.constructor = Mercedes;
Mercedes.prototype.horn = new Horn("Mercedes Honk!");

// Audi with its own special horn
function Audi() {
    Car.call(this);
}
Audi.prototype = Object.create(Car.prototype);
Audi.prototype.constructor = Audi;
Audi.prototype.horn = new Horn("Audi Honk!");

// Usage
var c = new Car();
console.log(c.honk());
var m = new Mercedes();
console.log(m.honk());
var a = new Audi();
console.log(a.honk());

Sign up to request clarification or add additional context in comments.

4 Comments

yes, thats right. but in my (real) case it's not just the sound, it's a complete method which is different.
@trek7: You can assign a new function to this.horn.theMethod. I've added a note to the answer about that.
@trek7: Added a live example as well.
thank you for your extensive answer. next time I will use ES2015 for sure! ;-) finally I solved my problem by adding an extra method hornHonk() to Car, which is called by honk(). Now I am able to inherit hornHonk in every car subtype and keep the Horn like it is. A little bit strange, but this was the only possibility not to redesign my whole project.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.