Lets consider a Scenario
Assume that, we need to fetch set of data from a database. We will be writing a very simple class to achieve this.
public class QueryManager
{
private string query;
public QueryManager()
{
}
public string FetchRowData()
{
//Return Query to get Top 10 rows.
}
}
What if we need to support multiple type of databases like MSSQL, Oracle, My SQL, etc.
Let us go ahead and modify the existing code to accommodate these requirements.
Here we have updated the FetchRowData method to return query based on the database dialect enumeration.
public class QueryManager
{
private DatabaseType _dbType;
private string query;
public QueryManager(DatabaseType dbType)
{
this._dbType = dbType;
}
public string FetchRowData(int numberOfRows)
{
switch (this._dbType)
{
case DatabaseType.SQL:
query = "select top " + numberOfRows.ToString() + " * from [Customer]";
break;
case DatabaseType.Oracle:
query = "select * from (select * from [Customer]) where rownum <= " + numberOfRows.ToString();
break;
case DatabaseType.DB2:
query = "select * from [Customer] fetch first " + numberOfRows.ToString() + " rows only";
break;
}
return query;
}
}
Though having a switch statement provides a solution to the problem. This solution is also not scalable for every new database we need to support. FetchRowData. Essentially the QueryManager class violates one the basic tenets of OO programming i.e. “Keep the classes Closed for modification but Open for extension”
Possible Pattern to Apply
We can eliminate some of the potentical scalable issues by falling back to one the classic OO principles i.e. “Encapsulate what varies”. This is called as Strategy Pattern.
The Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Implementation
To implement the strategy pattern we would need to separate the things which are subjected to change, and in our current context it is the database dialect. So we separate implementation for all the actions which are dependent on the choice of database
public abstract class QueryManagerBase : ISqlDialect
{
public virtual string FetchRowData(int numberOfRows)
{
throw new NotImplementedException();
}
}
public class OracleQueryManager : QueryManagerBase
{
public override string FetchRowData(int numberOfRows)
{
return "select * from (select * from [Customer]) where rownum <= " + numberOfRows.ToString();
}
}
public class SQLQueryManager : QueryManagerBase
{
public override string FetchRowData(int numberOfRows)
{
return "select top " + numberOfRows.ToString() + " * from [Customer]";
}
}
public class DB2QueryManager : QueryManagerBase
{
public override string FetchRowData(int numberOfRows)
{
return "select * from [Customer] fetch first " + numberOfRows.ToString() + " rows only";
}
}
Why is this better
- Encapsulating the algorithm in separate Strategy classes provides flexibility to vary the algorithm independently of its context.
- The code of the strategy's containing class doesn't have to be changed when a new strategy is added.
- Polymorphic dispatch coding is very simple.
Drawbacks
- A client must understand how Strategies differ to be able to select the right strategy.
Hope you enjoyed reading.