One interesting interview question that tests your understanding of object creation control is: Design a class such that only 5 objects can be created. If you try to create a 6th object, it should throw an error.
In this blog post, we’ll explore two solutions to this problem—one using a static class property and the other using a decorator pattern. Both methods effectively limit the number of objects that can be instantiated, but they approach the problem from different angles.
Let’s dive in!
Solution 1: Using a Static Class Property
In this approach, we use a static class property to keep track of the number of instances that have been created. A static property is shared across all instances of the class, which makes it the perfect mechanism to maintain a global instance count.
Code Example:
export class CustomerService {
static instanceObject: CustomerService;
private static MAX_INSTANCE_COUNT = 5;
private static currentCount = 0;
private constructor() {}
public static getInstance() {
this.currentCount++;
if (this.currentCount <= this.MAX_INSTANCE_COUNT) {
return new CustomerService();
} else {
throw new Error('You cannot create more instances');
}
}
}
Step-by-Step Breakdown:
- Static Properties:
static instanceObject: CustomerService;
private static MAX_INSTANCE_COUNT = 5;
private static currentCount = 0;
instanceObject
: This static property will hold the class instance.MAX_INSTANCE_COUNT
: This constant limits the number of objects that can be created.currentCount
: A static counter that tracks how many objects have been created.
- Private Constructor:
private constructor() {}
- The constructor is made private to ensure objects cannot be created using the
new
keyword directly.
- getInstance Method:
public static getInstance() {
this.currentCount++;
if (this.currentCount <= this.MAX_INSTANCE_COUNT) {
return new CustomerService();
} else {
throw new Error('You cannot create more instances');
}
}
- Each time
getInstance
is called, it increments thecurrentCount
. - If the
currentCount
is within the limit (MAX_INSTANCE_COUNT
), it creates a new instance usingnew CustomerService()
. - If
currentCount
exceeds the limit, an error is thrown.
Example Usage:
const instance1 = CustomerService.getInstance(); // Success
const instance2 = CustomerService.getInstance(); // Success
//...
const instance5 = CustomerService.getInstance(); // Success
const instance6 = CustomerService.getInstance(); // Error: "You cannot create more instances"
Solution 2: Using a Decorator Pattern
In the second approach, we use a decorator to control the number of objects that can be instantiated. This is a more flexible solution as it allows you to reuse the restriction logic with different classes.
Code Example:
export function RestrictInstances(count: number) {
return function<T extends { new (...args: any[]): {} }>(classObj: T) {
let currentCount = 0;
return class extends classObj {
constructor(...args: any[]) {
currentCount++;
if (currentCount <= count) {
super(...args);
} else {
throw new Error('You cannot create more objects');
}
}
};
};
}
@RestrictInstances(5)
class Sales {
constructor(public name: string) {}
}
Step-by-Step Breakdown:
- Decorator Function:
export function RestrictInstances(count: number) {
return function<T extends { new (...args: any[]): {} }>(classObj: T) {
RestrictInstances
is a decorator factory that accepts acount
(the max number of instances allowed).- Inside, we return a new class that extends the original class (
classObj
).
- Instance Counter:
let currentCount = 0;
- This
currentCount
variable tracks how many objects have been instantiated.
- Constructor Logic:
return class extends classObj {
constructor(...args: any[]) {
currentCount++;
if (currentCount <= count) {
super(...args);
} else {
throw new Error('You cannot create more objects');
}
}
};
- Each time a new object is created, the constructor increments
currentCount
. - If the
currentCount
exceeds the allowed limit (count
), the decorator throws an error.
- Class Usage:
@RestrictInstances(5)
class Sales {
constructor(public name: string) {}
}
- The
@RestrictInstances(5)
decorator is applied to theSales
class, restricting it to a maximum of 5 objects.
Example Usage:
const sale1 = new Sales('Deal1'); // Success
const sale2 = new Sales('Deal2'); // Success
//...
const sale5 = new Sales('Deal5'); // Success
const sale6 = new Sales('Deal6'); // Error: "You cannot create more objects"
Comparing Both Solutions
- Static Class Method:
- Pros:
- Simplicity: Easy to understand.
- Centralized logic inside the class.
- Cons:
- Limited to one class.
- The restriction logic cannot be easily reused for other classes.
- Decorator Pattern:
- Pros:
- Flexibility: You can reuse this decorator with any class, simply by applying the decorator.
- Cleaner code: The restriction logic is separate from the class definition.
- Cons:
- Slightly more complex due to the use of TypeScript’s advanced decorator feature.
Conclusion
Both approaches achieve the goal of limiting the number of instances that can be created from a class. The static class method is simple and works well if you need to enforce this behavior for a single class. On the other hand, the decorator pattern is more powerful and reusable, allowing you to apply the same instance restriction logic to multiple classes.
For interviews, both solutions show a good understanding of object creation control, class-level restrictions, and more advanced TypeScript features like decorators. Try both methods out and see which one fits your style!