new instantiation design alternatives

Allen Wirfs-Brock allen at wirfs-brock.com
Fri Sep 12 09:12:34 PDT 2014


On Sep 12, 2014, at 8:18 AM, Mark S. Miller wrote:

> Even when written explicitly, either by an IDE or a human, the 
> 
> constructor(a, b, c) {
>     this = new super(...arguments);
>     ...
> }
> 
> pattern is usually bad. It is fine in a certain special case I mention below. It would be a disaster to have this pattern be the default smoothed over by a syntactic shortcut. Here's the problem:
> 
> ----- at time v1 -----
> class Point {
>     constructor(x, y) 
>         this.x = x;
>         this.y = y;
>     }
>     equals(otherPt) {
>         return this.x === otherPt.x && this.y === otherPt.y;
>     }
> }
> 
> class ColoredPoint extends Point {
>     constructor(x, y, color) {
>         this = new super(x, y);
>         this.color = color;
>     }
>     // whether we override equals here is not the issue
> }
> 
> ---- at later time/revision v2 ----
> class Point {
>     constructor(x, y, fudgeFactor = 0.000001) {
>         this.x = x;
>         this.y = y;
>         this.fudgeFactor = fudgeFactor;
>     }
>     equals(otherPt) {
>         return abs(this.x - otherPt.x) <= fudgeFactor &&
>                 abs(this.y - otherPt.y) <= fudgeFactor;
>     }
> }
> --------
> 
> Under normal circumstances, as shown above, this is a valid evolution of the Point class, even without examining or revising existing clients like ColoredPoint. Since the Point constructor had only two non-optional parameters, it could normally assume that existing clients had only called it with two arguments. Thus, it would normally be assumed compatible with existing clients to add new optional arguments. Passing all arguments by default breaks this assumption, making the "fragile base class" problem much worse.


Mark, making such a change in a v2 would normally be considered bad practice in any OOP language I've worked with and probably would violate  most local style guidelines.  Subclassing for implementation sharing is at best gray box encapsulation.  The developer of a subclass who over-rides and/or super invokes methods of a base class has to know some things about the base.  Ideally, those requirements are  captures in an explicitly documented subclassing contract provided by the base class' developer, but even if there is no such documentation there is always an implicit subclassing contract for every class that is used as a base class.

If you are the developer of a such a base class, there are practical constraints on if and how you can change your subclassing contract. 

If you have access to all code that subclasses your base class, you can make any changes you want as long as you are willing to updated the code of every subclass.  This is one of the tasks that motivated the invention of OO refactoring tools. You v2 change would be a change that fall into into this category.  If you have access to all subclasses you can make the change, but you are also responsible for updating the subclasses.

But if you don't have access to all subclassing clients (for example if you base class is part of a widely used library or framework) you simply can't change your subclassing contract in this manner because, as you point out, you can't update existing client code that depends upon the existing contract.  Whether or not auto new super occurs, the problem still exists as a subclass client may well have explicitly coded `new super(...arguments)` in its subclass constructor.   Arguably, such a subclass would be operating within the rules of the implicit subclassing contract of the v1 Point as its parameter list implies that it will pay attention to only two arguments.  If the v1 developer wanted to future proof its constructor it should have stated it as `constructor (x,y, ...reservedForTheFuture)`. Note that this general problem applies to all methods that might be super invoked, not just constructors.

To me, this issue is one of ease of use for the most common use case versus a slight refactoring hazard for a rare, and and likely ill-advised design change pattern.  The most common use case is a subclass that simply adds an additional constructor parameter corresponding to an additional instance variable.  And it that case, most people we shown the alternatives to prefer this:
```js
 class ColoredPoint extends Point {
    constructor(x, y, color) {
        this.color = color;
    }
}
```
over 
```js
 class ColoredPoint extends Point {
    constructor(x, y, color) {
        this = new super(x, y);
        this.color = color;
    }
}
```

Allen

> 
> The special case where the super(...arguments) is ok, even needed, is when the base class constructor already has a rest parameter.
> 
> EIBTI applies forcefully here.
> 
> -- 
>   Cheers,
>   --MarkM
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20140912/eafcd45f/attachment-0001.html>


More information about the es-discuss mailing list