在子类中重命名父类属性

时间:2016-02-11 01:30:42

标签: javascript class inheritance ecmascript-6

在es2015中,如果我有基类来表示看起来像这样的List

class List {
  constructor(data){
    this.data = data
  }

  sortBy(attribute){
    return this.data.sort((a,b) => {
      return (a[attribute] < b[attribute]) ? 1 : -1;
    })
  }
  get count() { return this.data.length }
}

然后我可能想要用不太通用的数据子类化该基类,即,如果我是一个精灵,玩具:

class ToyList extends List {
  constructor(toys){
    super(toys);
    this.toys = toys;
  }
}

此时ToyListList没有区别,但名称除外。但是,如果您查看ToyList的实例,则它具有 datatoys属性。这些引用相同的数组,在概念化ToyList的点时,data没有多大意义。

如果我制作ToyList,我同时拥有.data.toys属性:

tl = new ToyList(['truck', 'plane', 'doll'])
Object { data: Array[3], toys: Array[3] }

然后我的tl同时拥有datatoys属性。它们都是对同一个数组的引用,但我想要的是的子类具有toys引用。

这是另一个在基类上使用方法的例子:

class Todos extends List {
  constructor(todos){
    super(todos);
    this.todos = todos;
  }

  get byPriority(){
    return this.todos.sortBy('priority')
  }
}

var thingsToDo = [
  {task: 'wash the dog', priority: 10},
  {task: 'do taxes', priority: 1},
  {task: 'clean the garage', priority: 0}

]

var todos = new Todos(thingsToDo);
todos.byPriority

这样会很好,因为我可以引用.byPriority来获取列表的排序版本,该版本非常特定于这种特定类型的数据。但我无法看到如何实现这一点,因为

但我得到的是:

TypeError: this.todos.sortBy is not a function

总而言之,我想要的是一种使用特定于子类语义的名称来引用基类属性的方法,而不会丢失基类的方法。

4 个答案:

答案 0 :(得分:2)

在评论中引用我们的不满,更好的实施(imo),可扩展并避免您询问的问题

var AP = Array.prototype; //just lazy

class List {
    constructor(elements){
        for(var i = 0, j = (elements && elements.length)|0; i<j; ++i)
            this[i] = elements[i];
        //use length instead of count, stay compatible with the Array-methods
        //will make your life easier
        this.length = i;
    }

    length: 0

    sortBy(attr){
        return this.sort(function(a,b){
            return (a[attribute] < b[attribute]) ? 1 : -1
        });
    }

    //some functions have to be wrapped, to produce a List of the right type
    filter(fn){
        return new (this.constructor)(AP.filter.call(this, fn));
    }

    clone(){ return new (this.constructor)(this) }
}

//some functions can simply be copied from Array
//no need to re-implement or even wrap them.
List.prototype.sort = AP.sort;
List.prototype.push = AP.push;
List.prototype.pop = AP.pop;

子类

class ToyList extends List {
  constructor(toys){
    //maybe you want to filter the input, before you pass it to the list
    //or convert it, or whatever, it's all up to you
    super(toys && AP.filter.call(toys, v=>v instanceof Toy));
  }

  //... additional functionality
}

和示例用法

class Toy {
  constructor(name){
    this.name = name;
  }
}

var a = new ToyList([
  new Toy("foo"), 
  new Toy("bar"), 
  "not a toy",
  new Toy("baz") 
])

console.log(a instanceof ToyList, a);

var b = a.filter(toy => toy.name.charAt(0) === "b");

console.log(b instanceof ToyList, b);

编辑:使用Todos

添加了您的示例
class Todos extends List {
    //don't even need a constructor, since I simply want to pass 
    //the array to the parent-constructor

    //don't use getter for functionality, use methods!
    byPriority(){
        return this.sortBy('priority');
    }
}

var thingsToDo = [
  {task: 'wash the dog', priority: 10},
  {task: 'do taxes', priority: 1},
  {task: 'clean the garage', priority: 0}
]

var todos = new Todos(thingsToDo);
todos.byPriority()

答案 1 :(得分:2)

  

ToyList同时拥有datatoys属性。这些引用相同的数组,在概念化ToyList的点时,data没有多大意义。

您的实际问题是:ToyList作为List的子类有意义。

如果(出于任何原因)您的课程应该与List类似,但没有data属性,那么它不再是一个子类 。它会违反Liskov substitution principle

现在你有什么选择?

  • 正如您已经考虑过的那样,您可以将其作为子类,其中更具体的.toys属性是.data的别名。这很好,但你也不能避免在那里拥有data财产。
  • 您可能希望直接废弃data属性并将元素直接存储在对象上。您的List类看起来像#34;数组,但有有用的辅助函数&#34;。如果这是您的意图,您应该考虑将其作为Array的实际子类。 @ Thomas的答案就是那个方向。
  • 您可能希望支持composition而不是继承。您已经使用过这个概念 - 您的List个实例在Array属性中包含data个。如果你有WishlistToylist或其他任何专门用于处理眼镜或玩具的方法,并且有相应的方法,你只需在他们的List中存储.toys个实例插槽。
    考虑到TodoList的调用(其中this.todos.sortBy('priority')将是this.todos),您实际上似乎希望List能够像这样工作。在子类上,只需要this.sortBy('priority')即可完成工作。
  • 我并没有真正理解你的ToyListList的专长。如果除了名字之外没有什么特别之处,也许你真的不需要一个完全不同的课程。如果JavaScript具有泛型或类型变量,则您使用List<Toy>,但它不会直接使用List

答案 2 :(得分:2)

我认为你有很多不同的问题。

您的列表对sortBy的定义有疑问,您需要考虑3个案例,如下:

class List {
  constructor(data){ this.data = data; }
  sortBy(attribute){
    console.log("sortBy");
    return this.data.sort( (a,b) => {
      if (a[attribute] < b[attribute]) return -1;
      if (a[attribute] > b[attribute]) return 1;
      return 0;
    });
  }
  get count() { return this.data.length; }
}

现在您可以扩展List,如果您想将data命名为toys,请定义名为get的{​​{1}}方法以返回数据。它可能很奇怪,但是如果你继承toys()那么你应该使用List(如果没有,不要将它子类化)。还有一种方法:您可以删除data属性,然后创建data但是唉,在toys中设计sortBy方法会很困难(或使用第二个参数来命名要排序的数组?)。所以,让我们使用第一个建议:

List

class ToyList extends List { constructor(toys){ super(toys); } get toys() { return this.data; } }

做同样的事情
Todos

class Todos extends List { constructor(todos){ super(todos); } get todos() { return data; } get byPriority(){ return this.sortBy('priority'); } } 的定义有点奇怪,因为它有边框效应(排序元素)。我(个人)会把它写成标准方法。

然后让我们进行一些测试:

byPriority

我建议您多阅读一下有关OOP和继承的内容吗?需要子类但删除数据属性当然是一个糟糕的设计。

答案 3 :(得分:0)

IMO,如果可能的话,更好的方法是将List类视为纯虚拟,意味着你永远不会创建该类的实例,而只是从它扩展。

纯虚拟类不应该具有构造函数,并且方法是假设存在某些属性。但是,您可以使用构造函数来设置基类应该用于“data”属性的名称。

class List {
 constructor(keyName) { this.keyName = keyName }
 sortBy(attr) { return this[this.keyName].sort(...) } 
}

class ToyList extends List {
  constructor('toys') {
    super(toys)
    this.toys = toys;
  }
}