Dealing with Telescopic Constructors: Anti-Pattern

We all have been through situations, where we had to create classes with multiple constructors or constructor with a lot of dependencies (parameters). These classes tend to get bloated quickly with the over used constructor methods and too many parameters and starts messing with the default properties values. Whenever you find yourself into this situation you; my friend; have been trapped by a notorious anti-pattern called Telescopic Constructors or, Telescopic Initializers. The initial intention of this pattern was to simplify the process of working with classes with a lot of initializer parameters.

In this blogpost, I am going to discuss how we get trapped by the telescopic constructor. Let us take a look at the following example code snippet:

enum OrderSize {
case small
case medium
case large
}
enum SpiceRange {
case spicy
case hot
case extraHot
}
class Biriyani {
let count: Int
let size: OrderSize
let spiceRange: SpiceRange
init (count: Int, size: OrderSize, spiceRange: SpiceRange) {
self.count = count
self.size = size
self.spiceRange = spiceRange
}
}
class Restaurant {
var biriyani = Biriyani(count: 1, size: .medium, spiceRange: .extraHot)
...
}

We all love biriaynis, right? If not, what are you doing with your life man! 😱 Go have some biriyani first!

Jokes apart, here we have a restaurant that sells biriyanis. The Biriyani class defines an initializer with three parameters. This requires calling classes to know what the default values will be for the parameters and set them every time:

var biriyani = Biriyani(count: 1, size: .medium, spiceRange: .extraHot)

Though we are forcing the Restaurant class to set all parameters of the Biriyani class every time, most customers won’t be giving a lot of customized orders, making it possible to use some default values for the class. For example, most customers will only order a single medium sized dish with only the spice range varied.

So we try to improve our Biriyani class’ initialization usability by using telescoping constructors. This pattern tells us to create convenience initializers that will provide default values for our orders.

class Biriyani {
let count: Int
let size: OrderSize
let spiceRange: SpiceRange
init (count: Int, size: OrderSize, spiceRange: SpiceRange) {
self.count = count
self.size = size
self.spiceRange = spiceRange
}
convenience init(size: OrderSize, spiceRange: SpiceRange) {
self.init(count: 1, size: size, spiceRange: spiceRange)
}
convenience init(spiceRange: SpiceRange) {
self.init(count: 1, size: .medium, spiceRange: spiceRange)
}
}
class Restaurant {
var biriyaniOrder1 = Biriyani(size: .medium, spiceRange: .extraHot)
var biriyaniOrder2 = Biriyani(spiceRange: .extraHot)
...
}

Each convenience initializer removes an additional parameter and calls the original initializer with a default value. It allows other classes to create it without knowing the default values for the parameters:

var biriyaniOrder1 = Biriyani(size: .medium, spiceRange: .extraHot)

var biriyaniOrder2 = Biriyani(spiceRange: .extraHot)

Savvy, right? So far so good. Now what if the restaurant adds another parameter to the Biriyani class called BiriyaniType? We’ll have to add some extra convenience initializers to support more custom orders.

enum BiriyaniType {
case veg
case nonveg
}
class Biriyani {
let count: Int
let size: OrderSize
let spiceRange: SpiceRange
let type: BiriyaniType
init (count: Int, size: OrderSize, spiceRange: SpiceRange, type: BiriyaniType) {
self.count = count
self.size = size
self.spiceRange = spiceRange
self.type = type
}
convenience init(size: OrderSize, spiceRange: SpiceRange, type: BiriyaniType) {
self.init(count: 1, size: size, spiceRange: spiceRange, type: type)
}
convenience init(spiceRange: SpiceRange, type: BiriyaniType) {
self.init(count: 1, size: .medium, spiceRange: spiceRange, type: type)
}
convenience init(spiceRange: SpiceRange) {
self.init(count: 1, size: .medium, spiceRange: spiceRange, type: .nonveg)
}
convenience init(type: BiriyaniType) {
self.init(count: 1, size: .medium, spiceRange: .spicy, type: type)
}
}
class Restaurant {
var biriyaniOrder1 = Biriyani(size: .medium, spiceRange: .hot, type: .veg)
var biriyaniOrder2 = Biriyani(spiceRange: .extraHot, type: .veg)
var biriyaniOrder3 = Biriyani(spiceRange: .extraHot)
var biriyaniOrder4 = Biriyani(type: .veg)
...
}

Now the customers have a lot of options to order their biriyanis:

var biriyaniOrder1 = Biriyani(size: .medium, spiceRange: .hot, type: .veg)

var biriyaniOrder2 = Biriyani(spiceRange: .extraHot, type: .veg)

var biriyaniOrder3 = Biriyani(spiceRange: .extraHot)

var biriyaniOrder4 = Biriyani(type: .veg)

Just by increasing an extra parameter BiriyaniType, we’ve been forced the Biriyani class to add two more constructors to the class. Now consider what if the restaurant has to add more parameters like this? What will happen? This is the pitfall of using Telescoping Constructors. It is considered an Anti-pattern because, it results a large number of initializers that are hard to read and maintain.

Fortunately in swift we can provide default values to parameters:

class Biriyani {
let count: Int
let size: OrderSize
let spiceRange: SpiceRange
let type: BiriyaniType
init (count: Int = 1, size: OrderSize = .medium, spiceRange: SpiceRange = .spicy, type: BiriyaniType = .nonveg) {
self.count = count
self.size = size
self.spiceRange = spiceRange
self.type = type
}
}
class Restaurant {
var biriyaniOrder1 = Biriyani(size: .medium, spiceRange: .extraHot)
var biriyaniOrder2 = Biriyani(spiceRange: .extraHot)
...
}

The default values for the parameters  are used whenever the Restaurant class  wants to create a Biriyani by omitting them. This is the benefit of defining the default values within the Biriyani class without the necessity of defining a permutation of initializers.

I am going discuss another way to solve the telescoping initializers in my next post using Builder Pattern.

I hope you have enjoyed and understood the pitfalls of this particular anti-pattern.  Any questions, feedbacks or if you think I have some improvements to do, feel free to comment and let me know!

Happy coding! 😁

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.