21
NovFactory Design Pattern in C#: A Guide to Flexible Object Creation
Factory Design Pattern
Factory Design Pattern is a basic concept in C#'s object-oriented programming. It enables you to generate objects without defining the specific type of object that will be created. This pattern encapsulates object generation, making it easier and more manageable. Using a factory method or factory class allows you to abstract away the instantiation process, resulting in a more modular and scalable solution.
In this design pattern tutorial, we'll look at the Factory Design Pattern, explain what it is and when to use it, and provide examples to demonstrate its application. Let us begin by examining the following question: "What is the Factory Design Pattern?"
What is the Factory Method Pattern?
- The Factory Method Pattern is a design pattern that provides an interface for creating objects while allowing subclasses to determine the type of objects to be made. Bypassing the instantiation process to subclasses encourages loose coupling.
- Consider a logistics company that uses a variety of transportation methods, such as trucks, ships, and planes.
- The company may utilize a factory method to generate the proper transportation object depending on the individual requirements, allowing for simple extension or modification of transportation kinds without affecting the core logistics code.
Understanding the Problem of not using the Factory Design Pattern
- Tight coupling: This occurs when your code is tightly bound to specific classes, making it impossible to change or extend the types of objects created without modifying the current code. This can result in decreased flexibility and more maintenance effort.
- Code Duplication: Without a factory, object creation code is frequently duplicated across the program. This redundancy can cause inconsistencies and problems in the code, making it more challenging to manage.
- Difficulty in Testing: Testing components based on specific implementations might be tricky. Using a factory allows you to simply change out implementations for testing, making your code more testable and reusable.
- Scalability Issues: As your application expands, managing object creation directly in client code can become inefficient. The Factory Design Pattern centralizes object generation, making it more scalable and maintainable.
Example without using the Factory Pattern in C#
Example
using System;
public abstract class Document
{
public abstract void Open();
}
public class WordDocument : Document
{
public override void Open()
{
Console.WriteLine("Opening a Word document.");
}
}
public class PDFDocument : Document
{
public override void Open()
{
Console.WriteLine("Opening a PDF document.");
}
}
public class DocumentManager
{
public void OpenDocument(string type)
{
Document doc;
if (type == "Word")
{
doc = new WordDocument();
}
else if (type == "PDF")
{
doc = new PDFDocument();
}
else
{
throw new ArgumentException("Invalid document type");
}
doc.Open();
}
}
class Program
{
static void Main()
{
DocumentManager manager = new DocumentManager();
manager.OpenDocument("Word"); // Output: Opening a Word document.
manager.OpenDocument("PDF"); // Output: Opening a PDF document.
}
}
In this code, the DocumentManager class uses conditional logic to create WordDocument or PDFDocument objects based on the document type specified. This technique ties DocumentManager to specific document types and necessitates modification of the OpenDocument function whenever new document types are added, making it less flexible and more challenging to maintain.
After Implementing the Factory Design Pattern in C#
Example
using System;
// Abstract Product
public abstract class Document
{
public abstract void Open();
}
// Concrete Product 1
public class WordDocument : Document
{
public override void Open()
{
Console.WriteLine("Opening a Word document.");
}
}
// Concrete Product 2
public class PDFDocument : Document
{
public override void Open()
{
Console.WriteLine("Opening a PDF document.");
}
}
// Factory
public static class DocumentFactory
{
public static Document CreateDocument(string type)
{
switch (type)
{
case "Word":
return new WordDocument();
case "PDF":
return new PDFDocument();
default:
throw new ArgumentException("Invalid document type");
}
}
}
// Client Code
class Program
{
static void Main()
{
try
{
// Use the factory to create a WordDocument
Document wordDoc = DocumentFactory.CreateDocument("Word");
wordDoc.Open(); // Output: Opening a Word document.
// Use the factory to create a PDFDocument
Document pdfDoc = DocumentFactory.CreateDocument("PDF");
pdfDoc.Open(); // Output: Opening a PDF document.
// Attempting to create an invalid document type
Document invalidDoc = DocumentFactory.CreateDocument("Excel");
invalidDoc.Open(); // This line will throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message); // Output: Invalid document type
}
}
}
In this example, the DocumentFactory class centralizes the generation of Document objects, allowing the client code in the Main method to request documents without knowing which classes are involved. This technique simplifies object generation, increases flexibility by eliminating reliance on specific implementations, and makes document types easier to manage and extend.
Factory Method Pattern - UML Diagram & Implementation
The UML class diagram for the implementation of the factory method design pattern is given below:
The classes, interfaces, and objects in the above UML class diagram are as follows:
Product- Role: Interface
- Purpose: Specify a standard interface for factory-created items. It assures that all concrete products follow the same set of processes.
- Role: Class
- Purpose: This class implements the Product interface and its specialized implementation. It defines the product's concrete behavior and attributes.
- Role: Abstract Class.
- Purpose: Declares a factory method that returns a Product instance. It may also define additional methods that employ Product objects, but it does not explain how to build them. This course serves as a framework for developing products.
- Role: Subclass of Creator.
- Purpose: Uses the factory technique to create customized Product instances. Defines the actual process of creating goods and guarantees that the correct Product type is instantiated. This class provides a concrete implementation for product generation.
Here's a more extensive example that demonstrates the relationships
interface Product
{
}
class ConcreteProductA : Product
{
}
class ConcreteProductB : Product
{
}
abstract class Creator
{
public abstract Product FactoryMethod(string type);
}
class ConcreteCreator : Creator
{
public override Product FactoryMethod(string type)
{
switch (type)
{
case "A": return new ConcreteProductA();
case "B": return new ConcreteProductB();
default: throw new ArgumentException("Invalid type", "type");
}
}
}
This code demonstrates the Factory Method design pattern, in which a ConcreteCreator class determines which concrete Product type to construct based on input, separating object production from consumption.
Understanding Factory Method Pattern using a real-time example
The classes, interfaces, and objects in the above class diagram can be identified as follows:
IFactory - This interface defines the way to create objects. It declares the factory method, which yields an IVehicle object (or something similar).
Scooter & Bike - These classes reflect the products that the factory method generates. They use the IVehicle interface (or something similar) to identify individual vehicle kinds, such as scooters and bicycles.
VehicleFactory - This is an abstract class or interface that declares the factory method, which returns an IVehicle. It may also include other methods that work with IVehicle objects, but it does not indicate which type of vehicle will be built.
ConcreteVehicleFactory - These are concrete classes that use the factory approach to produce specific items. For example, ScooterFactory and BikeFactory are concrete creators that override the factory method to create and return a Scooter or Bike object, respectively.
C# - Sample Code
using System;
namespace Factory
{
/// <summary>
/// The 'Product' interface
/// </summary>
public interface IFactory
{
void Drive(int miles);
}
/// <summary>
/// A 'ConcreteProduct' class
/// </summary>
public class Scooter : IFactory
{
public void Drive(int miles)
{
Console.WriteLine("Drive the Scooter : " + miles.ToString() + "km");
}
}
/// <summary>
/// A 'ConcreteProduct' class
/// </summary>
public class Bike : IFactory
{
public void Drive(int miles)
{
Console.WriteLine("Drive the Bike : " + miles.ToString() + "km");
}
}
/// <summary>
/// The Creator Abstract Class
/// </summary>
public abstract class VehicleFactory
{
public abstract IFactory GetVehicle(string Vehicle);
}
/// <summary>
/// A 'ConcreteCreator' class
/// </summary>
public class ConcreteVehicleFactory : VehicleFactory
{
public override IFactory GetVehicle(string Vehicle)
{
switch (Vehicle)
{
case "Scooter":
return new Scooter();
case "Bike":
return new Bike();
default:
throw new ApplicationException(string.Format("Vehicle '{0}' cannot be created", Vehicle));
}
}
}
/// <summary>
/// Factory Pattern Demo
/// </summary>
class Program
{
static void Main(string[] args)
{
VehicleFactory factory = new ConcreteVehicleFactory();
IFactory scooter = factory.GetVehicle("Scooter");
scooter.Drive(10);
IFactory bike = factory.GetVehicle("Bike");
bike.Drive(20);
Console.ReadKey();
}
}
}
This code includes a Vehicle Factory that wraps vehicle production. This allows clients to access alternative vehicles (scooters or Bikes) without knowing their specific classes, promoting flexibility and loose coupling.
Output
Drive the Scooter : 10km
Drive the Bike : 20km
Read More Articles Related to Design Pattern |
Summary
The factory method design pattern provides a flexible and centralized approach to object production. It separates clients from the creation process, allowing dynamic object instantiation based on runtime requirements. This pattern allows for clearer, more maintainable code and promotes loose coupling, making it an invaluable tool for developers. Also, consider our .NET design patterns training for a better understanding of other design patterns concepts.