1

Hello everyone)) I am working on the react application, where mobx is being used for the state management. Some time ago I have added such class:

class EventLogStore {
    private _eventLogRecord: IEventLogRecord[];
    
    get eventLogRecords(): IEventLogRecord {
        return this._eventLogRecords;
    }    

    add(eventLogRecord, options): void;
    removeEventLogRecord(eventLogRecord): void;
}

I like that class, as it is easy to understand how to use it. However, later other developers has started to add some functionality that need access _eventLogRecords, and class start to grow:

class EventLogStore {
    private _eventLogRecord: IEventLogRecord[];
   
    get eventLogRecords(): IEventLogRecord {
        return this._eventLogRecords;
    }    

    getSomeCustomFilter1(params): IEventLogRecords() {

    }

    getMsPassedFromEventAdded(): number {
        return this.getMsPassedFromSomeEventAdded(DEFAULT_EVENT);
    }

    getMsPassedFromSomeEventAdded(event): number {

    }

    getSomeCustomFilter1(params): IEventLogRecords() {

    }
    
    add(eventLogRecord, options): void;
    removeEventLogRecord(eventLogRecord): void;
}

From one point of view it is really seems like boilerplate job, to write functions, like

getMsPassedFromEventAdded(eventLogStore: EventLogStore): IEventLogRecords[] {
    return eventLogStore.eventLogRecords ...;
}, 

as we can locate that function under the EventLogStore itself to reduce amount of parameters to pass,

From another point, I really dislike that previously it was easy to understand the responsibility of the eventLogStore, but now you scroll up/down and still miss the main responsibility of the class, and it continue growing)).

I'd like to suggest to split the functionality, e.g.

class EventLogStore {
    private _eventLogRecords: IEventLogRecords = [];
    private _feature1FiltersStore = new Feature1FiltersStore(this);
    private _feature2FiltersStore = new Feature2FiltersStore(this);

    get feature1FiltersStore(): Feature1FiltersStore;
    get feature2FiltersStore(): Feature2FiltersStore;

    add(eventLogRecord, options): void;
    removeEventLogRecord(eventLogRecord): void;
}

// we have some kind of DI (hooks :D) that can allow to do the next
export function useFeature1Filters() {
    return useEventLogStore().feature1FiltersStore;
}

I like that functionality would be grouped and the eventLogStore would cleaned up and other developers still can pass less parameters, but I'd like to discuss some questions on that solution:

  1. should feature1Filters be a private field of the EventLogStore or it should be out of EventLogStore - I have left it there, because it seems would be easier to navigate over the code, e.g. you open EventLogStore and see it's business feature stores, where it is being used, however thing can become harder if any featureStore would need more dependencies. So, I'd like to extract it out from EventLogStore, e.g.
class EventLogStore {
    private _eventLogRecords: IEventLogRecords = [];

    add(eventLogRecord, options): void;
    removeEventLogRecord(eventLogRecord): void;
}

class Feature1FiltersStore {
    constructor(private eventLogStore: EventLogStore) {

    }
}

useFeature1EventLogStore() {

}

// we have some kind of DI (hooks :D) that can allow to do the next
export function useFeature1Filters() {
    return useEventLogStore().feature1FiltersStore;
}
  1. Does it become much harder to understand then flat functions in the EventLogStore? For me it seems easier, as it is easier to understand the responsibilities.
  2. Any other alternatives?

1 Answer 1

1

You might be writing this in TypeScript, but it is all ECMA Script/JavaScript in the browser. Put all the extra methods that filter log messages in other files that add to the prototype of the EventLogStore class.

For example, EventLogStore.js can be the nice, clean and focused class that you originally wrote. Then in EventLogStore.Filters.js:

EventLogStore.prototype.getSomeCustomFilter1 = function(params) {
    //...
};

EventLogStore.prototype.getMsPassedFromSomeEventAdded = function(event) : number {
    //...
};

You are free to dump all the filters into one file, or group similar methods together in different files. This is a lot like a partial class in C#. The filter methods will still have access to all instance methods and properties via this, so you shouldn't need to modify any of the logic.

Sometimes the bad old days of JavaScript still have some use.

3
  • Going to consider the provided solution and mixins typescriptlang.org/docs/handbook/mixins.html (see "Alternative Pattern" section). Just in case would keep in mind, a variant when all the feature stores are separate classes, then possible to import only required js.
    – Nikita
    May 26, 2021 at 22:17
  • @Nikita: come to think of it, the code in my question is probably what a TypeScript mixin becomes once it is transpiled to JavaScript. May 26, 2021 at 22:33
  • 1
    Yes, seems like editing the prototype, or "partial classes" is the basic idea. With usage info from here stackoverflow.com/questions/5458990/… , I am updated applyMixins from typescriptlang.org/docs/handbook/mixins.html to return a new class declaration, now the export from eventLogStore.ts going to look like export applyMixins(EventLogStore, [Filters1Feature, Filters2Feature]. Also need to check how is it supported by IDE, how is it combined with mobx, and etc. The refactoring would next week, currently were investigations.
    – Nikita
    May 26, 2021 at 22:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.