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\$

    1 Answer 1

    1
    \$\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\$

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.