The factory pattern is a creational design pattern that provides an interface for creating objects in a super class, but allows subclasses to alter the type of objects that will be created.
In the factory pattern, a factory class is responsible for creating objects of a specific type. The factory class has a method that creates objects, and this method is called the “factory method.” The factory method is responsible for creating objects of a specific type, and it does this by calling a constructor or some other method to create the object.
The factory pattern is useful when you want to create objects of a specific type, but you want to allow subclasses to alter the type of objects that will be created. This is useful in situations where you have a class hierarchy and you want to allow subclasses to specify the type of objects that will be created.
Here is a simple example of the factory pattern in TypeScript:
interface Product {
getName(): string;
}
class ConcreteProductA implements Product {
getName(): string {
return 'Product A';
}
}
class ConcreteProductB implements Product {
getName(): string {
return 'Product B';
}
}
abstract class Creator {
abstract factoryMethod(): Product;
}
class ConcreteCreatorA extends Creator {
factoryMethod(): Product {
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends Creator {
factoryMethod(): Product {
return new ConcreteProductB();
}
}
In this example, the Creator
class defines an abstract factory method that returns a Product
object. The ConcreteCreatorA
and ConcreteCreatorB
classes are concrete implementations of the Creator
class that override the factory method to create specific types of products: ConcreteProductA
and ConcreteProductB
respectively.
To use these classes, you would create an instance of a concrete creator class and then call its factory method to create a product object.
const creator = new ConcreteCreatorA();
const product = creator.factoryMethod();
console.log(product.getName()); // Output: "Product A"
const creator = new ConcreteCreatorB();
const product = creator.factoryMethod();
console.log(product.getName()); // Output: "Product B"
Another Example
Imagine that you are building a mobile app that needs to display a list of items to the user. The items can be of different types, such as text, images, or videos. You want to allow the user to tap on an item to view more details about it.
To implement this, you could use the factory pattern to create different types of “item views” for each type of item. The item views would be responsible for rendering the item and handling user interactions.
Here is an example of how you might implement this using the factory pattern in TypeScript:
interface ItemView {
render(item: Item): HTMLElement;
}
class TextItemView implements ItemView {
render(item: TextItem): HTMLElement {
const container = document.createElement('div');
container.textContent = item.text;
return container;
}
}
class ImageItemView implements ItemView {
render(item: ImageItem): HTMLElement {
const container = document.createElement('div');
const image = document.createElement('img');
image.src = item.imageUrl;
container.appendChild(image);
return container;
}
}
class VideoItemView implements ItemView {
render(item: VideoItem): HTMLElement {
const container = document.createElement('div');
const video = document.createElement('video');
video.src = item.videoUrl;
container.appendChild(video);
return container;
}
}
class ItemViewFactory {
static createView(item: Item): ItemView {
if (item instanceof TextItem) {
return new TextItemView();
} else if (item instanceof ImageItem) {
return new ImageItemView();
} else if (item instanceof VideoItem) {
return new VideoItemView();
} else {
throw new Error('Invalid item type');
}
}
}
To use these item views, you would call the createView
method of the ItemViewFactory
class, passing in an instance of the item that you want to render. The factory will return the appropriate item view for that type of item.
const item = new TextItem('Hello, world!');
const view = ItemViewFactory.createView(item);
const element = view.render(item);
Another Example
interface Animal {
makeSound(): void;
}
class Dog implements Animal {
makeSound() {
console.log('Woof!');
}
}
class Cat implements Animal {
makeSound() {
console.log('Meow!');
}
}
class AnimalFactory {
static createAnimal(type: string): Animal {
switch (type) {
case 'dog':
return new Dog();
case 'cat':
return new Cat();
default:
throw new Error(`Invalid animal type: ${type}`);
}
}
}
const dog = AnimalFactory.createAnimal('dog');
dog.makeSound(); // prints "Woof!"
const cat = AnimalFactory.createAnimal('cat');
cat.makeSound(); // prints "Meow!"
In this example, the Animal
interface defines a makeSound
method that all animal classes must implement. The Dog
and Cat
classes are implementations of the Animal
interface, and they each provide a different implementation of the makeSound
method.
The AnimalFactory
class has a createAnimal
method that takes a string parameter representing the type of animal to create. The method uses a switch
statement to determine which type of animal to create, and returns a new instance of the corresponding animal class.
The createAnimal
method can be called with either ‘dog’ or ‘cat’ to create a new instance of the Dog
or Cat
class, respectively. When the makeSound
method is called on the resulting object, it will print the appropriate sound for the type of animal.
Overall, the Factory pattern is a useful way to abstract the creation of objects and allow for flexibility in the types of objects that can be created. It can be used in situations where the exact class of object that needs to be created is not known in advance, or when the process of creating objects is complex and needs to be encapsulated in a separate class.
Another Example
interface Item {
id: number;
name: string;
price: number;
}
interface ItemView {
render(): string;
}
class BasicItemView implements ItemView {
constructor(private item: Item) {}
render(): string {
return `
<div class="item">
<h3>${this.item.name}</h3>
<p>Price: $${this.item.price}</p>
</div>
`;
}
}
class PremiumItemView implements ItemView {
constructor(private item: Item) {}
render(): string {
return `
<div class="item premium">
<h3>${this.item.name}</h3>
<p>Price: $${this.item.price}</p>
<p>This is a premium item!</p>
</div>
`;
}
}
class ItemViewFactory {
static createItemView(item: Item): ItemView {
if (item.price > 50) {
return new PremiumItemView(item);
} else {
return new BasicItemView(item);
}
}
}
const item1: Item = {
id: 1,
name: 'Widget',
price: 25
};
const item2: Item = {
id: 2,
name: 'Gadget',
price: 75
};
const itemView1 = ItemViewFactory.createItemView(item1);
console.log(itemView1.render());
// prints:
// <div class="item">
// <h3>Widget</h3>
// <p>Price: $25</p>
// </div>
const itemView2 = ItemViewFactory.createItemView(item2);
console.log(itemView2.render());
// prints:
// <div class="item premium">
// <h3>Gadget</h3>
// <p>Price: $75</p>
// <p>This is a premium item!</p>
// </div>
In this example, the Item
interface represents a product that can be purchased, with an id
, name
, and price
. The ItemView
interface represents a view for an Item
, with a render
method that returns a string of HTML that can be displayed.
The BasicItemView
and PremiumItemView
classes are implementations of the ItemView
interface, and they each provide a different implementation of the render
method that generates different HTML for basic and premium items, respectively.
The ItemViewFactory
class has a createItemView
method that takes an Item
object as a parameter and returns an ItemView
object based on the price of the item. If the item’s price is greater than 50, the method returns a PremiumItemView
object. Otherwise, it returns a BasicItemView
object.
The createItemView
method can be called with an Item
object to create an ItemView
object that can be used to render the item. When the `