The open/closed principle (OCP) is another object oriented principle that simple states:

Classes should be open for extension but closed for modification.

In my last post we fixed the SRP violation, but we left a violation of the open/closed principle. The reason this principle is important is because it helps us design components/classes so that the need for change in the future is reduced. When building software systems it is much safer to introduce new components rather than altering existing ones. Each time we change the behavior of an existing component, we increase the possibility of error in existing classes that depend on that classes behavior.

The code we left off with was this:

 1 public class DatabaseGateway
 2     {
 3       IConnectionFactory connectionFactory;
 4       IMapper<IDataReader, IEnumerable<DataRow>> mapper;
 5 
 6       public DatabaseGateway(IConnectionFactory connectionFactory, IMapper<IDataReader, IEnumerable<DataRow>> mapper)
 7       {
 8         this.connectionFactory = connectionFactory;
 9         this.mapper = mapper;
10       }
11 
12       public IEnumerable<DataRow> execute(string sql)
13       {
14         using( var connection = connectionFactory.OpenConnection())
15         {
16           var command = connection.CreateCommand();
17           command.CommandText = sql;
18           command.CommandType = CommandType.Text;
19           return mapper.MapFrom(command.ExecuteReader());
20         }
21       }
22     }

Each time we need to change how a query is executed against the database, we need to modify the DatabaseGateway. What we would like is to avoid having to modify DatabaseGateway each time we need to query against the database in a different way. (e.g. if we wanted to execute a stored procedure instead of raw SQL.)

We can do this by introducing the strategy pattern. > The strategy pattern allows us to change algorithms at runtime.

Instead of handing the “execute” method a raw SQL string, we’re going to pass in a query object.

 1 public class DatabaseGateway
 2     {
 3       IConnectionFactory connectionFactory;
 4 
 5       public DatabaseGateway(IConnectionFactory connectionFactory)
 6       {
 7         this.connectionFactory = connectionFactory;
 8       }
 9 
10       public void execute(IQuery query)
11       {
12         using( var connection = connectionFactory.OpenConnection())
13         {
14           query.execute_using(connection.CreateCommand());
15         }
16       }
17     }

We’re now able to create different implementations of IQuery that can each run against the IDbCommand interface. By inverting control to an object that is handed to us we are able to add new types of queries to be run against the database in the future without the need to modify the DatabaseGateway.

 1 public interface IQuery
 2 	{
 3 		void execute_using(IDbCommand command);
 4 	}
 5 
 6     public class RawSQLQuery : IQuery
 7     {
 8       string sql;
 9       public RawSQLQuery(string sql)
10       {
11         this.sql = sql;
12         Result = new DataTable();
13       }
14 
15       public DataTable Result{ get; private set; }
16 
17       public void execute_using(IDbCommand command)
18       {
19           command.CommandText = sql;
20           command.CommandType = CommandType.Text;
21           Result.Load(command.ExecuteReader());
22       }
23     }
24 
25     public class RawSQLCommand : IQuery
26     {
27       string sql;
28       public RawSQLQuery(string sql)
29       {
30         this.sql = sql;
31         Result = new DataTable();
32       }
33 
34       public DataTable Result{ get; private set; }
35 
36       public void execute_using(IDbCommand command)
37       {
38           command.CommandText = sql;
39           command.CommandType = CommandType.Text;
40           command.ExecuteNonQuery();
41       }
42     }

The query objects are our different Strategy object that we can pass to the DatabaseGateway to execute commands against the database based on the client components needs.

comments powered by Disqus