最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Renaming a parent class property in subclass - Stack Overflow

programmeradmin7浏览0评论

In es2015, if I have base class to represent a List that looks like this:

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

Then I might want to subclass that base class with a less generic kind of data, namely, if I am an elf, toys:

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

At this point ToyList is no different from List, except for the name. But if you look at an instantiation of ToyList, it has both data and toys properties. These refer to the same array, in terms of conceptualizing the point of a ToyList, data doesn’t make much sense.

If I make a ToyList, I have both .data and a .toys attributes:

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

Then my tl has both a data and a toys attribute. They’re both references to the same array, but what I would like is for the subclass to only have the toys reference.

Here’s another example which utilizes a method on the base class:

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

This would be nice, because then I could just refer to .byPriority to get a sorted version of the list which is very specific to this particular kind of data. But I can’t see how I can make that happen, because

But what I get is:

TypeError: this.todos.sortBy is not a function

So to summarize, what I want is a way to refer to to base class properties with a name which is specific to the semantics of the subclass, without losing the methodology of the base class.

In es2015, if I have base class to represent a List that looks like this:

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

Then I might want to subclass that base class with a less generic kind of data, namely, if I am an elf, toys:

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

At this point ToyList is no different from List, except for the name. But if you look at an instantiation of ToyList, it has both data and toys properties. These refer to the same array, in terms of conceptualizing the point of a ToyList, data doesn’t make much sense.

If I make a ToyList, I have both .data and a .toys attributes:

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

Then my tl has both a data and a toys attribute. They’re both references to the same array, but what I would like is for the subclass to only have the toys reference.

Here’s another example which utilizes a method on the base class:

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

This would be nice, because then I could just refer to .byPriority to get a sorted version of the list which is very specific to this particular kind of data. But I can’t see how I can make that happen, because

But what I get is:

TypeError: this.todos.sortBy is not a function

So to summarize, what I want is a way to refer to to base class properties with a name which is specific to the semantics of the subclass, without losing the methodology of the base class.

Share Improve this question edited Feb 11, 2016 at 4:10 asked Feb 11, 2016 at 1:30 user2467065user2467065 4
  • Well, why do you inherit from List, if you don't want the functionality that List provides? Seems like your Abstraction needs a re-evaluation !? – Thomas Commented Feb 11, 2016 at 2:00
  • I have added a couple more methods to the base class to make the point clearer. Of course there would be other methods on the Collection, the sorts of things you can do with a Backbone collection, for instance (sort, search, save, whatever). My question is how to override a given property on the base class so that the derived class can have domain-specific methods and properties, and nothing else. – user2467065 Commented Feb 11, 2016 at 3:56
  • Nothing against adding additional functionality on the derived classes. But as soon as you don't apply your collection to the data-property all the additional functionality of the List will fail, and you'll have to wrap or re-implement them for the derived classes; like you've done with get toyCount() instead of using get count(). The whole approach makes no sense, and only adds plexity. I'll add an example of a better approach. – Thomas Commented Feb 11, 2016 at 4:08
  • But what I get is: TypeError: this.todos.sortBy is not a function sortBy is a method of your List-class, but this.todos is not a List, it's a private property of the Todos-class and of type Array, wich doesn't implement your methods. That are exactly the problems I'm talking about. You would have to re-implement the sortBy-function on Todos to sort the todos-property instead of the data-property, and so on... for every subclass you write. Take a look at my Answer, i've written an example Code, that would walk around this whole Problem. – Thomas Commented Feb 11, 2016 at 5:00
Add a ment  | 

4 Answers 4

Reset to default 2

referencing our discurrion in the ments, a better implementation (imo), extensible and avoiding the problem you asked about

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

the subclass

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
}

and an example usage

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

Edit: added your Example with the 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()

ToyList has both data and toys properties. These refer to the same array, in terms of conceptualizing the point of a ToyList, data doesn’t make much sense.

There's your actual problem: your ToyList doesn't make sense as a subclass of List.

If (for any reasons) your class should be similar to List, but not have a data property, then it's not a subclass any more. It would violate the Liskov substitution principle.


Now what are your options?

  • as you already considered, you can make it a subclass in which the more specific .toys property is an alias for .data. This is perfectly fine, but you can't avoid having that data property there as well.
  • you might want to outiright scrap that data property and store elements directly on the object. Your List class looks like "Array but with useful helper functions". If that was your intention, you should consider making it an actual subclass of Array. @Thomas's answer goes in that direction.
  • you might want to favor position over inheritance. You've already used the concept - your List instances contain Arrays in their data properties. If you have a Wishlist or Toylist or whatever, that deal specifically with whishes or toys and have corresponding methods for them, you can simply store a List instance in their .toys slot.
    You actually seemed to expect your TodoList to work like that, given the invocation of this.todos.sortBy('priority') (where this.todos would be a List). On an subclass, just this.sortBy('priority') would do the job.
  • I didn't really get how your ToyList is a specialisation of List. If there is nothing special about it but the name, maybe you actually don't need a different class alltogether. If JavaScript had generics or type variables, you'd use a List<Toy>, but it doesn't so you can just use Lists directly.

I think you have a lot of different problems.

Your list has a problem with the definition of sortBy, you need to take 3 cases in account, like this:

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

Now you can extend the List, and if you want to name data as toys then define a get method named toys() to return the data. It may strange to you, but if you subclass List then you should use data (if not, don't subclass it). There is an alternative: you can delete data attribute and then create toys but alas, designing a sortBy method in List would be difficult (or use a second parameter to name the array to sort?). So, let's use the first suggestion:

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

Let do the same for Todos:

class Todos extends List {
  constructor(todos){ super(todos); }
  get todos() { return data; }
  get byPriority(){
    return this.sortBy('priority');
  }
}

The definition of byPriority is a little bit weird as it has a border effect (sorting the elements). I (personally) would write it as a standard method.

Then let's make some tests:

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

May I suggest you to have a little more read about OOP and inheritance? The point of need to subclass but removing data attribute is certainly a bad design.

IMO, a better approach, if possible, would be to treat the List class are pure virtual, Meaning you will never create an instance of that class, but only just extend from it.

Pure virtual classes are not supposed to have constructors, and the methods are to assume certain properties exist. However, you could infact use the constructor to set the name that the base class should use for the 'data' property.

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;
  }
}
发布评论

评论列表(0)

  1. 暂无评论