0
\$\begingroup\$

I've written some code which features Eric Elliot's javascript factory function mixin composition (https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1), but since there's no object field name associated with the class it doesn't seem like most other forms of composition I see. What would the draw backs be compared the style of composition where each class you compose with is given a field name (I'm calling this named composition)?

let pipe = (...fns) =>x => fns.reduce((acc,fn)=> {
   return fn(acc)
 },x)
let createCanvas= ()=>o=> ({
   ...o,
   makeCanvas(width,height) {
     this.can = document.createElement("canvas")
     this.can.width = width
     this.can.height = height
     this.ctx = this.can.getContext("2d")
   },
   clearCanvas() {
     this.ctx.clearRect(0,0,this.can.width,this.can.height)
   },
   returnCanvas() {
   return this.can
 }
 })
  let drawingLines = () => o => ({
    ...o,
    line(x, y, xx, yy) {
      this.ctx.beginPath();
      this.ctx.moveTo(x, y);
      this.ctx.lineTo(xx, yy);
      this.ctx.stroke();
    }
  });
  let mouseTracker = () => o => ({
    ...o,
    trackCanvas() {
      this.track(this.can);
    },
    track(ele) {
      ele.addEventListener("mousemove", e => {
        this.bb = ele.getBoundingClientRect();
        this.mx = e.clientX - this.bb.left;
        this.my = e.clientY - this.bb.top;
      });
    }
  });
  let rectMethods = () => ({
    makeRect(x, y, w, h) {
      this.ctx.strokeRect(x, y,  w, h);
    },
  });
  let rectMixin = () => o => ({
    ...o,
    ...rectMethods()
  });

  // apparently width gets set automatically, which is pretty nice
  let height = 150;
  let width= 150;
  let firstCanvas = pipe(
    createCanvas(),
    drawingLines(),
    mouseTracker(),
    rectMixin()
  )({});

  firstCanvas.makeCanvas(width, height);
  firstCanvas.trackCanvas();

I'm very interested in avoiding the verbosity of named composition, especially in cases where an existing class has many methods that I would have to rewrite the methods for.

let usefulEventFunctions=()=>({
//...imagine functions named one-five for countings sake...
  evOne() {
},
evTwo() {
},
})
let usefulMathFunctions =()=>({
//...imagine another set of named functions one-five also...
mthOne(){
},
mthTwo(){
}
})
// here's the class composed of the others
let myObject = ()=>({
  math:usefulMathFunctions(),
  events:usefulEventFunctions(),
  // below would be the 10 signatures 
evOne() {
   this.events.evOne()
},...
mthTen() {
this.math.mthTen()
}
})

Using the mixin composition I can see how we would avoid this in a language like javascript, but looking over https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance, and Object Composition, it appears other languages only feature named composition, so how is one supposed to avoid all the duplicated methods in choosing composition over inheritance?

\$\endgroup\$
0
\$\begingroup\$

Question

*"...so how is one supposed to avoid all the duplicated methods in choosing composition over inheritance?"

I assume you are talking about JavaScript.

It might be a minor point but In JS we use functions, methods are reserved for the engine where the name comes from the language it is written in V8 for example is written in C++

Duplicating functions

There is no need to duplicate functions no matter how you create your object in JavaScript.

I just can not workout why you would want to do

From your bottom snippet

mthTen() {                // WHY??????
    this.math.mthTen()
}

Examining your example via rewrites

Defining the objects

const Events = () => ({evOne() {}, evTwo() {}});
const Maths = () => ({mthOne() {}, mthTwo() {}});

BTW Use literal if you don't need closure

BTW you would only define objects (using this pattern) via functions if you were closing over some encapsulated data. They are better defined as literals in this case, avoiding the creation of unused closure for each object

const events = {evOne() {}, evTwo() {}};
const maths = {mthOne() {}, mthTwo() {}};

At the object level

If you want the functions to be accessible at the object level you may build it as follow

const createFoo = (math, events) => ({...math, ...events});
const obj = createFoo(math, events);

// Calling
obj.evOne();
obj.mthOne();

At the object property level

To create them as named properties

const createFoo = (math, events) => ({math:{...math}, events:{...events}});
const obj = createFoo(math, events);

// Calling
obj.events.evOne();
obj.math.mthOne();

As static functions

In many cases you would use them as static functions.

// static instance reference. There is only one math and events for many Foos
const Foo = (math, events) => ({math, events});
const obj = Foo(math, events);

// Calling
obj.events.evOne();
obj.math.mthOne();

Or // Static instance. There is only one instance of math and events referenced by the constructor Foo const Foo = Object.assign(() => ({}), {math, events}); const obj = Foo();

// Note access via the instantiation function  Foo
Foo.events.evOne();
Foo.math.mthOne();
obj.event.evOne(); // will throw as it does not exist.

Or

// Static instance functions. There is only one instance of math and events 
// referenced via functions of the constructor  Foo
const Foo = Object.assign(() => ({}), {...math, ...events});
const obj = Foo();

// Note access via the instantiation function  Foo
Foo.evOne();
Foo.mthOne();
obj.evOne(); // will throw as it does not exist.

Note there is no formal way to define static functions its only static by how it is used and instanced (Via the class syntax static is just syntactical sugar for (assign to constructor, rather than prototype))

If you must duplicate

Warning Don't do this at home, it is only as an example. Doing this in any code can result in severe confusion and eventual application death.

If you really wanted to have a second copy of each function. eg obj.evOne and obj.event.evOne You can automate the function creation, binding the copied function references to the object you want as this

const events = {evOne() {}, evTwo() {}};
const logger = {
    logA() { console.log("A" + this.count++)},
    logB() { console.log("B" + this.count++)}, 
    count: 0
};

const addFunctions = (obj, ...instances) => {
    for (const instance of instances) {
        for (const [name, func] of Object.entries(instance)) {
            func instanceof Function  && (obj[name] = func.bind(instance));
        }
    }
    return obj;
}

const createFoo = (a, b) => addFunctions({logger: a, events: b}, a, b);
const foo = createFoo({...logger}, {...events});

foo.logger.logA();  // should be A0
foo.logA();         // should be A1
foo.logger.logB();  // should be B2
foo.logB();         // should be B3

Question

"What would the draw backs be compared the style of composition where each class you compose with is given a field name (I'm calling this named composition)?"

This question is way too subjective to answer in any death.

There are drawbacks to every pattern, how the drawbacks effect the code quality is very dependent on the coders skill and level of experience.

If you are comfortable with a pattern and have the experience your proficiency outweighs any pattern's drawbacks.

The biggest drawback to any pattern is how experience a coder is in using it.

To be a well rounded coder you must gain proficiency in all the patterns.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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