Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
like image 212
David Avatar asked Nov 23 '18 14:11

David


2 Answers

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 parent class properties (static declarations), but 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)

Then you can do things like:

new Chess.Square();

And generally everything else you do with a class or objects of a class.

What JavaScript can let you do is to have nested constructors. Even with above notation, a JavaScript class is essentially its constructor -- Chess.prototype.constructor === Chess. Put another way, classes "decay" into constructor functions at runtime. Well, there is some extra metadata associated with them by ECMAScript and some differences, but the following is an example of nesting 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 is where you also need to know how scoping works in JavaScript. In Java, the Chess class as specified above is known as a nested instance class, as opposed to a nested static class. The first example using class keyword does specify a form of the latter, though (a static class).

The thing with JavaScript is that it's actually more flexible than what the traditional OOP model describes, and "nested classes" can mean different things, to different people, and in different programs. But the language evidently allows you do a lot of things which may help you implement your intended model 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.

like image 92
amn Avatar answered Sep 22 '22 03:09

amn


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.

like image 28
likle Avatar answered Sep 23 '22 03:09

likle