multiple file classes

The issue

Working with huge classes, it's hard to sort the methods and find what you need (class of thousands of line). Which method is useful ? Where to look at ? public, private and protected keywords, CTRL+F or plugins like minimap helps, but in long files it's still hard.

Soluce

Allow to split the definition of a class into multiple files. Which will allows the developer to separate the code in a logicial way.

For example : put the protected methods in one file and public methods in an other file.

Actual way to solve the issue

File Foo.js

import Root from './parts/Root.js';
import Part1 from './parts/Part1.js';

/**
 * class Foo extends recursiveInterface(array)
 *
 * Is equivalent to :
 *
 * class Foo extends(
 *        class A extends (
 *               class B extends (
 *                        class C extends (class ROOT)
 *                )
 *        )
 *   )
 */
function recursiveInterface(arr, i = 0) {
  if (!arr[i]) {
    return class { };
  }

  return arr[i](recursiveInterface(arr, i + 1));
}

export default class Foo extends recursiveInterface([
  Part1,

  // Keep Root at the end, this is the root class for the dependencies
  Root,
]) { }

File Root.js or File Part1.js

export default function(superclass) {
  return class Foo extends superclass {

  };
}

To use it :


import Foo from 'Foo.js';

const obj = new Foo();

// Obj here contains all methods
// and attributes defined in all the files parts

Possible solution to integrate

We could add a new keyword called "complete" that will automatically merge the class definitions, like :

File Foo.js

import Part1 from './Part1.js';
import Part2 from './Part2.js';

export default class Foo complete Part1, Part2 {
    // ...
}

File Part1.js / Part2.js / ...

export default class Foo {
    // ...
}

Thank you for reading me :)

What is it about these classes that means you can't split them up, either into separate smaller classes, or with a deeper inheritance hierarchy?

Example 1:

One of my class is an holder for constants.

I have split it into several classes already, sorting the Constant by categories. The thing is I want to be only one way to access the constant in my app. So I could everywhere do :

Constant.EARTH_RADIUS

Because multiple inheritance does not exists in Javascript, the syntax to merge the multiple classes is tricky.

Example 2:

I have a class that is representing a Collection in Mongodb which contains CRUD functions (addUser, removeUser, deleteUser, getUser ...). Because some of theses methods are complex, it's necessary to create protected methods that will be called to perform specific data manipulation or actions.

example :

protected checkInfo(data) { ... }

public addUser(data) {
    this.checkInfos(data);
  
    ...
}

What I've experienced is that when you multiply the number of way to interact with your collection (getUserByCompany, connect, disconnect ...) the class grows and it's difficult to find quickly what are the methods that are interesting (you can ctrl+f on the keyword public thought).

It could be nice in that example to put public methods into a User.js file and the protected functions into a UserProtected.js file.

Post scriptum

There are multiple posts on stack overflow talking about multiple inheritance and merging classes altogether

For constants, why a class instead of an object literal, which you could trivially merge with object spread syntax?

For example 2, that looks like typescript syntax - all methods in JS are only public or private, there’s no “protected” - but it seems like you could have “User extends UserProtected” and put them in separate files already?

1 Like

First of all thank you for your time :)

Second, you guessed it right, I'm using Typescript syntax through Babel along others plugins (@babel/plugin-proposal-decorators, @babel/plugin-proposal-optional-chaining ...).

Then, concerning the Constant class, I am using static constants, but also dynamic constants that are set at the start of my app from a configuration file.

Example :

let instance = null;

class Constant {
   protected foo = null;

   static get FOO() {
       return this.getInstance().foo || 'Foo';
   }

   public static getInstance() {
       return instance || new Constant();
   }
}

Configuration file loader:

Constant.getInstance().foo = 'bar';

About the example 2, yes I could indeed extends the UserProtected class, but now imagine that I have 3, or or 5 files, because I could split protected methods into separated files for clarity. I will have the problem described above.

I'm not saying that there is no way to achieve what I want to do, I'm trying to demonstrate that it could be easier to achieve it if they were a standardized syntax available :)

I'd find it useful to have a way to spread long class definitions over multiple files.

Some classes are just big. For example in Construct 3 (editor.construct.net), there is a tree view in the Project Bar which contains items representing all the assets in an entire game development projects. Different kinds of items work differently with their own restrictions, interactions, and menus. There is a ProjectBar class, maybe a dozen types of items, and each type of item has maybe a dozen different kinds of methods associated with it.

If you have a class with potentially over 100 methods, having them all in a single class declaration makes for a super long file and loads of scrolling. Of course this can theoretically be split up in to different classes, but then ideally there would be some kind of proper interface between them rather than different classes essentially just accessing private properties all over the place. It is actually quite a considerable amount of work to do this, or you have to more or less give up on encapsulation.

Basically some classes really do just end up big, not because they're a god object, but because they have a lot of logic dedicated to their specific task. In practice our pragmatic solution is to just resort back to the prototype style, e.g.:

// BigClass.js
class BigClass {
    // ... lots of methods ...
}
// BigClass_PartA.js
BigClass.prototype.SomePartAMethod = function () {
    // ...
};

BigClass.prototype.AnotherPartAMethod = function () {
    // ...
};
// BigClass_PartB.js
BigClass.prototype.SomePartBMethod = function () {
    // ...
};

BigClass.prototype.AnotherPartBMethod = function () {
    // ...
};

It would be nice if we could do something like this instead, to add extra methods to an existing class:

// BigClass_PartA.js
class continue BigClass {
    SomePartAMethod() { ... }
    AnotherPartAMethod() { ... }
}
1 Like

My knee-jerk reaction is that this is basically this stage 1 proposal, just using complete instead of with for the main class and class instead of mixin for each part.