In general, it is more desirable to write new code in an existing codebase than it is to change existing code. This is especially true if you are unfamiliar with the codebase or it has little to no test coverage to facilitate ease of refactoring. Modifying an existing codebase increases the risk of breaking existing functionality and adds to the testing time as now we must regression test all parts of the system that have been affected by our change.
Wouldn’t it be nice if there were some way for us to modify existing functionality by writing new code, and leaving the existing codebase untouched (ok, relatively untouched)? Enter the Open/Closed Principle (OCP). The according to Hoyle definition states "software entities should be open for extension, but closed for modification". Sweet! But what does that mean?
It means we want to design our applications in such a way that allows us to add new functionality or behavior without having to modify existing code. This help us to reduce code fragility, where a single change causes a ripple throughout other areas of our application, forcing us to modify larger portions of existing code. Again, this increases our testing time and reduces the overall maintainability of the code.
So let’s look at a very simple example of how adhering to the Open/Closed Principle can help improve the value and quality of our code (I want to stay focused here on the OCP concept and not get bogged down in the fact that there are other design issues with this code, i.e., ignoring the Single Responsibility Principle) . Here is the ubiquitous person class:
1: public class Person2: {
3: public string FirstName { get; set; }
4: public string LastName { get; set; }
5:
6: public bool IsValid
7: {
8: get
9: {
10: return (!string.IsNullOrEmpty(FirstName) &&
11: FirstName.Length <= 30);
12: }
13: }
We have a person class with two properties and one validation rule: the person must have a first name and it cannot be more than thirty characters. This works perfectly until the business rules change. Now we must add a new validation rule for the last name as well.
In order for us to change this code and apply the new business rules, we can either continue updating the IsValid property or take the time to update to code to fix this issue such that it will not burn us again.
First we will refactor our person class and introduce a validator class:
1: public class Person
2: {
3: private IValidator validator;
4:
5: public Person(IValidator validator)
6: {
7: this.validator = validator;
8: }
9:
10: public string FirstName { get; set; }
11: public string LastName { get; set; }
12:
13: public bool IsValid
14: {
15: get
16: {
17: return validator.IsValid(this);
18: }
19: }
20: }
1: public interface IValidator
2: {
3: bool IsValid(Person person);
4: }
5:
6: public class PersonFirstNameValidator : IValidator
7: {
8: public bool IsValid(Person person)
9: {
10: return (!string.IsNullOrEmpty(person.FirstName)
11: &&
12: person.FirstName.Length <= 30);
13: }
14: }
And here is the validator that picks up the new business rules:
1: public class PersonFirstAndLastNameValidator : IValidator
2: {
3: public bool IsValid(Person person)
4: {
5: return (IsPropertyValid(person.FirstName) &&
6: IsPropertyValid(person.LastName));
7: }
8:
9: private bool IsPropertyValid(string value)
10: {
11: return (!string.IsNullOrEmpty(value)
12: &&
13: value.Length <= 30);
14: }
15: }