Introduction
This article attempts to show a usage of the Factory Method design pattern in combination with reflection to achieve extensibility to our application.
In order to illustrate it, we will use an example of creation of accounts for a money management application.
Background
It's not the intention of this article to teach how the Factory Method pattern works, nor the power/features of reflection, instead, we want to demonstrate how to take advantage of them together. If you need to understand any of these two concepts, you may want to search for it first, the Internet is full of materials about it, so you will not have problems finding it! ;)
Problem
Suppose we have to design a money management application, where a user can have different types of accounts to keep track of his/her money (you can think of Microsoft Money, Quicken, ...). The different types of accounts could be for example (among many others):
- Bank Account
- Credit Card
- Investment Account
- Loan
- Retirement Account
- Stock Options
- Wallet
- Under the Mattress
- etc.
For each of the account types, we have to ask the user for specific information (maybe even store in a different database table,... depends on your application). For example, you may want to include for a bank account, information regarding account number, opening date, bank identifier, etc., information that will be pointless for a Wallet account.
So, the problem here is to efficiently ask the user to enter the information for the accounts, and do that in a way we could easily add new account types if needed.
Step 1: Initial Design
Starting our way up to better code, the first approach would be to create an Account
class, and add a AccountType
property to it. In this way, we will have create in this class, properties to all information possible to all kinds of accounts, and when we need to prompt for that information, we do something like the following:
public void PromptForDetails()
{
Account account = new Account();
account.AccountType = comboSelectedType;
if (account.AccountType == AccountTypeEnum.BankAccount)
{
}
else if (account.AccountType == AccountTypeEnum.InvestmentAccount)
{
}
}
With this, we will have to deal with creating and maintaining the enumeration for the account types, filling the combos, etc.
Step 2: Different Classes for Different Account Types
Well, improving our code we now add one different class for each account type, and put a method to prompt for details in each:
public class BankAccount
{
public void PromptForDetails()
{
}
}
public class InvestmentAccount
{
public void PromptForDetails()
{
}
}
Better yet, we create a superclass our account classes could inherit methods from:
public abstract class AccountBase
{
public abstract void PromptForDetails();
}
public class BankAccount : AccountBase
{
public override void PromptForDetails()
{
}
}
public class InvestmentAccount : AccountBase
{
}
But we will still have to prompt for details like this:
public void PromptForDetails()
{
Account account;
if (comboSelectedType == AccountTypeEnum.BankAccount)
{
account = new BankAccount();
}
else if (comboSelectedType == AccountTypeEnum.InvestmentAccount)
{
account = new InvestmentAccount();
}
account.PromptForDetails();
}
Now we could take care of the prompting for account details in each of our classes, and reuse it along the application. But we still have to create the enum
, fill the combo and create an if
block to verify which type was selected (and all these places will have to be modified if a new account type is added).
Step 3: Now with the Factory Method + Reflection
So, the idea here is to make the program decide which account type we want. That's where the Factory Method design pattern comes in place. The problem we will still have is that we have to make the factory aware of which types of accounts we have available, and that is what makes it difficult for us at the time we want to add more account types in the system, because we will need to go in the factory class and change it appropriately.
The solution is to use reflection to look at our program and inform the factory which account types we have. So the first thing is how do we know every account type we have?
To do that, we will need to create a property in each account class, to inform how we want the type to be called. For that, let's create the property "AccountType
":
public class BankAccount : AccountBase
{
public static String AccountType
{
get { return "Bank Account"; }
}
}
public class InvestmentAccount : AccountBase
{
public static String AccountType
{
get { return "Investment Account"; }
}
}
Now we will use reflection to find every class that inherits from "AccountBase
", and we will read from it, the property we just create:
public static List<string> AccountTypeList()
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();
List<string> list = new List<string>();
Type[] types;
types = currentAssembly.GetTypes();
foreach (Type currentType in types)
{
if (currentType.BaseType != null &&
currentType.BaseType.Name.Contains("AccountBase"))
{
try
{
String accountType;
accountType = currentType.GetProperty("AccountType",
BindingFlags.Static |
BindingFlags.Public).GetValue(null, null).ToString();
if (!String.IsNullOrEmpty(accountType))
{
list.Add(accountType);
}
}
catch (Exception ex)
{
}
}
}
list.Sort(delegate(string s1, string s2) { return s1.CompareTo(s2); });
return list;
}
Ok, until here we have one benefit already, we could much more easily fill a combo for account type for example, and may no more need enum
s. Note that the code below to fill a combo will never have to change, no matter how many new account classes you add:
combo.Items.Clear();
foreach (String s in AccountTypeList())
{
combo.Items.Add(s);
}
Knowing all the account types, and how to present them to the user (maybe by a combo like above), we just need to be able to create new instances of an account of the type we want by our factory. Similarly to looking for the list of account types, we create or Factory method like this:
public static AccountBase FactoryMethod(string accountTypeDesired)
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();
Type[] types;
types = currentAssembly.GetTypes();
foreach (Type currentType in types)
{
if (currentType.BaseType != null &&
currentType.BaseType.Name.Contains("AccountBase"))
{
try
{
String accountType;
accountType = currentType.GetProperty("AccountType",
BindingFlags.Static |
BindingFlags.Public).GetValue(null, null).ToString();
if (accountType.Trim() == accountTypeDesired.Trim())
{
return (AccountBase)Activator.CreateInstance(currentType);
}
}
catch (Exception ex)
{
}
}
}
return null;
}
Well, that's it! Now our code to create and prompt for accounts will simple be (and will never need to change as we add new account types):
String selectedAccountType = comboAccountTypes.Text;
if (!String.IsNullOrEmpty(selectedAccountType))
{
AccountBase account = AccountFactory.FactoryMethod(selectedAccountType);
account.PromptForDetails();
}
Sample Project
We encourage you to download the sample project and see how it's done, see how the classes are structured, etc. Also, try adding a new account class and see how it works...
Conclusion
In this article, we were able to create an extensible application to manage different types of accounts. Each new type of account could be easily added to the system by just adding a new class that subclasses the account base class. In this article, the accounts example was just a good way I found to express the idea behind factory+reflection, but you can use it in several different opportunities in your own projects, and of course, as more 'changeable' the feature you have is, more you will benefit from using the approach present here. Anyway, hope it will be of any use for some of you. Thank you for reading!
History
- 31st August, 2008: Initial post