Factory Design Pattern Example

Factory Design Pattern: The factory method pattern is an object-oriented creational design pattern. Factory Pattern deals with the problem of creating objects without specifying the class of object that will be created. 

Creating an object often requires complex processes, which is not appropriate to include within a composing object. The object's creation may lead to a significant code duplication and may not provide a sufficient level of abstraction. In an application if object creation code is spread in whole application, and if you need to change the process of object creation then you need to go in each and every place to make necessary changes.

Factory design pattern handles these problems by defining a separate method for creating the objects.
In simple words, if we have a series of similar kind of class and want to create objects for these classes, for example if we have a super class and many sub-classes, and based on data provided, we need to return the object of one of the sub-classes, then we use a factory pattern.

This pattern introduces loose coupling between classes which is the most important principle one should consider and apply while designing the application. Loose coupling can be introduced by using abstract entities rather than concrete implementations.

When to use: The Factory patterns can be used in following cases:
1. When a class does not know which class of objects it must create.
2. A class specifies its sub-classes to specify which objects to create.
3. At run time you need to create an object based on input parameter.

How to use: To use the factory pattern we need to define an interface for creating an object, but let the classes that implement the interface decide which class to instantiate.

Let us understand this with a simple problem example.

Suppose you want to develop a Media Player application, which should play the mp3 and mp4 format audio , video files. The class diagram for Player can be shown as following

The Interface and classes involved are shown below

Interface Decoder.java is written as following
public interface Decoder {
InputStream decodeFile(String mediaFileName);
}

MP3Decoder.java can be written as

public class MP3Decoder implements Decoder {

@Override
public InputStream decodeFile(String mediaFileName) {
//decode your file here and return the decoded stream
System.out.println("MP3Decoder: decoding file "+mediaFileName);
return null;
}
}

MP4Decoder.java can be written as

public class MP4Decoder implements Decoder {

@Override
public InputStream decodeFile(String mediaFileName) {
//decode your file here and return the decoded stream
System.out.println("MP4Decoder: decoding file "+mediaFileName);
return null;
}
}

Player.java can look like this

public class Player {

public void init() {
// initialize the various audio,video components here
}

public void play(String mediaFileName) {
if (mediaFileName == null || "".equals(mediaFileName)) {
throw new RuntimeException("Invalid media file.");
}
Decoder decoder = getMediaDecoder(mediaFileName);
if (decoder != null) {
play(decoder.decodeFile(mediaFileName));
} else {
throw new RuntimeException("Media format not supported.");
}
}

private Decoder getMediaDecoder(String mediaFileName) {
mediaFileName = mediaFileName.toUpperCase();
if (mediaFileName.endsWith(".MP3")) {
return new MP3Decoder();
} else if (mediaFileName.endsWith(".MP4")) {
return new MP4Decoder();
}
return null;
}

private void play(InputStream stream) {
System.out.println("Playing..$..$");
}
}


Problem: As you can observe here that the classes MP3Decoder and MP4Decoder are tightly coupled with the Player. If Player need to support the a new format then a new decoder needs to be added in Player class. So each time we need to modify the Player class.

Solution: To avoid these problems we can apply Factory design pattern here. The class diagram using Factory can be shown as following.




To Apply the factory pattern we can introduce a DecoderFactory.java class and can move the Decoder objects creation from Player class to this class as following.


public class DecoderFactory{
public static Decoder getMediaDecoder(String mediaFileName) {
mediaFileName = mediaFileName.toUpperCase();
if (mediaFileName.endsWith(".MP3")) {
return new MP3Decoder();
} else if (mediaFileName.endsWith(".MP4")) {
return new MP4Decoder();
}
return null;
}
}

Now modify the Player class to use this factory class

public class Player {
public void init() {
// initialize the various audio,video components here
}

public void play(String mediaFileName) {
if (mediaFileName == null || "".equals(mediaFileName)) {
throw new RuntimeException("Invalid media file.");
}
Decoder decoder = DecoderFactory.getMediaDecoder(mediaFileName);
if (decoder != null) {
play(decoder.decodeFile(mediaFileName));
} else {
throw new RuntimeException("Media format not supported.");
}
}

private void play(InputStream stream) {
System.out.println("Playing..$..$");
}
}

To test our factory for player we can write a FactorPatternDemo.java class as following

public class FactorPatternDemo {

public static void main(String[] args) {
String mediaFile = "C://songs/song1.mp3";
Player player = new Player();
player.init();
player.play(mediaFile);
//play other file
mediaFile = "C://videos/song2.mp4";
player.play(mediaFile);
}
}

Output:

MP3Decoder: decoding file C://songs/song1.mp3
Playing..$..$
MP4Decoder: decoding file C://videos/song2.mp4
Playing..$..$

Now you can see in the Player class, the coupling with Decoders is removed with the help of DecoderFactor. And also if Player need to support a new format then no modification is required in Player class, only Factory need to be updated for new type of Decoder.

No comments :

Post a Comment