I'm building the concept of a family for a product, with members being of different types ( accountHolder
, payingCustomer
, student
, and so on). Originally I built these as sub-classes of FamilyMember
, but I ended up with some repeated code and eventually bumped into a significant problem: a student
of our platform can also be the sole payingCustomer
and accountHolder
.
Given how object composition is widely touted as a good idea in JS, I decided to go that route. However, the methods of a particular object type (eg accountHolder
) can't access properties of the instantiated object, if the property belong to another object type (eg student
).
To make this more objective I've decided to replicate the behaviour using the following code:
const person = (props) => {
let state = {
name: props.name,
}
state.isOfAge = () => {
// state.isAdult is always undefined because
// isAdult doesn't exist in this object
return state.isAdult === true
}
return state
}
const adult = (props) => {
return {
isAdult: true,
}
}
const factory = (props) => {
return Object.assign({}, person(props), adult(props))
}
const john = factory({
name: 'John',
})
console.clear()
console.log(john) // { isAdult: true, name: "John", isOfAge... }
console.log(john.isOfAge()) // false
I was expecting john
's method isOfAge
to be able to access the property isAdult
, since it's in the object. However, conceptually I understand why it doesn't work: isOfAge
is a method of state
, not the resulting adult
instance.
If I were using classes or even a traditional prototype/constructor mechanism I knew how to make it work (eg attaching to prototype
). With object composition I've no idea how to get there, probably due to lacking experience with FP.
Thanks for the help!
You can use this
instead of state
inside isOfAge
. That way, the this
will be deduces when the method isOfAge
gets called, it will be bound to whatever object it is called on. Though, you'll have to use a regular function instead of an arrow one for that to work (arrow functions don't have a this
):
const person = (props) => { let state = { name: props.name, } state.isOfAge = function() { // use a regular function return this.isAdult === true // use this here instead of state } return state } const adult = (props) => { return { isAdult: true, } } const factory = (props) => { return Object.assign({}, person(props), adult(props)) } const john = factory({ name: 'John', }) console.log(john); console.log(john.isOfAge()); // returns 'true' because 'this' inside 'isOfAge' will be 'john'
All objects made from other objects and language primitives are composite objects.
The act of creating a composite object is known as composition.
...Concatenation composes objects by extending an existing object with new properties, eg,
Object.assign(destination, a, b), {...a, ...b}
.
...
The Hidden Treasures of Object Composition
So from your pattern and use of a factory function it looks like concatenation? The demo below is a concatenation composition. Note the parenthesis wrapped around the brackets of payment
:
const payment = (status) => ({...})
this allows payment
to be returned as an object instead of a function. If you have data that's a little more flexible, you'll need less methods. name: string
and age: number
are the properties I used considering it practical or in your case name: string
and adult: boolean
.
const payment = (status) => ({ adult: () => status.age > 17 ? true : false, account: () => status.adult() ? 'holder' : 'student' }); const member = (name, age) => { let status = { name, age }; return Object.assign(status, payment(status)); }; const soze = member('Kaiser Soze', 57); console.log(soze); console.log(soze.adult()); console.log(soze.account()); const jr = member('Kaiser Soze Jr.', 13); console.log(jr); console.log(jr.adult()); console.log(jr.account());
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.