Register to get access to free programming courses with interactive exercises

Object composition JS: Polymorphism

This lesson describes a system that helps you organize your class-based code properly.

In languages with OOP without encapsulation, such problems are solved easier and occur less frequently. You should try writing code in Clojure or Elixir and find out how it happens.

Let's imagine we're making a site that has an authentication mechanism. When we execute it, we see a greeting, which is put together differently depending on the user's age. For users under 18, then one thing is written. For everyone else, it will be something else.

In this case, a straightforward implementation would be the best solution to the problem, but in this lesson, we look at polymorphism within the class model and go a different way. Our task is simplified, so we don't waste time analyzing it.

The first impulse of many developers would be to introduce two classes: Under18 and Above18. Next, it would be to add a getMessage() method in each class.

In the end, we get subtype polymorphism:

class Under18 extends User {
  getMessage() {
    // Hi Sam
    return `Hi ${this.firstName}`;
  }
}

class Above18 extends User {
  getMessage() {
    // Hello Mr Smith
    // Hello Mrs Tomson
    return `Hello ${this.appeal} ${this.firstName}`;
  }
}
// Somewhere in the template
// We select the correct class for the user when the HTTP request starts processing
= user.getMessage()

This solution, although it works, takes us down the wrong path. We have under and over 18, and then there'll be different behavior for those over 65. The situation will get even worse when, in addition to these divisions, we also divide by gender.

In this case, we get a large number of combinations, for each of which we have to create a separate user class:

  • Girls over 18
  • Girls under 18
  • Boys over 18
  • Boys under 18
  • ...

Authors of books about patterns explain it by separating vehicles by type (sailing, flying, and riding). It turns out that some people both sail and drive at the same time.

Now let's try to answer this question: Why shouldn't we use subtypes to solve this problem?

The user is an entity drawn from our domain. Subject domains and text outputs on the screen are different things. Text outputs relate to application logic but not to business logic.

If you don't think about it, you'll come to a point when everything happening on the site will be within the user because it's all connected to the user in one way or another, so we get a god object.

The right solution in such situations focuses on composition, an approach based on the interaction of objects rather than on the hierarchy of classes.

There are two cases in our task:

  • Users under 18
  • Users over 18

Also, we have two different behaviors described by two classes. Let us call them:

  • GreetingForAbove18
  • GreetingForUnder18

We implement the getMessage method in each of the classes. This method will return the greeting required for that user's category in each class:

class GreetingForUnder18 {
  getMessage(user) {
    return `Hi ${user.firstName}`;
  }
}

class GreetingForAbove18 {
  getMessage(user) {
    return `Hello ${user.appeal} ${user.firstName}`;
  }
}

There are two options for how the user interacts with the objects from these classes:

  • To pass it to the constructor
  • To the getMessage(user) method itself

What is the right thing to do? You should try to understand whether you are dealing with data abstraction or not:

With users, it is clear. Users have uniqueness and lifetime, so they are data abstractions Message outputs are stateless operations. The existence of a class and an object for it is due to the desire for subtype polymorphism and nothing more

So, in this example, it's better to pass the user through a method:

// Somewhere in the template
= greeting.getMessage(user)

Selecting and creating an appropriate object was left out of the picture.

We have a factory responsible for this, so we call it somewhere before the generation of output from the template:

const buildGreetingObject = (user) => {
  if (user.getAge() < 18) {
    return new GreetingForUnder18();
  } else {
    return new GreetingForAbove18();
  }
}

The main thing in this scheme is that the user remains a user. It is still only responsible for the logic of the application core. Imagine we add new message output conditions, and our two classes turn into ten (because of ten output options that depend on different parameters). Even in that case, it won't affect the user in any way.

More importantly, when new tasks appear that aren't related to the output of the message, the user will still be unaffected. For example, we'll want to send emails to different users after registration. The number of classes created will correspond to the number of types of emails. The principle will remain the same. The factory, choosing the right type at the beginning of the registration process, and the polymorphic behavior when sending an email.

Eagle-eyed readers will notice that the result is suspiciously similar to Strategy. Oddly enough, this is indeed Strategy.

As a result, many small classes will appear in the code. The number of these classes is equal to the number of possible behaviors. The majority of objects of these classes have no state and are needed to organize polymorphic code.


Recommended materials

  1. Interface segregation principle

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.