How to prevent inheritance of a specific method?

Let's say I have two classes, A

and B

. B

extends A

and therefore inherits all of its methods. I can override them if I want. My question is if I can prevent B

from inheriting a specific method A

. What I've tried looks like this.

// setup
class A {
  constructor(x) {
    this.x = x;
  }

  valueOf() {
    return this.x;
  }

  toString() {
    return `{x:${this.x}}`;
  }
}

class B extends A {
  constructor(x) {
    super(x);
    delete this.valueOf;
  }
}

delete B.prototype.valueOf;

// example
const a = new A(42);
const b = new B(42);

// should work
console.log(a.valueOf());
// should throw TypeError, not a function
console.log(b.valueOf());
      

Run codeHide result


+3


source to share


6 answers


This is actually valueOf

a bad example, because every object gets one of the Object.prototype

. Tryconsole.log(({}).valueOf())

But you could do the trick by hiding this property



// setup
class A {
  constructor(x) {
    this.x = x;
  }

  valueOf() {
    return this.x;
  }

  toString() {
    return `{x:${this.x}}`;
  }
}

class B extends A {
   get valueOf() { return undefined }
}

class C extends A {
}

Object.defineProperty(C.prototype, 'valueOf', {})


// example
const a = new A(42);
const b = new B(42);
const c = new C(42);

// should work
console.log(a.valueOf());
// should throw TypeError, not a function
try {
  console.log(b.valueOf());
} catch (e) {
  console.log(e.message)
}



try {
  console.log(c.valueOf());
} catch (e) {
  console.log(e.message)
}
      

Run codeHide result


+1


source


Several people have already told you how to solve your problem. Now I would like to try to convince you not to do this .; -)

Inheritance should model the IS-A relationship. Or, more specifically, the IS-SUBSTITUTABLE-FOR-A relationship. What is the Liskov Substitution Principle ("L" in SOLID ).



It is assumed that any piece of code that expects to work on object "A" could have object "B" (instead of "A" instead of "B") and everything should only work, But if "B" there is no method, if it is not provides a complete "A" interface, it is no longer replaceable, and the coefficients are very good, this is not the right situation to use inheritance.

+2


source


No, It is Immpossible. Inheritance in JS means inheriting from another object (prototype), not inheriting its individual properties. If you inherit from an object, you inherit all of its properties.

As you said you can override properties of course. You cannot have delete

them, since the inheriting object does not contain them at all - you will need to remove them from the prototype, which you do not need. However, you can easily shade it with a value undefined

:

class A {
  constructor(x) {
    this.x = x;
  }

  valueOf() {
    return this.x;
  }

  toString() {
    return `{x:${this.x}}`;
  }
}

class B extends A {
}
B.prototype.valueOf = undefined;

      

+1


source


delete this.valueOf

and delete B.prototype.valueOf

don't work because there is no property valueOf

to delete. Inheritance works by looking up the prototype chain when the property is not found in the object itself, rather than by copying properties from the parent.

What you can do is assign something this.valueOf

so that it doesn't follow the chain:

this.valueOf = null;

      

Then it will get an error that is null

not a function.

And instead of doing this in every object, you can do this in the prototype B

:

B.prototype.valueOf = null;

      

0


source


In your example, B is not extending A, but extending part A. This subset of functionality is the actual common ancestor, so you can refactor so that A and B extend the common class X.

You can also implement a function in B to explicitly throw an error, but that doesn't seem to be your goal. It has become popular in Javascript to use composition to create classes with the same functionality, but one point of implementation.

This is how you could write it with composition:

const ValueProto = {
  getValue() {
    return this.x
  }
}

const StringProto = {
  toString() {
    return `{x:${this.x}}`;
  }
}

const ClassA = (x) => Object.assign({}, ValueProto, StringProto, {x});
const ClassB = (x) => Object.assign({}, StringProto, {x});

const object1 = ClassA(5)
console.log(object1.toString()) // 'x:5'
console.log(object1.getValue()) // 5


const object2 = ClassB('hello')
console.log(object2.toString()) // 'x:hello'
console.log(object2.getValue()) // not a function
      

Run codeHide result


This is a quick example, and there are several different models for implementing composition that use a class object, but I don't know what this is from my head.

0


source


@Bergi has already explained why inheritance is not the right tool for dealing with these types of types.

What to look for are mixin-based composition techniques. Thanks to the custom example provided, this would even be a trait as they can handle composition of behavior with rewriting, aliasing, missing and even modifying, while still using both properties and classes.

Since JavaScript doesn't support traits, a solution that comes close to it might look like the one shown in SO on "Mixins for ES6 classes overflowed by babel"

Basic templates have functions and proxy objects via closure, delegation via apply

/ call

, and redirects.

A straight forward approach that links to the example code provided by the OP and also uses some of the above techniques, and mixin templates might look like ...

function withFullyExposeX() {   // function based mixin pattern ...
                                //
  this.valueOf = function () {  // ... implementing `this` bound
    return this.x;              // behavior that always has to be
  };                            // applied via `call` or `apply`
  this.toString = function () { // onto any object type.
    return `{x:${this.x}}`;     //
  };                            //
}                               //

var withExposeXValueAndShadowXStringify = (function (mixin) {
  return function () {
    mixin.call(this);                   // - apply existing mixin/trait.
    this.toString = function () {};     // - overwrite specific behavior.
  };
}(withFullyExposeX));

var withProxyOnlyExposesValueOfX = (function (mixin) {  // function based trait pattern ...
  var localProxy = {};                                  //
  mixin.call(localProxy);                               // ... that also is mixin based but
                                                        // uses a local proxy object in order
  return function () {                                  // to reuse the implementation of an
                                                        // already existing mixin or trait.
    this.valueOf = function () {                        //
      return localProxy.valueOf.call(this);             // thus such a pattern not only uses
    };                                                  // explicit delegation via `apply`/`call`
  //this.toString = function () {} // - overwrite.      // but it in addition features forwarding
  }                                                     // via it encapsulated proxy object.
}(withFullyExposeX));                                   //


class X {
  constructor(x) {
    this.x = x;
  }
}

class A extends X {}
//  constructor(x) {
//    //withFullyExposeX.call(this);  // applying the mixin does work for both positions ...
//  }                                 //
//}                                   //
withFullyExposeX.call(A.prototype);   // ... but prototype in this case is the better choice.

class B extends X {}
withExposeXValueAndShadowXStringify.call(B.prototype);

class C extends X {}
withProxyOnlyExposesValueOfX.call(C.prototype);

var
  x = new X('x'),

  a = new A('a'),
  b = new B('b'),
  c = new C('c');

console.log('x.valueOf : ', x.valueOf);
console.log('a.valueOf : ', a.valueOf);
console.log('b.valueOf : ', b.valueOf);
console.log('c.valueOf : ', c.valueOf);

console.log('x.valueOf() : ', x.valueOf());
console.log('a.valueOf() : ', a.valueOf());
console.log('b.valueOf() : ', b.valueOf());
console.log('c.valueOf() : ', c.valueOf());

console.log('x.toString : ', x.toString);
console.log('a.toString : ', a.toString);
console.log('b.toString : ', b.toString);
console.log('c.toString : ', c.toString);

console.log('x.toString() : ', x.toString());
console.log('a.toString() : ', a.toString());
console.log('b.toString() : ', b.toString());
console.log('c.toString() : ', c.toString());
      

.as-console-wrapper { max-height: 100%!important; top: 0; }
      

Run codeHide result


0


source







All Articles