Friday, December 28, 2007

Using abstract class to separate the logic

Problem: 

1) I have an windows app that I am converting into web app.
2) I have DAL in win app that should be changed as little as possible because it is also currently in development. And if it will be supplemented with new methods, classes etc., it should continue working in web app with minimal changes/corrections.
3) Win app data objects in DAL are mainly ok to use in web version, but the mechanism of connecting to database and populating and caching data objects is very different from one I am going to use in web app. The main problem is that every data object has static private variable that points to SqlConnection. That class is responsible for connecting to database, handling connection open/close states, handling transaction flow etc. So main point is that class can not be used in web app at all. 

Simple example of what I am talking about.

Let's say we have a class that is called from win-app and web-app, and which should not (cannot) be changed.
   1:  public class User
   2:  {
   3:      private DataAccess  dataAccess = new dataAccess();
   4:      
   5:      public void Delete(int id)
   6:      {
   7:          dataAccess.Execute("spDeleteUser", new string[]{"@UserID"}, new object[]{id});
   8:      }
   9:  }
  10:   
  11:  And let's say we have DataAccess   class implemented like this:
  12:   
  13:  public class DataAccess
  14:  {
  15:      private static SqlConnection m_Connection = null;
  16:      
  17:      public void Execute(string spName, string[] parameterNames, object[] parameterValues)
  18:      {
  19:          // Here is some implementation
  20:      }
  21:  }

Searching for solution.

Ok, first I thought about simply changing the base data access class and adding to each method of it check block that checks if the method is called from HttpContext, and if so, execute web-specific piece of code, or otherwise win-specific. 
It works, but it leads to hell :) Class is not simply the set of methods, it has some encapsulated data, it has the sequence of execution. Connection in win-app is opened once, whereas in web it is opened and closed per each request. So I started to search more elegant solution, where I could alter entire class logic instead of altering methods, and where I could separate two platform-specific classes into separate files.

So what I did.

I created the following classes (naming conventions I used is taken from providers naming, although I am not using Provider Model, it's very similar approach)

DataAccessProvider .cs:
   1:   
   2:  public abstract class DataAccessProvider 
   3:  {
   4:      public abstract void Execute(string spName, string[] parameterNames, object[] parameterValues);
   5:  }

WindowsDataAccessProvider.cs:
   1:  public abstract class WindowsDataAccessProvider :DataAccessProvider 
   2:  {
   3:      private static SqlConnection m_Connection = null;
   4:   
   5:      public override void Execute(string spName, string[] parameterNames, object[] parameterValues)
   6:      {
   7:          // Windows-forms specific code, if static connection is already opened
   8:          // use it, don’t close connection until application termination
   9:      }
  10:  }
WebDataAccessProvider.cs:
   1:  public abstract class WebDataAccessProvider :DataAccessProvider 
   2:  {
   3:      private SqlConnection m_Connection = null;
   4:   
   5:      public override void Execute(string spName, string[] parameterNames, object[] parameterValues)
   6:      {
   7:          // Web-specific code, open instance of private connection
   8:          // use it, close it after execution
   9:      }
  10:  }
DataAccess.cs:
   1:  public class DataAccess
   2:  {
   3:      private DataAccessProvider _provider;
   4:      
   5:      public DataAccess()
   6:      {
   7:          if(HttpContext.Current==null) // we have code called from Windows app
   8:          {
   9:              _provider = new WindowsDataAccessProvider();
  10:          }
  11:          else // we have code called from Web app
  12:          {
  13:              _provider = new WebDataAccessProvider();
  14:          }
  15:      }
  16:      
  17:      public void Execute(string spName, string[] parameterNames, object[] parameterValues)
  18:      {
  19:          _provider.Execute(spName,  parameterNames, parameterValues);
  20:      }
  21:  }
Note: 
if DataAccess class is inherited from some another class, we should redirect all public methods of parent class to provider as well. This can be a limitation to that model if we have no access to parent class's code.

Overall it worked great in my case. I separated logic in 5 core classes that needed to be changed. Separation is great, all the logic is split into different files, and real implementation of specific classes  is very different for different platforms



Ok and one more note. If we need to load different implementation at run time, depending on configuration information for example, its very simple to change classes I demonstrated to work like that. We just need to inherit DataAccessProvider class from ProviderBase, and add Initialise method's override to WindowsDataAccessProvider and WebDataAccessProvider classes. Then instead of checking HttpContext.Current in constructor of DataAccess class to load provider we just need to use ProvidersHelper.InstantiateProviders method.

No comments:

Post a Comment