One Of The Methodologies To Write Clean, Maintainable & Extensible Software

Posted by Ahmed Tarek Hasan on 10/31/2012 12:41:00 AM with 4 comments
One of the most important goals when writing a software is to make your code:
  1. Clean
  2. Maintainable: easy to be maintained, debugged and fixed when needed
  3. Extensible: easy to be extended and added to when new requirements are introduced

So, the question now is how to achieve this in the most appropriate way without tending to increase code complexity and make it very hard to read and understand. Some may think that this is a quite easy task to be done and it doesn't need a hassle, if you are one of these, hang on for a while.

Don't get me wrong, I don't mean that it is an impossible goal to achieve or a so complex task to be carried out, all what I mean is that as far as you understand the main concept behind each aspect, as easy you will find it. So, don't just imitate, try to understand and get it well.

To imagine what we are talking about here, let's suppose the following:
  1. You are required to write a software, let's say a windows forms application
  2. With very simple user interface
  3. The client should be able to use this application to carry out two tasks:
    1. Get the details (ID, Name, Age) of an employee by searching for him by ID
    2. Get the details (ID, Name, Age) of a list of employees by searching for them by Name
  4. So, far it is so simple, right?
  5. But, there is a main requirement, the system should be able to get these employees from SQL & Oracle databases one at each time
  6. Also, the system user should be able to switch between databases at run-time through user interface
  7. Also, it may be requested by client that the system supports an extra new database at any day and this feature should take as minimum cost (time and money) as possible
So, how can we achieve this by the easiest way keeping in mind the three main goals (clean, maintainable & extensible). This is what I am going to try to explain as simple as I can so please keep reading if you are interested.

To do so, we will try to develop the software described above but I will keep secondary things as simple as possible so that the main focus will be on the design concept and so that we are not confused by minor things.

I will break down the code into small blocks to make it easy to understand and spare our minds for the big bang parts :)

So, let's start ..........


Analysis:
  1. We will write our application in a 3-tier architecture
  2. The UI/Presentation layer will be called "MainProgram" as we will merge it with the driver application for simplicity
  3. The Business Logic layer will be called "BLL" and it will be presented by a class called "BusinessLogicLayerManager" which is a manager class including all business related methods and implementations. This class is independent on the database type and it is oriented to the system business needs. It doesn't care if the main data storage back-end is SQL or Oracle database or even XML
  4. The Data Access layer will be called "DAL". It should be presented by a manager class which includes all related methods and implementations for retrieving data from databases. This class is dependent on the main data storage back-end type as it is the one responsible for actual retrieving of data from this back-end. So, if the back-end is XML, the class should include some code to get employees from an XML file by parsing nodes and so on. On the other hand, if the back-end is SQL database, then this class should include some code to get employees from a SQL database by executing some SQL stored procedures, functions, routines and so on......
  5. But, we know that according to the requirements our application should deal with more than one back-end type, as a start, it should deal with SQL and Oracle databases. Also, the application should be able to switch between them at run-time. How????!!!!! Let's wait till we reach this point
  6. The system deals with only one entity which is "Employee" but we will add another one which is a collection of employees and it will be called "EmployeeCollection"
  7. Also, we will need some helping classes to be shared between all layers, so to avoid circular references we will isolate these classes in a separate project called "CommonEntities"
  8. So, let's start with the "CommonEntities" project which is a class library project including a class with this code inside
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using System.Collections.ObjectModel;  
       
     namespace CommonEntities  
     {  
       public class Employee  
       {  
         int id;  
         public int Id  
         {  
           get { return id; }  
           set { id = value; }  
         }  
       
         string name;  
         public string Name  
         {  
           get { return name; }  
           set { name = value; }  
         }  
       
         int age;  
         public int Age  
         {  
           get { return age; }  
           set { age = value; }  
         }  
       }  
       
       public class EmployeeCollection : Collection<Employee>  
       {  
       }  
     }  
    
  9.  As you see the "Employee" class is so simple and the "EmployeeCollection" class is just an inherited class from Collection<Employee>
  10. Since we already know that the application should be able to switch between SQL and Oracle databases, then at some point we will need to choose from UI one of these two database types
  11. So, we will need to retrieve the user input in an appropriate way and also encapsulate it in a strong typed way, so I thought we will need an "enum" with both choices. This enum is called "DALManagerType", don't get stuck at the name right now, we will get to it soon and you will understand why I named it so
  12. So, we need to have another cs file including this enum, the code is as follows
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
       
     namespace CommonEntities.Enums  
     {  
       public enum DALManagerType  
       {  
         SQL = 0,  
         Oracle = 1  
       }  
     }  
    
  13. Now, according to the requirements, the application should be able to:
    1. Get the details (ID, Name, Age) of an employee by searching for him by ID
    2. Get the details (ID, Name, Age) of a list of employees by searching for them by Name
  14. So, in order to achieve that, we need our business logic layer to include two methods:
    1. A method which takes the id of an employee as an input parameter and returns this employee
    2. A method which takes a name as an input parameter and returns all employees which have this name
  15. The implementation of these two methods should be oriented to business and not related by any means to the back-end type. Actually, these two methods should be calling the corresponding ones in the data access layer and apply some few business manipulations on the results if needed, nothing less, nothing more
  16. So, to achieve this, we will create a new class library project named "BLL" and add to it the methods as below
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using DAL;  
     using CommonEntities.BaseClasses;  
     using CommonEntities.Enums;  
     using DAL.Factories;  
     using CommonEntities;  
       
     namespace BLL  
     {  
       public class BusinessLogicLayerManager  
       {  
         public Employee GetEmployeeById(int employeeId)  
         {  
             
         }  
         public EmployeeCollection GetEmployeesByName(string employeeName)  
         {  
             
         }  
       }  
     }
    
  17.  Now, inside the two methods, we should call corresponding methods in the data access layer to actually get the employee(s) data. This leads us to the next point, the data access layer
  18. The data access layer should also include two methods to do the same job as the ones in the "BLL" but this time the methods should actually retrieve data from the back-end according to its type (SQL, Oracle, XML, .....)
  19. Here we have a problem, the system should support 2 types of back-ends (SQL & Oracle) at the same time with the ability to switch between them at run-time. We can do this by creating 2 classes for "DAL", one for SQL and the other for Oracle. Then, at run-time, when system user chooses from UI to use SQL, our "BLL" should use the SQL DAL class, and when he chooses to use Oracle, our "BLL" should use the Oracle DAL class
  20. So, at this point, our "BLL" could be as follows
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using DAL;  
     using CommonEntities.BaseClasses;  
     using CommonEntities.Enums;  
     using DAL.Factories;  
     using CommonEntities;  
       
     namespace BLL  
     {  
       public class BusinessLogicLayerManager  
       {  
         private DALManagerType dbType;  
       
         public BusinessLogicLayerManager(DALManagerType dALMngrType)  
         {  
           dbType = dALMngrType;  
         }  
         public Employee GetEmployeeById(int employeeId)  
         {  
           switch(dbType)  
           {  
             case DALManagerType.SQL  
               //use corresponding method in the SQL DAL class  
               break;  
             case DALManagerType.Oracle  
               //use corresponding method in the Oracle DAL class  
               break;  
           }  
         }  
         public EmployeeCollection GetEmployeesByName(string employeeName)  
         {  
           switch(dbType)  
           {  
             case DALManagerType.SQL  
               //use corresponding method in the SQL DAL class  
               break;  
             case DALManagerType.Oracle  
               //use corresponding method in the Oracle DAL class  
               break;  
           }  
         }  
       }  
     }  
    
  21.  So now, each time the system user switches the DB type, a new BLL class is initialized using the constructor which takes the DB type enum as an input parameter. Then, each time you try to use the BLL methods, they will check the DB type and correspondingly decide which DAL class to use
  22. This will work, but this is not the best way cause now the coupling between the BLL and DAL is very very strict. This is because:
    1.  You have to check for the DB type everywhere inside the BLL which is not logical as it should be independent on the DB type
    2. Any tiny change in the DAL classes structure will need a corresponding change in the BLL logic to match
    3. Any added extra DB type will require changes in the BLL to support this new DB type
    4. The code will be unstable due to duplicate logic all around the BLL (like the switch-case block)
  23. For these reasons and more, we should somehow reduce the coupling between the BLL and DAL as far as we can
  24. Also, the logic says that the BLL should not be dependent on the DB type and it should only be oriented to the business requirements. This doesn't mean that the BLL should deal with only one type of DAL, but the way it interacts with many DAL types should be the same
  25. Does this ring any bells? Do you feel you already know or encountered this case when a module (class) only cares about what other module (class) offers not the way it offers it? If you knew that the answer is "Interfaces", you can go on, if not, I recommend you read Interfaces - The Concept first to be able to fully understand the coming part
  26. So now I assume you already know the main concept behind interfaces but let's check why interfaces fits in our case:
    1. The BLL class expects all DAL classes to provide 2 methods with the header definitions we already know
    2. So, BLL wants to guarantee that all DAL classes will obey this requirement and accordingly, the BLL will be able to deal with all DAL classes blindly without any need to know their exact definitions (implementations)
    3. This way, we will be preferring "abstraction" to "implementation"
    4. So, the best way to do this is to define an interface which includes the headers of the 2 required methods and all DAL classes should implement this interface
    5. Now, the BLL should only work with any class implementing this interface and the compiler will guarantee the rest, no room for errors or misunderstanding
  27.  So, to implement this part using interfaces, we first need to create the interface, so in the "CommonEntities" class library project, we will add a cs file with this code inside
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
       
     namespace CommonEntities.Interfaces  
     {  
       public interface IDALManager  
       {  
         Employee GetEmployeeById(int employeeId);  
         EmployeeCollection GetEmployeesByName(string employeeName);  
       }  
     }  
    
  28. Now, we are ready to work on our DAL classes, but first, lets discuss something. We already said that each of our DAL classes should implement this interface and the BLL will deal with instances from this interface as a substitute for the real DAL classes instances. This is good but I think we can tune it up a bit
  29. I think it will be more appropriate to introduce a layer in-between the interface and each DAL class. This layer will be an abstract class
  30. Why will we do this? we will do this because the in-between abstract class will provide us with the ability to add common implementation between all DAL classes if we need to do so. May be in our case we don't need it cause the DAL classes are so simple with only 2 methods which can not by any way share common implementations, but I am just pointing out a note which could be useful in other cases
  31. So, following the in-between layer approach:
    1. We will have an abstract class
    2. This abstract class (not the DAL classes) implements the interface
    3. Each DAL class will inherit from this abstract class (instead of the interface)
    4. The BLL class will operate using definition of the abstract class (not the interface)
  32.  So, in the "CommonEntities" class library project, we will add a cs file with this code inside
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using CommonEntities.Interfaces;  
       
     namespace CommonEntities.BaseClasses  
     {  
       public abstract class DALManagerBase : IDALManager  
       {  
         public abstract Employee GetEmployeeById(int employeeId);  
         public abstract EmployeeCollection GetEmployeesByName(string employeeName);  
       }  
     }  
    
  33. Note that we marked the 2 methods in the abstract class as "abstract" cause we don't need to write any implementation right now cause there is no common implementation between child DAL classes for these two methods and each DAL class should decide its own implementations
  34. So, now we have our abstract class which all DAL classes should inherit and the BLL class should use to reference the appropriate DAL class
  35. Now we have to update the BLL class to use the new approach (abstract DAL class) rather than using the old one (concrete DAL classes). So, the code of the BLL will be as follows
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using DAL;  
     using CommonEntities.BaseClasses;  
     using CommonEntities.Enums;  
     using DAL.Factories;  
     using CommonEntities;  
       
     namespace BLL  
     {  
       public class BusinessLogicLayerManager  
       {  
         private DALManagerBase dALMngr;  
       
         public BusinessLogicLayerManager(DALManagerType dALMngrType)  
         {  
           switch (dALMngrType)  
           {  
             case DALManagerType.SQL:  
               dALMngr = new SQLDataAccessLayerManager();  
               break;  
             case DALManagerType.Oracle:  
               dALMngr = new OracleDataAccessLayerManager();  
               break;  
           }  
         }  
         public Employee GetEmployeeById(int employeeId)  
         {  
           return dALMngr.GetEmployeeById(employeeId);  
         }  
         public EmployeeCollection GetEmployeesByName(string employeeName)  
         {  
           return dALMngr.GetEmployeesByName(employeeName);  
         }  
       }  
     }  
    
  36. Now, the DAL implementation can be switched at run-time depending on the type of the DB used without too much coupling between the BLL and DAL except for the "switch-case" block. This is good, but believe  it or not, it could get better
  37. We can also get rid of the switch-case block inside the BLL by moving it to a separate class which will be responsible for returning an appropriate DAL class corresponding to the DB type chosen at run-time
  38. So, to do this, we will create a class in the DAL class library project which has only one method. This method takes the DB type enum and returns a corresponding DAL class to be passed to the BLL
  39. So, add a new cs file to the "DAL" class library project with the code below
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using CommonEntities.BaseClasses;  
     using CommonEntities.Enums;  
       
     namespace DAL.Factories  
     {  
       public static class DataAccessLayerManagerFactory  
       {  
         public static DALManagerBase GetDALManager(DALManagerType dALManagerType)  
         {  
           DALManagerBase result = null;  
       
           switch (dALManagerType)  
           {  
             case DALManagerType.SQL:  
               result = new SQLDataAccessLayerManager();  
               break;  
             case DALManagerType.Oracle:  
               result = new OracleDataAccessLayerManager();  
               break;  
           }  
       
           return result;  
         }  
       }  
     }  
    
  40. This factory class is a static class with one static method cause we don't need to create an instance of it. Also, note that the return type of the method is the abstract DAL class not the interface or any of the concrete DAL classes. This is logic cause this method will be called from BLL which expects an instance of the abstract DAL class, not an interface or any concrete class
  41. So, now, our BLL will be modified as follows
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using DAL;  
     using CommonEntities.BaseClasses;  
     using CommonEntities.Enums;  
     using DAL.Factories;  
     using CommonEntities;  
       
     namespace BLL  
     {  
       public class BusinessLogicLayerManager  
       {  
         private DALManagerBase dALMngr;  
       
         public BusinessLogicLayerManager(DALManagerType dALMngrType)  
         {  
           dALMngr = DataAccessLayerManagerFactory.GetDALManager(dALMngrType);  
         }  
         public Employee GetEmployeeById(int employeeId)  
         {  
           return dALMngr.GetEmployeeById(employeeId);  
         }  
         public EmployeeCollection GetEmployeesByName(string employeeName)  
         {  
           return dALMngr.GetEmployeesByName(employeeName);  
         }  
       }  
     }  
    
  42. As you can see, our BLL is now loosely coupled with DAL and doesn't depend on its implementation by any way. This enables us to apply any updates on DAL classes without the need to apply any updates on the BLL, but note that this statement is valid only when preserving the inheritance between the DAL classes and the abstract DAL class
  43. I have one note to add here, did you ask yourself why we needed to add the factory class in the DAL class library project? We did so cause we need the BLL class to reference this class (because we used it to call the method to get the appropriate  DAL class instance). Also, the factory class itself needed to reference the DAL classes (to be able to return the appropriate instance between these DAL classes). So, the best place to define this factory class with avoiding circular reference is in the DAL namespace itself
  44. Now, finally we are going to work on the DAL classes. We will create a class library project with the name "DAL"
  45. Then create a cs file with the code below
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using CommonEntities.BaseClasses;  
     using CommonEntities;  
       
     namespace DAL  
     {  
       public class SQLDataAccessLayerManager : DALManagerBase  
       {  
         public override Employee GetEmployeeById(int employeeId)  
         {  
           //should write here the actual implementation which  
           //gets an employee by id from SQL database  
       
           Employee dummyEmployee = new Employee();  
           dummyEmployee.Id = 1;  
           dummyEmployee.Name = "Ahmed from SQL";  
           dummyEmployee.Age = 27;  
       
           return dummyEmployee;  
         }  
         public override EmployeeCollection GetEmployeesByName(string employeeName)  
         {  
           //should write here the actual implementation which  
           //gets a collection of employees by name from SQL database  
       
           EmployeeCollection dummyEmployeeCollection = new EmployeeCollection();  
           dummyEmployeeCollection.Add(new Employee() { Id = 1, Name = "Ahmed 1 from SQL", Age = 27 });  
           dummyEmployeeCollection.Add(new Employee() { Id = 2, Name = "Ahmed 2 from SQL", Age = 27 });  
           dummyEmployeeCollection.Add(new Employee() { Id = 3, Name = "Ahmed 3 from SQL", Age = 27 });  
       
           return dummyEmployeeCollection;  
         }  
       }  
     }  
    
  46. Then create another cs file with the code below
     using System;  
     using System.Collections.Generic;  
     using System.Linq;  
     using System.Text;  
     using CommonEntities.BaseClasses;  
     using CommonEntities;  
       
     namespace DAL  
     {  
       public class OracleDataAccessLayerManager : DALManagerBase  
       {  
         public override Employee GetEmployeeById(int employeeId)  
         {  
           //should write here the actual implementation which  
           //gets an employee by id from Oracle database  
       
           Employee dummyEmployee = new Employee();  
           dummyEmployee.Id = 1;  
           dummyEmployee.Name = "Ahmed from Oracle";  
           dummyEmployee.Age = 27;  
       
           return dummyEmployee;  
         }  
         public override EmployeeCollection GetEmployeesByName(string employeeName)  
         {  
           //should write here the actual implementation which  
           //gets a collection of employees by name from Oracle database  
       
           EmployeeCollection dummyEmployeeCollection = new EmployeeCollection();  
           dummyEmployeeCollection.Add(new Employee() { Id = 1, Name = "Ahmed 1 from Oracle", Age = 27 });  
           dummyEmployeeCollection.Add(new Employee() { Id = 2, Name = "Ahmed 2 from Oracle", Age = 27 });  
           dummyEmployeeCollection.Add(new Employee() { Id = 3, Name = "Ahmed 3 from Oracle", Age = 27 });  
       
           return dummyEmployeeCollection;  
         }  
       }  
     }  
    
  47. Now, we have created two DAL classes, one for SQL DB and the other for Oracle DB. You will find that the implementation of the 2 methods in the 2 classes is dummy code which is not actually retrieving data from any databases, just returning some hard-coded employee and employee collection with the words SQL and Oracle in the details to distinguish between the source of data
  48. I meant to make the implementation stupid simple so that the full focus is on the concept and methodology rather than complex business implementation
  49. Now, we are going to build our driver application which will use our BLL to retrieve data and populate some UI controls
  50. Create a windows forms application project with the name "MainProgram"
  51. The code for the designer will be as follows
     namespace MainProgram  
     {  
       partial class Form1  
       {  
         /// <summary>  
         /// Required designer variable.  
         /// </summary>  
         private System.ComponentModel.IContainer components = null;  
       
         /// <summary>  
         /// Clean up any resources being used.  
         /// </summary>  
         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>  
         protected override void Dispose(bool disposing)  
         {  
           if (disposing && (components != null))  
           {  
             components.Dispose();  
           }  
           base.Dispose(disposing);  
         }  
       
         #region Windows Form Designer generated code  
       
         /// <summary>  
         /// Required method for Designer support - do not modify  
         /// the contents of this method with the code editor.  
         /// </summary>  
         private void InitializeComponent()  
         {  
           this.cmbDbType = new System.Windows.Forms.ComboBox();  
           this.btnGetEmployee = new System.Windows.Forms.Button();  
           this.label1 = new System.Windows.Forms.Label();  
           this.lblID = new System.Windows.Forms.Label();  
           this.lblName = new System.Windows.Forms.Label();  
           this.lblAge = new System.Windows.Forms.Label();  
           this.btnGetEmployees = new System.Windows.Forms.Button();  
           this.grdEmployees = new System.Windows.Forms.DataGridView();  
           ((System.ComponentModel.ISupportInitialize)(this.grdEmployees)).BeginInit();  
           this.SuspendLayout();  
           //   
           // cmbDbType  
           //   
           this.cmbDbType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;  
           this.cmbDbType.FormattingEnabled = true;  
           this.cmbDbType.Items.AddRange(new object[] {  
           "SQL",  
           "Oracle"});  
           this.cmbDbType.Location = new System.Drawing.Point(64, 20);  
           this.cmbDbType.Name = "cmbDbType";  
           this.cmbDbType.Size = new System.Drawing.Size(121, 21);  
           this.cmbDbType.TabIndex = 0;  
           this.cmbDbType.SelectedIndexChanged += new System.EventHandler(this.cmbDbType_SelectedIndexChanged);  
           //   
           // btnGetEmployee  
           //   
           this.btnGetEmployee.Location = new System.Drawing.Point(12, 62);  
           this.btnGetEmployee.Name = "btnGetEmployee";  
           this.btnGetEmployee.Size = new System.Drawing.Size(184, 23);  
           this.btnGetEmployee.TabIndex = 1;  
           this.btnGetEmployee.Text = "Get Employee By ID";  
           this.btnGetEmployee.UseVisualStyleBackColor = true;  
           this.btnGetEmployee.Click += new System.EventHandler(this.btnGetEmployee_Click);  
           //   
           // label1  
           //   
           this.label1.AutoSize = true;  
           this.label1.Location = new System.Drawing.Point(12, 23);  
           this.label1.Name = "label1";  
           this.label1.Size = new System.Drawing.Size(47, 13);  
           this.label1.TabIndex = 2;  
           this.label1.Text = "DB Type";  
           //   
           // lblID  
           //   
           this.lblID.AutoSize = true;  
           this.lblID.Location = new System.Drawing.Point(12, 101);  
           this.lblID.Name = "lblID";  
           this.lblID.Size = new System.Drawing.Size(22, 13);  
           this.lblID.TabIndex = 3;  
           this.lblID.Text = "ID:";  
           //   
           // lblName  
           //   
           this.lblName.AutoSize = true;  
           this.lblName.Location = new System.Drawing.Point(133, 101);  
           this.lblName.Name = "lblName";  
           this.lblName.Size = new System.Drawing.Size(38, 13);  
           this.lblName.TabIndex = 4;  
           this.lblName.Text = "Name:";  
           //   
           // lblAge  
           //   
           this.lblAge.AutoSize = true;  
           this.lblAge.Location = new System.Drawing.Point(281, 101);  
           this.lblAge.Name = "lblAge";  
           this.lblAge.Size = new System.Drawing.Size(30, 13);  
           this.lblAge.TabIndex = 5;  
           this.lblAge.Text = "Age:";  
           //   
           // btnGetEmployees  
           //   
           this.btnGetEmployees.Location = new System.Drawing.Point(15, 138);  
           this.btnGetEmployees.Name = "btnGetEmployees";  
           this.btnGetEmployees.Size = new System.Drawing.Size(184, 23);  
           this.btnGetEmployees.TabIndex = 6;  
           this.btnGetEmployees.Text = "Get Employees By Name";  
           this.btnGetEmployees.UseVisualStyleBackColor = true;  
           this.btnGetEmployees.Click += new System.EventHandler(this.btnGetEmployees_Click);  
           //   
           // grdEmployees  
           //   
           this.grdEmployees.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;  
           this.grdEmployees.Location = new System.Drawing.Point(15, 168);  
           this.grdEmployees.Name = "grdEmployees";  
           this.grdEmployees.Size = new System.Drawing.Size(354, 170);  
           this.grdEmployees.TabIndex = 7;  
           //   
           // Form1  
           //   
           this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);  
           this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;  
           this.ClientSize = new System.Drawing.Size(385, 350);  
           this.Controls.Add(this.grdEmployees);  
           this.Controls.Add(this.btnGetEmployees);  
           this.Controls.Add(this.lblAge);  
           this.Controls.Add(this.lblName);  
           this.Controls.Add(this.lblID);  
           this.Controls.Add(this.label1);  
           this.Controls.Add(this.btnGetEmployee);  
           this.Controls.Add(this.cmbDbType);  
           this.Name = "Form1";  
           this.Text = "Form1";  
           this.Load += new System.EventHandler(this.Form1_Load);  
           ((System.ComponentModel.ISupportInitialize)(this.grdEmployees)).EndInit();  
           this.ResumeLayout(false);  
           this.PerformLayout();  
       
         }  
       
         #endregion  
       
         private System.Windows.Forms.ComboBox cmbDbType;  
         private System.Windows.Forms.Button btnGetEmployee;  
         private System.Windows.Forms.Label label1;  
         private System.Windows.Forms.Label lblID;  
         private System.Windows.Forms.Label lblName;  
         private System.Windows.Forms.Label lblAge;  
         private System.Windows.Forms.Button btnGetEmployees;  
         private System.Windows.Forms.DataGridView grdEmployees;  
       }  
     }  
    
  52. The form code behind is as follows
     using System;  
     using System.Collections.Generic;  
     using System.ComponentModel;  
     using System.Data;  
     using System.Drawing;  
     using System.Linq;  
     using System.Text;  
     using System.Windows.Forms;  
     using BLL;  
     using CommonEntities;  
       
     namespace MainProgram  
     {  
       public partial class Form1 : Form  
       {  
         public Form1()  
         {  
           InitializeComponent();  
         }  
       
         private BusinessLogicLayerManager bLLMngr;  
       
         private void Form1_Load(object sender, EventArgs e)  
         {  
           cmbDbType.SelectedIndex = 0;  
           bLLMngr = new BusinessLogicLayerManager(CommonEntities.Enums.DALManagerType.SQL);  
         }  
       
         private void btnGetEmployee_Click(object sender, EventArgs e)  
         {  
           Employee emp = bLLMngr.GetEmployeeById(264);  
           lblID.Text = emp.Id.ToString();  
           lblName.Text = emp.Name;  
           lblAge.Text = emp.Age.ToString();  
         }  
       
         private void btnGetEmployees_Click(object sender, EventArgs e)  
         {  
           EmployeeCollection empColl = bLLMngr.GetEmployeesByName("dummy string");  
           grdEmployees.DataSource = empColl;  
         }  
       
         private void cmbDbType_SelectedIndexChanged(object sender, EventArgs e)  
         {  
           switch (cmbDbType.SelectedIndex)  
           {  
             case 0:  
               bLLMngr = new BusinessLogicLayerManager(CommonEntities.Enums.DALManagerType.SQL);  
               break;  
             case 1:  
               bLLMngr = new BusinessLogicLayerManager(CommonEntities.Enums.DALManagerType.Oracle);  
               break;  
           }  
         }  
       }  
     }  
    
  53. You will find the code so stupid simple. It just uses the BLL class passing to it the DB type which the system user chose from UI and you know the rest .........
  54. So, running the program you will get this
 

Notes:
  1. The design which we implemented in the form of (Interface <= Abstract Class <= Concrete Class) is actually a design pattern called "Bridge", so now smile :)
  2. The design of moving the logic responsible for returning DAL instances based on the DB type to a factory class is actually a design pattern called "Factory", so, smile again :)
  3. Using this technique is not only for DALs, it is used for many other cases where you need to have different implementations but reserving the main business line

Recommendations:
  1. Read more about "Bridge" and "Factory" design patterns
  2. Practice using this technique when needed and always avoid over design. It harms when you just apply what you know but not what you need
  3. Always try to keep it stupid simple as far as you can

The code for this demo can be found here


Finally, wish you find this post useful :)



Categories: , ,