Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Manav Misra
Manav Misra

Posted on • Edited on

     

Consider Composition

“Favor object composition over class inheritance”

the Gang of Four, “Design Patterns: Elements of Reusable Object-Oriented Software”

Banana

“...the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”

∼ Joe Armstrong, “Coders at Work”

Overview

Rather than focusing on the OOP capabilities of JS, many folks come to appreciate the relative simplicity and ease of usingcomposition overinheritance.

Put simply:

  1. Inheritance focuses on establishing 'is a' relationships. Although this practice somewhat dominates the industry, one of the drawbacks is that we must carefully try to plan and anticipate many use cases for 'things' ahead of time. It's not too difficult to overlook something and then have to struggle to backtrack.
  2. Composition is more about 'has a' relationship - or we focus on what something should be able to do. Essentially, we 'mix in' or compose whatever functionality from wherever we might need it, 'on the fly.' This tends to make us a bit more adaptable, and even the syntax and implementation can be easier to follow.

Thisvideo is a great 'primer' for the concepts expressed here.

Also, as a reference to the 'inheritance version' of the code I go through here:


Composition

Ina previous post, we explored creating newobjects 'on the fly_ usingobject composition. Now we'll take it to the next level.

Function Factories 🏭 to Encapsulate 'Functionality'

Again, in order to build up aStudent orFaculty member, we will want tocompose them from some other objects that represent different pieces of functionality.

constGreeter=(name)=>({greet(){return`👋🏽. My name is,${name}.`;},});constCourseAdder=()=>({addCourse(newCourse){this.currentCourses=[newCourse,...this.currentCourses];},});constRaiseEarner=()=>({giveRaise(raiseAmt){this.salary+=raiseAmt;},});
Enter fullscreen modeExit fullscreen mode

Done! We have 3 'things' that we can compose together to build 'bigger' things. Again, we capitalize to denote that these are 'things' made to be reused or composed (same convention with React components).

Each of these is afunction factory that returns anobject. that 'wraps' 🎁 amethod to implement some specific functionalities.

constGreeter=(name)=>({greet(){return`👋🏽. My name is,${name}.`;
Enter fullscreen modeExit fullscreen mode

Greeter encapsulatesname in aclosure. It means that afterGreeter isinvoked, instead ofname beinggarbage collected,name will stick around as long as it's needed bygreet.

We'll see 👇🏽 thatGreeter will becomposed intoStudent andFaculty. Whenever we create aStudent, for example, thename that we use will be 'enclosed' ingreet. Each timegreet is run 🏃🏽‍♂️ on aStudent, that 'enclosed'name will bereferenced.

Student andFaculty

constStudent=({id,name,age,major,credits,gpa,currentCourses})=>({id,name,age,major,credits,gpa,currentCourses,...Greeter(name),...CourseAdder(),});constFaculty=({id,name,age,tenured,salary})=>({id,name,age,tenured,salary,...Greeter(name),...RaiseEarner(),});
Enter fullscreen modeExit fullscreen mode

BothFaculty andGreeter 'mix in' 👩🏽‍🍳 the desired functionalities. For example:...Greeter(name),. We can say that each of thesehas aGreeter.

When we mix inGreeter, we arebinding thename - there's thatclosure.

RaiseEarner andCourseAdder areinvoked andbound with athis-this.currentCourses andthis.salary.

Next, we'll instantiatemark, an instance ofStudent andrichard, an instance ofFaculty.

constmark=Student({id:1124289,name:"Mark Galloway",age:53,major:"Undeclared",credits:{current:12,cumulative:20,},gpa:{current:3.4,cumulative:3.66,},currentCourses:["Calc I","Chemistry","American History"],});constrichard=Faculty({id:224567,name:"Richard Fleir",age:72,tenured:true,salary:77552,});
Enter fullscreen modeExit fullscreen mode

All we do is pass in their unique properties, ourfunction factories crank them out.

Testing things out:

mark.addCourse("Psych");richard.giveRaise(5000);console.log(mark.greet(),richard.greet());console.log(mark,richard);
Enter fullscreen modeExit fullscreen mode

We can see:

👋🏽. My name is, Mark Galloway. 👋🏽. My name is, Richard Fleir.{id: 1124289,  name:'Mark Galloway',  age: 53,  major:'Undeclared',  credits:{ current: 12, cumulative: 20},  gpa:{ current: 3.4, cumulative: 3.66},  currentCourses:['Psych','Calc I','Chemistry','American History'],  greet:[Function: greet],  addCourse:[Function: addCourse]}{id: 224567,  name:'Richard Fleir',  age: 72,  tenured:true,  salary: 82552,  greet:[Function: greet],  giveRaise:[Function: giveRaise]}
Enter fullscreen modeExit fullscreen mode

The Main Advantage Over Inheritance

I prefer this implementation better, both syntactically and conceptually. I like to think of what things 'do' rather than what they 'are.'

But...here's a 'real' advantage. What if, we have aStudent...that gets hired on to teach also? So...aFacultyStudent - one that can add courses and/or might get a raise.

A possibly unpredicted situation such as this really shows wherecomposition shines ✨.

More Inheritance? 👎🏽

Maybe. But...what is aFacultyStudent...should it extend fromStudent orFaculty? In classical OOP, this is where you might create aninterface - a means to encapsulate functionality that can be implemented by variousclasses. Fine.

More Composition 👍🏽

How about this instead?

constFacultyStudent=({id,name,age,major,credits,gpa,currentCourses,tenured,salary,})=>({id,name,age,major,credits,gpa,currentCourses,tenured,salary,// Composition!...Greeter(name),...CourseAdder(),...RaiseEarner(),});constlawrence=FacultyStudent({id:1124399,name:"Lawrence Pearbaum",age:55,major:"CIS",credits:{current:12,cumulative:0,},gpa:{current:0.0,cumulative:0.0,},currentCourses:["JavaScript I"],tenured:false,salary:48000,});lawrence.addCourse("JavaScript II");lawrence.giveRaise(2000);console.log(lawrence.greet(),lawrence);
Enter fullscreen modeExit fullscreen mode
👋🏽. My name is, Lawrence Pearbaum.{id: 1124399,  name:'Lawrence Pearbaum',  age: 55,  major:'CIS',  credits:{ current: 12, cumulative: 0},  gpa:{ current: 0, cumulative: 0},  currentCourses:['JavaScript II','JavaScript I'],  tenured:false,  salary: 50000,  greet:[Function: greet],  addCourse:[Function: addCourse],  giveRaise:[Function: giveRaise]}
Enter fullscreen modeExit fullscreen mode

Essentially, we can just keep applying the same concepts over and over andcompose 'on the fly'... "all night long!" 🎵


All together now, and IK that was a lot! Thanks for sticking around.


Updated 1: If this topic is of interest to you, I refer you to this excellent article that's a few years old but still presents some of the same information in a slightly different way:

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I'm a JS Subject Matter Expert (SME) that has spent the past few years spearheading curricula and teaching initiatives at colleges and bootcamps, in person and virtually.
  • Location
    62236
  • Education
    BS - Mech. Eng. - Missouri S&T
  • Joined

More fromManav Misra

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp