[SOLVED] Trying to understand an object composition pattern which features a factory and a function based mixin technique

Issue

I’m trying to understand behavior of function based composition in JavaScript.

const Animal = (name) => {
  let properties = { name };
  return ({
    get name() { return properties.name },
    set name(newName) { properties.name = newName },
    breathe: function() {console.log(`${this.name} breathes!`); }
  })
}

const aquaticKind = (animal) => ({
  swim: () => console.log(`${animal.name} swims`)
})

const walkingKind = (animal, noOfLegs) => {
  const properties = { noOfLegs }
  return ({
    get noOfLegs() { return properties.noOfLegs },
    set noOfLegs(n) { properties.noOfLegs = n; },
    walk: () => console.log(`${animal.name} walks with ${properties.noOfLegs} legs`)
  })
}

const egglayingKind = (animal) => ({
  layEgg: () => console.log(`${animal.name} laid an egg`)
})

const Crocodile = (name) => {
  const info = Animal(name);
  return Object.assign(info,
                       walkingKind(info, 4),
                       aquaticKind(info),
                       egglayingKind(info)
                      );
}
const snooty = Crocodile('snooty');
snooty.breathe();
snooty.swim();
snooty.walk();
snooty.name = "coolie";
snooty.noOfLegs = 23 // I expected this to get update to 23
snooty.swim();
snooty.walk();
snooty.layEgg();

If you run the code above, you will see that noOfLegs never get updated, while name get updated. I can’t seem to wrap my head around this. How do we make noOfLegs get updated too?

Solution

MDN Documentation for object.assign shows you how to copy "accessors"

Here’s your code that works as expected – the completeAssign function is based entirely on the code in that link

const completeAssign = (target, ...sources) => {
    sources.forEach(source => {
        const descriptors = Object.keys(source).reduce((descriptors, key) => {
            descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
            return descriptors;
        }, {});
        Object.getOwnPropertySymbols(source).forEach(sym => {
            const descriptor = Object.getOwnPropertyDescriptor(source, sym);
            if (descriptor.enumerable) {
                descriptors[sym] = descriptor;
            }
        });
        Object.defineProperties(target, descriptors);
    });
    return target;
};

const Animal = (name) => {
    const properties = { name };
    return ({
        get name() { return properties.name },
        set name(newName) { properties.name = newName },
        breathe () { console.log(`${this.name} breathes!`); }
    })
}

const aquaticKind = (animal) => ({ 
    swim: () => console.log(`${animal.name} swims`) 
});

const walkingKind = (animal, noOfLegs) => {
    const properties = { noOfLegs };
    return ({
        get noOfLegs() { return properties.noOfLegs },
        set noOfLegs(n) { properties.noOfLegs = n; },
        walk: () => console.log(`${animal.name} walks with ${properties.noOfLegs} legs`)
    })
}

const egglayingKind = (animal) => ({
    layEgg: () => console.log(`${animal.name} laid an egg`)
})

const Crocodile = (name) => {
    const info = Animal(name);
    return completeAssign(info,
        walkingKind(info, 4),
        aquaticKind(info),
        egglayingKind(info)
    );
}
const snooty = Crocodile('snooty');
snooty.breathe();
snooty.swim();
snooty.walk();
snooty.name = "coolie";
snooty.noOfLegs = 23;
snooty.swim();
snooty.walk();
snooty.layEgg();

Answered By – Bravo

Answer Checked By – Dawn Plyler (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *