A few months ago I blogged about how we're using the single responsibility principle in our application at work. I'm going to make an effort here to write more "Real Life" content. Often when a developer read principles or ideas in books, there is disconnect between what he/she is reading and how an implementation would look in the real world. The goal of these "Real Life" scenarios is to show areas where I feel I've reaped some benefit from following a specific pattern or practice. This does not mean that my implementation is the only way or always "book correct". The goal is to act as a bridge (quite possibly a old rickety bridge) between the book knowledge and the real world.
Moving along...
One of the purported benefits of OOP has always been the abundant reuse of code. However, in my experience many applications fail to realize any amount of reuse beyond copying and pasting between applications. To really take advantage of code reuse you have to have small, concise pieces which you can rearrange in order to make a new behavior. As a side note, and I want this be clear, many of the principles of good software development, ie. SOLID, go hand-in-hand. You can't do one without the other and the benefits of one cannot be fully realized without the implementation of the others. You'll notice that a few sentences ago I said "small, concise pieces". A light bulb may have gone off in your head thinking "Single Responsibility Principle", and if it did, bonus points for you.
The key to code reuse is to build your components in a very focused way. By doing so, you can add functionality by combining different pieces of you application.
Consider the following code used to authenticate a user into a site:
1: protected void btnSubmit_Click(object sender, EventArgs e)
2: {
3: string Username = Server.HtmlEncode(txtUsername.Text);
4: string Password = Server.HtmlEncode(txtPassword.Text);
5: int nUserID;
6:
7: if (Username != "" && Password != "")
8: {
9: using (SqlConnection cnn = new SqlConnection(connectionString))
10: {
11: using (SqlCommand cmd = cnn.CreateCommand())
12: {
13: cmd.CommandText = "usp_Login";
14: cmd.Parameters.AddWithValue("@Username", Username);
15: cmd.Parameters.AddWithValue("@password", password);
16:
17: using(SqlDataReader reader = cmd.ExecuteReader())
18: {
19: if (reader.Read())
20: {
21: nUserId = Convert.ToInt32(reader[0]);
22: }
23: }
24: }
25: }
26:
27: if (nUserID > 0)
28: {
29: // if the user has checked the box
30: // store his information so he doesn't have to log in again
31: if (cbRememberMe.Checked)
32: FormsAuthentication.SetAuthCookie(nUserID.ToString(), true);
33: else
34: FormsAuthentication.SetAuthCookie(nUserID.ToString(), false);
35:
36: BusinessLogicLayer.User objUser = new User(nUserID);
37:
38: // store session variables for later reference;
39: Session["UserID"] = nUserID;
40: Session["Username"] = objUser.Username;
41: Session["Password"] = objUser.Password;
42: Session["AccessLevel"] = objUser.AccessLevel;
43: Session["AccessLevelName"] = Enum.GetName(typeof(Utilities.AccessLevel),objUser.AccessLevel);
44:
45: Response.Redirect("~/admin/admin.aspx");
46: }
47: else
48: {
49: View = Views.Failed;
50: }
51: }
52: }
While the code above is functional, it does way too much. In this button click event there are:
- Call to database explicitly
- logic to set an authorization cookie
- Some session variables being set
Imagine for a moment that you now want to implement a new piece of functionality, user impersonation. In order to accomplish this with the path of least resistance, which developers commonly take, would be to copy/paste all of the login logic to another UI event somewhere else in the site. The issue copy/paste development is that any future change to the login logic has to be copied again or else the impersonation feature isn't really impersonating a users experience. The insidious nature of copy/paste development is that while often it works, it creates a brittle application.
In a recent application we were wanting to add user impersonation. Below is an example of an implementation of user impersonation. Adding the feature to the application was gleefully simple. The ease in adding impersonation was due entirely to the proper granularity in our application components. Seriously, this is all the code it took:
1: public ActionResult ImpersonateUser(int id)
2: {
3: var authService = Container.Resolve<AuthService>();
4: var userRepository = Container.Resolve<UserRepository>();
5:
6: var userToImpersonate = userRepository.GetById(id);
7: if (userToImpersonate != null)
8: {
9: authService.SignIn(userToImpersonate);
10: }
11:
12: return RedirectToAction("Index", "Home");
13: }
I did not know we were going to want user impersonation for this application when we started. However because we developed the site with certain principles in mind, we were able to reuse pieces of code in ways not originally intended for added behavior. As you can see from the code above, there are no "clever hacks" or tricks to get impersonation to work. The beauty of the code above is that if we change the implementation of "SignIn(user)" all appropriate areas will follow the same rules since all areas use the same code.
As I'm closing I want to challenge those of you who have read this far to stop yourself the next time you find yourself copying code from one location to another. Is there an abstraction or component you're overlooking? Think about the long term and ask if you're really making the wisest decision for the long term health of your application. It will be through this introspection and honesty that you'll find areas where you can write smaller component which will allow you to realize the oft spoken benefit in OOP of code reuse.
Posted
03-09-2009 1:37 PM
by
Tim Barcz