David
David

Reputation: 138

A class within a class?

Ideally I would like to do something like the following:

class Chess = {
  constructor() {
    this.board = ...;
    ...
  };

  class Square = {
    constructor(row, col) {
      this.row = row;
      this.col = col;
  };
};

My main motivation is that with the Chess and Square classes defined separately something like: (this referring to the Chess class)

this.empty(square)

could be shortened to

square.empty()

which is more readable and more concise.

Unfortunately I can't just make a

Square.empty()

method since the results depends on the information in the Chess class and

square.empty(chess)

is no real improvement.

The reason I have a Square class is that something like

square.up()

seems much nicer than something like

[row, col + 1]

Do you have a suggestion on how I would accomplish the above? Some way to write a class within a class or something else entirely?

EDIT:

Following the advice from likle and alex I did the following:

I added a context property to the Class Square

class Square = {
  constructor(context, row, col) {
    this.context = context;
    this.row = row;
    this.col = col;
  };
};

Then redefined some methods from the Chess.prototype to the Square.protoype. For example:

// before
Chess.prototype.empty = function (square) {
  return this.piece(square) === 0;
};

// after
Square.prototype.empty = function () {
  return this.piece() === 0;
};

Which meant that every time I created a Square object I need to add context. For example:

new Square(3, 4); // before
new Square(this, 3, 4); // after
new Square(this.context, 3, 4); // sometimes like this

To make the code more readable I created the following method:

Chess.prototype.createSquare = function (row, col) {
  return new Square(this, row, col);
};

So a Square object can sometimes be created with

this.createSquare(3, 4);

Upvotes: 7

Views: 11820

Answers (3)

Armen Michaeli
Armen Michaeli

Reputation: 9140

One may argue that JavaScript does not have "nested" classes as such -- there is no way for a class to use parent class scope, for instance, nor would it be meaningful for anything but accessing parent class properties (static elements).

A class in JavaScript is just an object like any other, so you may as well define one and refer to it with a property on another class:

class Chess {
}

Chess.Square = class {
};

(yes, a name for a class is optional -- above, the Square property on Chess refers to a class without name; not necessarily great for debugging and introspection, but just illustrating a point here)

Having the above, you can do things like:

new Chess.Square();

And generally everything else you do with a class or objects of a class -- new simply expects a function that will act as a constructor, and class declarations actually "decay" into constructor functions when program is run -- values of all of the following expressions are true:

  • Chess instanceof Function
  • typeof Chess == "function"
  • Chess.Square instanceof Function
  • typeof Chess.Square == "function"
  • Chess.prototype.constructor == Chess
  • Chess.Square.prototype.constructor == Chess.Square

There is some extra metadata associated with every class by ECMAScript -- compared to actually using a function as constructor (the older, ECMAScript 5 notation before class keyword was introduced), and some differences, but it has no bearing on being able to nest constructors to access outer scope:

function Chess() {
    const chess = this;
    function Square() { /// Obviously, this function/constructor/class is only available to expressions and statements in the Chess function/constructor/class (and if explicitly "shared" with other code).
        Chess; /// Refers to the outer constructor/class -- you can do `new Chess()` etc
        this; /// Refers to this Square object being created
        chess; /// Refers to what `chess` declared outside this method, refers to at the time of creating this Square object
    }
}

The above uses what is known as "closures", which kind of require you to have a good grasp on how scoping works in JavaScript. In Java, the Square class as specified above, would be known as a nested instance class, as opposed to a nested static class. The first example at the very top of the answer, using class keyword, does specify a form of the latter, though (a "static" class).

Here is yet another way to define an "instance" class Square:

class Chess {
    constructor() {
        this.Square = class {
            constructor() {
                /// Be vigilant about `this` though -- in every function (except arrow functions) `this` is re-bound, meaning `this` in this constructor refers to an instance of the anonymous class, not to an instance of the `Chess` class; if you want to access the latter instance, you must have a reference to it saved for this constructor to access, e.g. with the `const chess = this;` statement in `Chess` constructor somewhere
            }
        }; /// Every `Chess` object gets a _distinct_ anonymous class referred to with a property named `Square`; is a class for every object expensive? is it needed? is it useful?
    }
}

The thing with JavaScript is that it, for better or worse, makes it possible to implement OOP in a bit more than one way, certainly more so than some of the other "OOP languages" allow, and "nested classes" can mean different things, to different people, and in different programs. This is known as "having a lot of rope to hang yourself with" -- a lot of facility that may either help you implement your intended model elegantly, and/or make your code hard to read (especially to people who don't know as much JavaScript as you) and debug. It's a trade-off one needs to address before going all in with "nested classes".

Upvotes: 11

user2800464
user2800464

Reputation: 111

The following piece of code works perfectly well:

class Chess  {
  constructor() {
    this.board = {}
    for (var i=0; i<8; i++) {
          for (var j=0; j<8; j++) {
               this.board[i,j] = new Square(i,j)
               }
          }

  };

};

class Square {
    constructor(row, col) {
      this.row = row;
      this.col = col;
      this.color = "black"
      if ((row+col)%2 == 1) this.color = "white"
  };
};

The two separate classes are interlinked within the Chess (main?) class with direct call to the Square class, so that at Chess instantiation time, the board squares are also defined.

Upvotes: 0

likle
likle

Reputation: 1797

Currently, there are no nested classes. What you could do is to have to two separate classes, Chess and ChessSquare - and have a reference to the chess passed in the constructor of the ChessSquare and keep that stored as a property. This way you won't have to pass it in the methods of the ChessSquare:

  class ChessSquare = {
    constructor(chess, row, col) {
      this.chess = chess;
      this.row = row;
      this.col = col;
    }

    empty() {
      // "this.chess" references the chess, and "this" references the square.
    }
  };

You probably want to create all instances of ChessSquare within the Chess class itself.

Upvotes: 4

Related Questions