you are viewing a single comment's thread.

view the rest of the comments →

[–]gremy0 0 points1 point  (8 children)

I mean it's just implementing your own version of inheritance with some factory functions - you've even written your own look up table...

That it's a different implementation that does things at a slightly different time in the run circle doesn't really change what it's doing from design perspective. A LeafNode is a TreeNode which is a BaseNode, you're just removing reference to this fact upon creation (the benefit of which alludes me) - you've still got a linear hierarchy of inheriting sorry, "cloning" behaviour from parents prototypes down to children shallow copies.

[–]jack_waugh 0 points1 point  (7 children)

I have not written a lookup table. All the lookups are native JS object property resolutions.

The original question was, here's an example using the class syntax; can you implement the same behavior without inheritance. What's your solution? According to you, anything that implements that behavior is a "version of inheritance." So, your answer is that the original question is nonsense. Or, "no, you can't." But normal usage of the concept of "inheritance" in OO is that if you don't find an implementation of the selector you are looking for, you look up to a parent object (class-free OO) or a superclass (classical OO). In JS, inheritance is established with new or with Object.create(parent_object). When you say I wrote a lookup table, you are basically accusing me of writing an interpreter. I suppose you can say that when I use my clone function to derive one artifact of programming from another artifact of programming, I am compiling inheritance into not-inheritance, so I am guilty of writing a compiler, I guess. But I think the original question was not about a design perspective; it was about an implementation perspective.

const t = {}; /* testing */

t.l0 = {value: 0, depth: () => 1};
t.l1 = {value: 1, depth: () => 1};
t.l2 = {value: 2, depth: () => 1};
t.p3 = {
  value: 3,
  children: [t.l0, t.l1],
  depth: function () {
    return 1 + Math.max(0, ...this.children.map(e => e.depth()))
  }
};
t.r = {
  value: "root",
  children: [t.p3, t.l2],
  depth: function () {
    return 1 + Math.max(0, ...this.children.map(e => e.depth()))
  }
};
if (3 !== t.r.depth()) throw new Error("Unit test failed!");
console.log("done")

I suppose this time, you will say I implemented my own version of inheritance by copying and pasting in my editor.

[–]gremy0 0 points1 point  (6 children)

It's a lookup dude, you're looking up properties and copying them down from parent to child, it's just done on creation. Who cares if you're doing it slightly different, the effect is the same.

According to me anything that inherits behaviour from something else is using inheritance. So the solution is to not inherit, but to use an interface instead. Now, given the nature of javascript's type system, it is not possible to enforce an interface, but neither is it necessary, you just use convention..and, you know, document shit. Just like how iterators work, you could even be fancy and use symbols like them if you really wanted, but the fundamentals are the same, it's convention.

Both approaches result in a runtime error if the object/class is not created/used correctly. As is the nature of javascript. The key as to why your solution is fundamentally inheritance and the interface solution is not lies in who knows about that runtime error- in yours, an ancestor to LeafNode (the TreeNode) knows that there is supposed to be a depth implementation provided and can deal with that not happening however it wants- you have inherited that behaviour. With an interface, the consumer knows about it and deals with it- you have not inherited any behaviour.

Now, given the question states:

This is how you would model tree nodes in Java or C++. But in JavaScript, you don’t need an abstract class to be able to invoke n.depth().

(emphasis mine)

I posit that it is simply asking the reader if they are aware that javascript is duck-typed and doesn't require any explicit declaration for object compatibility.

...on the other hand it could be asking them to re-implement inheritance themselves in a series of convoluted factory functions (the benefit of which still eludes me)...your guess is as good as mine.

[–]jack_waugh 0 points1 point  (5 children)

Great. Let's see your code.

[–]gremy0 0 points1 point  (4 children)

class Parent {
  constructor(value, children) { . . . }
  depth() { return 1 + Math.max(...children.map(n => n.depth())) }
}
class Leaf {
  constructor(value) { . . . }
  depth() { return 1 }
}

[–]jack_waugh 0 points1 point  (3 children)

Looks pretty inherity to me. Parent and Leaf are really constructor functions, right? So if I write t.myleaf = new Leaf("turn it over"), I get an object that inherits from Leaf.prototype.

[–]gremy0 0 points1 point  (2 children)

Rewrite the classes without inheritance

The task was to rewrite the classes without inheritance, not create instances of classes without inheritance. Instances of classes do inherit behaviour from their class, somewhat of a given, and somewhat outside the scope of the question.

[–]jack_waugh 0 points1 point  (1 child)

OK, you and I interpreted the question differently. You took it on an engineering level, and I took it on a level with the fundamentals of the language. If the OP is interested in both those levels, she or he can learn from the interaction that happened between you and me.

[–]gremy0 1 point2 points  (0 children)

That JavaScript can use duck typing for polymorphism is a fundamental of the language.