Factory Design Pattern Introduction
What is Factory Design Pattern
Gang of Four Definition :
“Define an interface for creating an object, but let sub-classes decide which class to instantiate. The Factory method lets a class defer instantiation it uses to sub-classes”
Factory pattern is one of the most used design patterns in real world applications
Factory pattern creates object without exposing the creation logic to the client and refer to newly created object using a common interface
From the above diagram, client uses factory and creates the product.
Implementation Guidelines :
We need to choose Factory Pattern when
The Object needs to be extended to subclasses
The Classes doesn’t know what exact sub-classes it has to create
The Product implementation tend to change over time and the Client remains unchanged
Simple Factory Example : Business Requirement
Differentiate employees as permanent and contract and segregate their pay scales as well as bonus based on their employee types
We can address the above requirement with the below implementations
- Implement without Factory Pattern
- Use a Simple Factory
- Enhance Simple factory to Factory Method Pattern
Prerequisite steps
CREATE TABLE [dbo].[Employee_Type] ( [Id] INT IDENTITY (1, 1) NOT NULL, [EmployeeType] VARCHAR (150) NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC) )
Step 2 : Add Permanent and Contract Employees as Master Data
CREATE TABLE [dbo].[Employee] ( [Id] INT IDENTITY (1, 1) NOT NULL, [Name] VARCHAR (50) NOT NULL, [JobDescription] VARCHAR (50) NOT NULL, [Number] VARCHAR (50) NOT NULL, [Department] VARCHAR (50) NOT NULL, [HourlyPay] DECIMAL (18) NOT NULL, [Bonus] DECIMAL (18) NOT NULL, [EmployeeTypeID] INT NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Employee_EmployeeType] FOREIGN KEY ([EmployeeTypeID]) REFERENCES [dbo].[Employee_Type] ([Id])
Step 4 : Update the Emloyee Model edmx file with the latest changes
public class BaseController : Controller { private ILog _ILog; public BaseController() { _ILog = Log.GetInstance; } protected override void OnException(ExceptionContext filterContext) { _ILog.LogException(filterContext.Exception.ToString()); filterContext.ExceptionHandled = true; this.View("Error").ExecuteResult(this.ControllerContext); } }
Step 6 : Regenerate the EmployeesController and its corresponding views
Step 7 : Comment the code in Create and Update views which accepts inputs for Bonus and HourlyPay
Solution 1: Implement without Factory Pattern
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "Id,Name,JobDescription,Number,Department,HourlyPay,Bonus,EmployeeTypeID")] Employee employee) { if (ModelState.IsValid) { if (employee.EmployeeTypeID == 1) { employee.HourlyPay = 8; employee.Bonus = 10; } else if (employee.EmployeeTypeID == 2) { employee.HourlyPay = 12; employee.Bonus = 5; } db.Employees.Add(employee); db.SaveChanges(); return RedirectToAction("Index"); } ViewBag.EmployeeTypeID = new SelectList(db.Employee_Type, "Id", "EmployeeType", employee.EmployeeTypeID); return View(employee); }
The above code introduces a) Tight coupling between Controller class and Business logic
b) For any new employee type addition, we end up modifying the controller code adding extra over heads in the development and testing process
Using a simple factory eliminates the above drawbacks.
Solution 2: Implement with Simple Factory
Step 1 : Add new Manager folder and add the below interface and classes
public interface IEmployeeManager { decimal GetBonus(); decimal GetPay(); }
public class ContractEmployeeManager : IEmployeeManager { public decimal GetBonus() { return 5; } public decimal GetPay() { return 12; } }
public class PermanentEmployeeManager : IEmployeeManager { public decimal GetBonus() { return 10; } public decimal GetPay() { return 8; } }
Step 2 : Create Factory folder and add the below Manager class
public class EmployeeManagerFactory { public IEmployeeManager GetEmployeeManager(int employeeTypeID) { IEmployeeManager returnValue = null; if (employeeTypeID == 1) { returnValue = new PermanentEmployeeManager(); } else if (employeeTypeID == 2) { returnValue = new ContractEmployeeManager(); } return returnValue; } }
Step 3 : Update the employee’s controller to consume the factory.
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "Id,Name,JobDescription,Number,Department,HourlyPay,Bonus,EmployeeTypeID")] Employee employee) { if (ModelState.IsValid) { EmployeeManagerFactory empFactory = new EmployeeManagerFactory(); IEmployeeManager empManager = empFactory.GetEmployeeManager(employee.EmployeeTypeID); employee.Bonus = empManager.GetBonus(); employee.HourlyPay = empManager.GetPay(); db.Employees.Add(employee); db.SaveChanges(); return RedirectToAction("Index"); } ViewBag.EmployeeTypeID = new SelectList(db.Employee_Type, "Id", "EmployeeType", employee.EmployeeTypeID); return View(employee); }