【问题标题】:Renaming a parent class property in subclass在子类中重命名父类属性
【发布时间】:2016-05-21 15:12:37
【问题描述】:

在 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 有一个data 和一个toys 属性。它们都是对同一个数组的引用,但我希望子类只有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

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

【问题讨论】:

  • 好吧,如果您不想要 List 提供的功能,为什么要从 List 继承呢?似乎您的抽象需要重新评估!?
  • 我在基类中添加了更多方法以使要点更清楚。当然,Collection 上还有其他方法,例如您可以对 Backbone 集合执行的各种操作(排序、搜索、保存等)。我的问题是如何覆盖基类上的给定属性,以便派生类可以具有特定于域的方法和属性,仅此而已。
  • 没有反对在派生类上添加附加功能。但是一旦你不将你的集合应用到data-property,List 的所有附加功能都会失败,你必须为派生类包装或重新实现它们;就像您使用 get toyCount() 而不是使用 get count() 一样。整个方法没有意义,只会增加复杂性。我将添加一个更好方法的示例。
  • But what I get is: TypeError: this.todos.sortBy is not a function sortBy 是您的 List 类的方法,但 this.todos 不是 List,它是 Todos 类和数组类型的私有属性,它不实现您的方法。这正是我正在谈论的问题。您必须在 Todos 上重新实现 sortBy 函数以对 todos 属性而不是数据属性进行排序,等等……对于您编写的每个子类。看看我的答案,我写了一个示例代码,可以解决整个问题。

标签: javascript class inheritance ecmascript-6


【解决方案1】:

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

你的列表对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的方法toys()返回数据。你可能觉得奇怪,但如果你继承List,那么你应该使用data(如果不是,不要继承它)。还有另一种选择:您可以删除data 属性,然后创建toys,但唉,在List 中设计sortBy 方法会很困难(或者使用第二个参数来命名要排序的数组?)。所以,让我们使用第一个建议:

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 的定义有点奇怪,因为它具有边框效果(对元素进行排序)。我(个人)会把它写成一个标准方法。

那么我们来做一些测试:

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

var tl = new ToyList(['truck', 'plane', 'doll']);
for (var i=0; i<3; i++) {
  console.log(tl.toys[i]); // access the *pseudo* toys attribute
}

var todos = new Todos(thingsToDo);
var r = todos.byPriority; // access the *pseudo*  byPriority attribute (border effect: sorting internal data)

for (var i=0; i<3; i++) {
  console.log(todos.data[i].priority);
}

我可以建议您多阅读一些关于 OOP 和继承的内容吗?需要子类化但删除数据属性的点肯定是一个糟糕的设计。

【讨论】:

  • 谢谢。 “标准”方法是什么意思?
  • 您对byPriority 的定义授权了属性访问语法,这有点奇怪(至少对我而言)。我更喜欢方法调用。无论如何,这在这里可能不是很重要。
【解决方案2】:

ToyList 同时具有 datatoys 属性。它们指的是同一个数组,就概念化ToyList 的点而言,data 没有多大意义。

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

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


现在你有什么选择?

  • 正如您已经考虑过的,您可以将其设为子类,其中更具体的.toys 属性是.data 的别名。这很好,但你不能避免在那里也有 data 属性。
  • 您可能想彻底废弃data 属性并将元素直接存储在对象上。您的 List 类看起来像“数组但具有有用的辅助函数”。如果这是您的意图,您应该考虑将其设为Array 的实际子类。 @Thomas 的回答朝着这个方向发展。
  • 您可能更喜欢composition 而不是继承。您已经使用了这个概念 - 您的 List 实例在其 data 属性中包含 Arrays。如果您有一个 WishlistToylist 或其他任何东西,专门处理 whishes 或玩具并有相应的方法,您可以简单地将 List 实例存储在它们的 .toys 插槽中。
    考虑到this.todos.sortBy('priority') 的调用(其中this.todos 将是List),您实际上似乎期望您的TodoList 能够像这样工作。在子类中,只需 this.sortBy('priority') 就可以完成这项工作。
  • 我真的不明白你的ToyListList 的特化。如果除了名称之外没有什么特别之处,也许你实际上并不需要一个不同的类。如果 JavaScript 有泛型或类型变量,你会使用 List&lt;Toy&gt;,但它没有,所以你可以直接使用 Lists。

【讨论】:

    【解决方案3】:

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

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

    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;
      }
    }
    

    【讨论】:

    • 这真的很模糊。
    • 对不起,我在回答中途忙于大声笑。更新了一些代码来说明我的意思
    【解决方案4】:

    引用我们在 cmets 中的论述,更好的实现 (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);
    

    编辑:将您的示例添加到待办事项中

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

    【讨论】:

    • 谢谢。最后一行 console.log(todos.byPriority()) 包含 length 作为“元素”。在我看来,您的方法本质上是重新创建数组,列表中的每个元素都作为List 本身的属性添加。这是一种有趣的方法(尤其是当它可以实际执行 class List extends Array 时),但它解决的问题与我原来的问题完全不同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 1970-01-01
    • 2015-10-02
    • 2012-09-01
    相关资源
    最近更新 更多