javascriptfunctionoopmixinsobject-composition

Passing functions from class to class ... How to maintain utility functionality and mixing it into objects?


I want to inherit all the methods from source classes into my main target class/function. I kind of did something, but I wonder if there are better or gentler ways to do this.
The idea is that I can keep good readability and separate methods in groups (files) so I know what belongs where.
P.S. Sorry for my bad english

Here's how I did it:

    function Main(){
      const self = this
      self.name = 'Main' 
      self.speak = () => {
        console.log(`called in class Main by class ${this.name}`)
      }
    }

    class A{
      //fake variables for IDE autofill
      //no constructor needed
      speakA(){
        console.log(`called in class A by class ${this.name}`)
      }
    }
    class B{
      speakB(){
        console.log(`called in class B by class ${this.name}`)
      }
    }
    class C{
      speakC(){
        console.log(`called in class C by class ${this.name}`)
      }
    }


    ;(function assignOFunctionsToObject(target, ...sources){
        sources.forEach(source => {
            Object.getOwnPropertyNames(source.prototype).forEach(name => {
                if(typeof source.prototype[name] === "function") {
                  target.prototype[name] = source.prototype[name]
                }
            })
        })
    })(Main,
        A, B, C)
        
    let main = new Main()
    main.speak()
    main.speakA()
    main.speakB()
    main.speakC()


Solution

  • Implementing utility functions within and across different modules one could at least choose 3 different approaches ...

    1. Write a function based mixin which is a single function with (this aware) methods bound to the function's this context.

      • apply the imported mixin to whichever object is in need of the mixin's methods.
    2. Write an object based mixin which is a single object with (this aware) methods.

      • assign the imported mixin to whichever object is in need of the mixin's methods.
    3. Write and export (this aware) functions.

      • make the imported functions part of an ad-hoc created object and assign the latter to whichever object is in need of the just created mixin's methods.

    // module ... function_based_utility_methods_mixin_A.js
    //
    function speakA() {
      console.log(`called as 'speakA' by instance of type '${ this.type }'`);
    }
    function speakAA() {
      console.log(`called as 'speakAA' by instance of type '${ this.type }'`);
    }
    
    /* export default */function withUtilityMethods_A() {
      this.speakA = speakA;
      this.speakAA = speakAA;
    }
    
    
    // module ... object_based_utility_methods_mixin_B.js
    //
    function speakB() {
      console.log(`called as 'speakB' by instance of type '${ this.type }'`);
    }
    function speakBB() {
      console.log(`called as 'speakBB' by instance of type '${ this.type }'`);
    }
    
    /* export default */const utilityMethods_B = {
      speakB,
      speakBB,
    }
    
    
    // module ... utility_methods_C.js
    //
    /* export */function speakC() {
      console.log(`called as 'speakC' by instance of type '${ this.type }'`);
    }
    /* export */function speakCC() {
      console.log(`called as 'speakCC' by instance of type '${ this.type }'`);
    }
    
    
    // module ... main.js
    
    // import withUtilityMethods_A from 'function_based_utility_methods_mixin_A.js';
    // import utilityMethods_B from 'object_based_utility_methods_mixin_B.js';
    // import { speakC, speakCC } from 'utility_methods_C.js';
    
    function Main () {
      this.type = 'main';
      this.speak = () => {
        console.log(`called as 'speak' by instance of type '${ this.type }'`);
      };
    }
    
    // option 1 ... 
    // - APPLY a function based mixin to the
    //   `Main` constructor function's prototype.
    withUtilityMethods_A.call(Main.prototype);
    
    // option 2 ... 
    // - ASSIGN an object based mixin to the
    //   `Main` constructor function's prototype.
    Object.assign(Main.prototype, utilityMethods_B);
    
    // option 3 ... 
    // - ASSIGN an ad-hoc created object based mixin
    //   to the `Main` constructor function's prototype.
    Object.assign(Main.prototype, { speakC, speakCC });
    
    const main = new Main();
    main.speak();
    
    main.speakA();
    main.speakAA();
    
    main.speakB();
    main.speakBB();
    
    main.speakC();
    main.speakCC();
    
    console.log({
      mainPrototype: Main.prototype
    });
    .as-console-wrapper { min-height: 100%!important; top: 0; }