Here, we will talk about code bits that are not 100% identical but follow the same pattern and can clearly be factored out.
Let us continue with our discussion on DRY. We discussed the below two issues in our previous tutorials.
Let's take a look at one example.
- public class Issue {
- public static void DoSomething() {
- WriteToConsole("Araham", "a good friend", 30);
- }
- public static void DoSomethingAgain() {
- WriteToConsole("Pramod", "a neighbour", 54);
- }
- public static void DoSomethingMore() {
- WriteToConsole("Praseedha", "my sister", 45);
- }
- public static void DoSomethingExtraordinary() {
- WriteToConsole("Nayana", "my daughter's best friend", 4);
- }
- public static void WriteToConsole(string name, string description, int age) {
- Console.WriteLine(string.Format("{0}, {1}, age - {2} ", name, description, age));
- }
- }
- class Program {
- static void Main(string[] args) {
- Console.WriteLine("About to run the DoSomething method");
- Issue.DoSomething();
- Console.WriteLine("Finished running the DoSomething method");
- Console.WriteLine("About to run the DoSomethingAgain method");
- Issue.DoSomethingAgain();
- Console.WriteLine("Finished running the DoSomethingAgain method");
- Console.WriteLine("About to run the DoSomethingMore method");
- Issue.DoSomethingMore();
- Console.WriteLine("Finished running the DoSomethingMore method");
- Console.WriteLine("About to run the DoSomethingExtraordinary method");
- Issue.DoSomethingExtraordinary();
- Console.WriteLine("Finished running the DoSomethingExtraordinary method");
- Console.ReadLine();
- }
- }
We’re simulating a simple logging function every time we run one of these “DoSomething” methods.
The pattern is clear
- Write a message to the console, carry out an action, and write another message to the console.
- The actions have an identical void, parameter-less signature.
- The logging message all have the same format, it’s only the method name that varies.
- If this chain of actions continues to grow then we have to come back here and add the same type of logging messages.
- Also, if you later wish to change the logging message format then you’ll have to do it in many different places
Lets us see how we can refactor the above code.
The first step is to factor out a single console-action-console chunk to its own method.
- private static void ExecuteStep() {
- Console.WriteLine("About to run the DoSomething method");
- Issue.DoSomething();
- Console.WriteLine("Finished running the DoSomething method");
- }
This is, of course, not good enough as the method is very rigid. It is hard coded to execute the first step only.
We can vary the action to be executed using the Action object,
- private static void ExecuteStep(Action action) {
- Console.WriteLine("About to run the DoSomething method");
- action();
- Console.WriteLine("Finished running the DoSomething method");
- }
We can call this method as follows,
- class Program {
- static void Main(string[] args) {
- ExecuteStep(Issue.DoSomething);
- ExecuteStep(Issue.DoSomethingAgain);
- ExecuteStep(Issue.DoSomethingMore);
- ExecuteStep(Issue.DoSomethingExtraordinary);
- Console.ReadLine();
- }
- private static void ExecuteStep(Action action) {
- Console.WriteLine("About to run the DoSomething method");
- action();
- Console.WriteLine("Finished running the DoSomething method");
- }
- }
Except that we’re not logging the method names correctly. That’s still hard coded to “DoSomething”.
That’s easy to fix as the Action object has public properties to read off the method name,
- private static void ExecuteStep(Action action) {
- string methodName = action.Method.Name;
- Console.WriteLine("About to run the {0} method", methodName);
- action();
- Console.WriteLine("Finished running the {0} method", methodName);
- }
We’re almost done,
- If you look at the Main method then the ExecuteStep (somemethod) is called 4 times. That is also a form of DRY-violation.
- Imagine that you have a long workflow, such as the steps in a chemical experiment. In that case, you may need to repeat the call to ExecuteStep many times.
We can instead put the methods to be executed in a collection of actions,
- private static IEnumerable < Action > GetExecutionSteps() {
- return new List < Action > () {
- Issue.DoSomething, Issue.DoSomethingAgain, Issue.DoSomethingExtraordinary, Issue.DoSomethingMore
- };
- }
You can use this from within Main as follows,
- class Program {
- static void Main(string[] args) {
- var actions = GetExecutionSteps();
- actions.ToList().ForEach(action => {
- ExecuteStep(action);
- });
- Console.ReadLine();
- }
- private static void ExecuteStep(Action action) {
- string methodName = action.Method.Name;
- Console.WriteLine("About to run the {0} method", methodName);
- action();
- Console.WriteLine("Finished running the {0} method", methodName);
- }
- private static IEnumerable < Action > GetExecutionSteps() {
- return new List < Action > () {
- Issue.DoSomething, Issue.DoSomethingAgain, Issue.DoSomethingExtraordinary, Issue.DoSomethingMore
- };
- }
- }
Now, it’s not the responsibility of the Main method to define the steps to be executed. It only iterates through a loop and calls ExecuteStep for each action.
Thanks for watching the tutorials. I appreciate your feedback.