Factories can be used in conjunction with Inversion of Control (IoC) libraries too.
Example
User
class, whose characteristics (ID, Security clearance level, etc.), are unknown until runtime (since the current user could be anyone who uses the application).ISecurityToken
for them, which can then be used to check if the user is allowed to perform certain actions or not.In this case, we have two implementations, which also use Marker Interfaces to make it easier to identify them to the IoC library; the IoC library in this case is just made up and identified by the abstraction IContainer
.
Note also that many modern IoC factories have native capabilities or plugins that allow auto-creation of factories as well as avoiding the need for marker interfaces as shown below; however since not all do, this example caters to a simple, lowest common functionality concept.
//describes the ability to allow or deny an action based on PerformAction.SecurityLevel
public interface ISecurityToken
{
public bool IsAllowedTo(PerformAction action);
}
//Marker interface for Basic permissions
public interface IBasicToken:ISecurityToken{};
//Marker interface for super permissions
public interface ISuperToken:ISecurityToken{};
//since IBasictoken inherits ISecurityToken, BasicToken can be treated as an ISecurityToken
public class BasicToken:IBasicToken
{
public bool IsAllowedTo(PerformAction action)
{
//Basic users can only perform basic actions
if(action.SecurityLevel!=SecurityLevel.Basic) return false;
return true;
}
}
public class SuperToken:ISuperToken
{
public bool IsAllowedTo(PerformAction action)
{
//Super users can perform all actions
return true;
}
}
Next we will create a SecurityToken
factory, which will take as a dependency our IContainer
public class SecurityTokenFactory
{
readonly IContainer _container;
public SecurityTokenFactory(IContainer container)
{
if(container==null) throw new ArgumentNullException("container");
}
public ISecurityToken GetToken(User user)
{
if (user==null) throw new ArgumentNullException("user);
//depending on the user security level, we return a different type; however all types implement ISecurityToken so the factory can produce them.
switch user.SecurityLevel
{
case Basic:
return _container.GetInstance<BasicSecurityToken>();
case SuperUser:
return _container.GetInstance<SuperUserToken>();
}
}
}
Once we've registered these with the IContainer
:
IContainer.For<SecurityTokenFactory>().Use<SecurityTokenFactory>().Singleton(); //we only need a single instance per app
IContainer.For<IBasicToken>().Use<BasicToken>().PerRequest(); //we need an instance per-request
IContainer.For<ISuperToken>().Use<SuperToken>().PerRequest();//we need an instance per-request
the consuming code can use it to get the correct token at runtime:
readonly SecurityTokenFactory _tokenFactory;
...
...
public void LogIn(User user)
{
var token = _tokenFactory.GetToken(user);
user.SetSecurityToken(token);
}
In this way we benefit from the encapsulation provided by the factory and also from the lifecycle management provided by the IoC library.