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 where OOP is done without encapsulation, such problems are solved more easily and occur less frequently. If you want to know how it happens, try writing code in Clojure or Elixir.

Let's imagine we're make a site that has an authentication mechanism. After it's executed, the user is presented with a greeting, which is put together differently depending on the age of the user. If the user is under 18, then one thing is written, for everyone else, it'll be something else.

In this case, a straightforward implementation, using if, would be the best solution to the problem. But in this lesson, we're looking at the use of polymorphism within the class model, so we'll go a different way. The task itself is specially 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. What we'd get in the end is 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
// The correct class for the user is selected at the moment when http-request -. starts processing
= user.getMessage()

This solution, although it works, takes us down the wrong path. Today we have under and over 18, and then there'll be a separate behavior for those over 65. Things 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
  • ...

The pattern that books like to give examples of is separating vehicles by type: sailing, flying, and riding. And then, suddenly, 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 themselves is an entity drawn from our domain. The subject domain and text output on the screen are completely different things. The second relates to application logic, but not to business logic. If you don't think about it, then you'll come to a point when everything that happens on the site will be within the user because it's all connected to the user themselves in one way or another. And we get a god object.

The right solution in such situations is based on composition, an approach based on the interaction of objects rather than on the hierarchy of classes. Let's start at the beginning. There are two situations in our task: users under 18 and users over 18. These are two different behaviors that will be described by two different classes. Let's call them: GreetingForAbove18 and GreetingForUnder18. We implement the getMessage method in each of the classes. In each class, this method will return exactly the greeting required for that category of user.

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

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

How will the user interact with the objects from these classes? There are two options, either we pass it to the constructor or to the getMessage(user) method itself. What's the right thing to do? Always try to understand whether we're dealing with data abstraction or not. Everything is clear with the users themselves. A user is a data abstraction, it has uniqueness (all users are different) and a lifetime. But the message output is a stateless operation. The very 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)

The issue of selecting and creating an appropriate object was left out of the picture. There's a factory responsible for this, which is called somewhere before the output from the template is generated.

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's still only responsible for the logic of the application core. Even if new message output conditions are added and our two classes turn into 10 classes (because of 10 output options that depend on different parameters), 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, a large number of small classes will appear in the code. The number of these classes is equal to the number of possible behaviors. Most of the objects of these classes have no state and are needed to organize polymorphic code.


Recommended materials

  1. Interface segregation principle

Hexlet Experts

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

The Hexlet support team or other students will answer you.

About Hexlet learning process

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:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs

From a novice to a developer. Get a job or your money back!

Frontend Developer icon
Profession
beginner
Development of front-end components for web applications
start anytime 10 months

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.