如何防止特定方法的继承?

时间:2017-07-17 19:08:37

标签: javascript inheritance ecmascript-6

假设我有两个班级ABB扩展A,因此继承了其所有方法。如果我也愿意,我可以覆盖它们。我的问题是,我是否可以阻止B继承A的特定方法。到目前为止,我所尝试过的是这样的。



// 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());




6 个答案:

答案 0 :(得分:2)

有几个人已经告诉你如何来解决你的问题。现在我想说服你不要这样做。 ; - )

继承应该模拟IS-A关系。或者,更具体地说,是IS-SUBSTITUTABLE-FOR-A关系。这是Liskov Substitution PrincipleSOLID中的“L”)。

任何期望在“A”对象上操作的代码应该能够被赋予“B”对象(“A”代替“B”)并且一切应该仍然可以正常工作。但是如果“B”缺少一个方法,如果它没有提供“A”的完整接口,那么它就不再是可替代的,并且几率非常好,这不是使用继承的正确情况。

答案 1 :(得分:1)

实际上valueOf是一个不好的例子,因为每个对象都来自Object.prototype。试试console.log(({}).valueOf())

但你可以通过隐藏这个属性来做一个技巧

// 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)
}

答案 2 :(得分:1)

不,这是不可能的。 JS中的继承意味着从另一个对象(原型)继承,而不是继承其各自的属性。如果从对象继承,则继承 all 其属性。

正如你所说,你可以覆盖当然的属性。您不能delete它们,因为继承对象根本不包含它们 - 您需要从原型中删除它们,这是您不想要的。但是,您可以使用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;

答案 3 :(得分:0)

delete this.valueOfdelete B.prototype.valueOf无法正常工作,因为没有valueOf属性可供删除。通过在对象本身找不到属性时搜索原型链,而不是通过从父对象复制属性,继承是有效的。

您可以做的是为this.valueOf分配一些内容,以便它不会跟随链:

this.valueOf = null;

然后会出现null不是函数的错误。

而不是在每个对象中执行此操作,您可以在B的原型中执行此操作:

B.prototype.valueOf = null;

答案 4 :(得分:0)

在你的例子中,B不扩展A,而是扩展了A的一部分。这个特征子集是实际的共同祖先,所以你可以重构,这样A和B都扩展了一个共同的类X.

您也可以在B中实现该功能,以便明确抛出错误,但这似乎不是您的目标。在Javascript中,使用组合来创建具有相同功能但只有一个实现点的类已经变得很流行。

以下是如何使用合成来编写它:

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

这是一个快速的例子,有很多不同的模型用于实现组合,有些人使用Class对象,但我不知道那个是我的头脑。

答案 5 :(得分:0)

@Bergi已经解释了处理这种整形类型的why inheritance is not the right tool

正在寻找的是基于 mixin 的合成技术。由于提供的特殊示例,它甚至可以是 traits ,因为它们可以处理行为组合,具有覆盖,别名,省略甚至修改它的同时参与 traits

由于JavaScript不支持特征,因此接近它的解决方案可能类似于SO提供的解决方案 "Mixins for ES6 classes, transpiled with babel"

基础模式有功能代理对象通过闭包委托通过{{1 } / apply以及转发

一种直接的方法,指的是OP提供的示例代码,并且还使用了上面提到的一些技术和mixin-patterns看起来像这样......



call

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's 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());