About Me

Training

Nothin But .Net Developer Bootcamp

Navigation

Search

Categories

On this page

Refactoring Ahoy!!

Archive

Blogroll

 Agile Developer Venkat's Blog
 Ayende @ Blog
 B#
 Barry Gervin's Software Architecture Perspectives
 Boy Meets World
 Brad Abrams
 Canadian Developers
 Christopher Steen
 Claritude Software News
 Clemens Vasters: Enterprise Development and Alien Abductions
 Coding Horror
 Coding in an Igloo
 Dare Obasanjo aka Carnage4Life
 Darrell Norton's Blog [MVP]
 David Hayden [MVP C#]
 Don Box's Spoutlet
 Eric Gunnerson's C# Compendium
 EZWeb guy: Jeffrey Palermo [C# MVP]
 Fear and Loathing
 Generalities & Details: Adventures in the High-tech Underbelly
 Greg Young [MVP]
 Greg's Cool [Insert Clever Name] of the Day
 IanG on Tap
 Ingo Rammer's Weblog
 ISerializable - Roy Osherove's Blog
 James Kovacs' Weblog
 Jason Haley
 Jean-Luc David
 Jeremy D. Miller -- The Shade Tree Developer
 JetBrains .NET Tools Blog
 Jimmy Nilsson's weblog
 John Bristowe's Weblog
 John Papa [MVP C#]
 Jon Skeet's Coding Blog
 JonGalloway.ToString()
 Jump the Fence or Walk Around
 Lambda the Ultimate - Programming Languages Weblog
 Larkware News
 Lutz Roeder
 Marquee de Sells: Chris's insight outlet
 Martin Fowler's Bliki
 Mike Nichols - SonOfNun Technology
 MSDN Magazine - .NET Matters
 MSDN Magazine - All Articles
 OdeToCode Blogs
 Onion Blog
 Planet TW
 Raymond Lewallen [MVP]
 Rockford Lhotka
 RodMan's Corner
 Roger Johansson's blog
 Sahil Malik - blah.winsmarts.com
 Sam Gentile's Blog
 Scott Bellware [MVP]
 Scott Hanselman's Computer Zen
 ScottGu's Blog
 secretGeek
 Service Station, by Aaron Skonnard
 Signum sine tinnitu--by Guy Kawasaki
 Stephen Toub
 Steve Eichert's Blog
 Steven Rockarts
 The Blog Ride
 The Coding Hillbilly
 The Daily WTF
 TheServerSide.net: News
 Tim Gifford
 Vance Morrison's Weblog
 you've been HAACKED

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 477
This Year: 47
This Month: 2
This Week: 4
Comments: 1345

 Tuesday, October 10, 2006
Tuesday, October 10, 2006 2:55:48 PM (Mountain Standard Time, UTC-07:00) ( .Net 2.0 | C Sharp )

In response to Stevens' post on the Decompose Conditional refactoring, I thought I would offer up a quick alternative that I could share with others. Take a look at the code after Stevens original refactoring (I am making guesses as to the actual implementations, but it should convey pretty accurately the general idea of what is going on. I converted the code to C#, for my sanity!! 

public enum PaymentType { Cash, CreditCard } public interface IPaymentView { PaymentType PaymentType{get;} event EventHandler Save; decimal Amount{get;} void ShowMessage(string message,bool someFlag); void CloseView(); } public interface IPaymentTask { void SaveCashPayment(decimal amount); void SaveCreditCardPayment(decimal amount); } public class PaymentPresenter { private IPaymentTask task; private IPaymentView view; public PaymentPresenter(IPaymentView view,IPaymentTask task) { this.task = task; this.view = view; HookupEventHandlersTo(view); } private void HookupEventHandlersTo(IPaymentView view) { view.Save+=delegate{ ProcessPayment(); }; } public void ProcessPayment() { if (PaymentTypeIsCash()) { SaveCashPayment(); } else { SaveCreditCardPayment(); } } private bool PaymentTypeIsCash() { return view.PaymentType == PaymentType.Cash; } private void SaveCashPayment() { try { task.SaveCashPayment(view.Amount); } catch (Exception) { view.ShowMessage("There was a problem saving the payment.",false); } view.CloseView(); } private void SaveCreditCardPayment() { try { task.SaveCreditCardPayment(view.Amount); } catch (Exception) { view.ShowMessage("There was a problem saving the payment.",false); } view.CloseView(); } }

By utilizing the Decompose Conditional refactoring, he was able to make the code in the ProcessPayment method a bit more readable. Steven asked for suggestions to help further refactor the code, and I am going to offer this one tidbit. Anytime I am switching on some data and then performing some processing based on the data, it screams like a good candidate for the "Replace Conditional with Strategy" refactoring. However, I am not going to go crazy. I will offer up a similar solution, that does not require the introduction of a whole strategy hierarchy.

The goal is to eliminate the conditional completely (and also, offer up a good migration path, in the event other payment types come into play). The key to this solution is to take advantage of delegates, and the PaymentType itself. One thing we can take advantage of in this scenario is the fact that the methods to process a payment have identical signatures. Let's create a delegate that can be used to invoke both of these methods:

 

private delegate void PaymentProcessor();

 

Armed with that signature we can now do the following:

 

public class PaymentPresenter { private IPaymentTask task; private IPaymentView view; private delegate void PaymentProcessor(); private IDictionary<PaymentType,PaymentProcessor> paymentProcessors; public PaymentPresenter(IPaymentView view,IPaymentTask task) { this.task = task; this.view = view; InitializePaymentProcessors(); HookupEventHandlersTo(view); } private void InitializePaymentProcessors() { paymentProcessors = new Dictionary<PaymentType,PaymentProcessor>(); paymentProcessors.Add(PaymentType.Cash,SaveCashPayment); paymentProcessors.Add(PaymentType.CreditCard,SaveCreditCardPayment); } private void HookupEventHandlersTo(IPaymentView view) { view.Save+=delegate{ ProcessPayment(); }; } public void ProcessPayment() { paymentProcessors[view.PaymentType](); } // public void ProcessPayment() // { // if (PaymentTypeIsCash()) // { // SaveCashPayment(); // } // else // { // SaveCreditCardPayment(); // } // } // private bool PaymentTypeIsCash() // { // return view.PaymentType == PaymentType.Cash; // } private void SaveCashPayment() { try { task.SaveCashPayment(view.Amount); } catch (Exception) { view.ShowMessage("There was a problem saving the payment.",false); } view.CloseView(); } private void SaveCreditCardPayment() { try { task.SaveCreditCardPayment(view.Amount); } catch (Exception) { view.ShowMessage("There was a problem saving the payment.",false); } view.CloseView(); } }

By taking advantage of the dictionary we have removed the need for both the if statement in the ProcessPayment method, as well as the method that determines whether a payment type is cash. Each payment type will be initialized to be processed by a particular process method. If a new payment type is added, it is as simple as adding a new method for processing the payment type, and an accompanying line to the InitializePaymentProcessors method, no change would be required to the ProcessPayment method.

For one last refactoring, let's eliminate the duplication that is happening in both of the Save methods. Both methods try to call the appropriate method on the service layer, if they fail the presenter tells the view to display a message, otherwise they tell the view to close. We can consolidate that logic into the ProcessPayment method as follows:

 

public void ProcessPayment() { try { paymentProcessors[view.PaymentType](); } catch (Exception) { view.ShowMessage("There was a problem saving the payment.",false); } view.CloseView(); }

With that change in place our respective save methods become one liners:

 

private void SaveCashPayment() { task.SaveCashPayment(view.Amount); } private void SaveCreditCardPayment() { task.SaveCreditCardPayment(view.Amount); }

 

Once those methods are collapsed as such, you can also inline the method calls themselves, and eliminate the methods that are just wrapping calls to the underlying service layer. To do that we need to change the signature of our delegate to a signature that matches the methods that will get called on the underlying service layer. The final result of this refactoring results in a much tighter presenter:

 

public class PaymentPresenter { private IPaymentTask task; private IPaymentView view; private IDictionary<PaymentType,PaymentProcessor> paymentProcessors; private delegate void PaymentProcessor(decimal amount); public PaymentPresenter(IPaymentView view,IPaymentTask task) { this.task = task; this.view = view; InitializePaymentProcessors(); HookupEventHandlersTo(view); } private void InitializePaymentProcessors() { paymentProcessors = new Dictionary<PaymentType,PaymentProcessor>(); paymentProcessors.Add(PaymentType.Cash,task.SaveCashPayment); paymentProcessors.Add(PaymentType.CreditCard,task.SaveCreditCardPayment); } private void HookupEventHandlersTo(IPaymentView view) { view.Save+=delegate{ ProcessPayment(); }; } public void ProcessPayment() { try { GetPaymentProcessorFor(view.PaymentType)(view.Amount); } catch (Exception) { view.ShowMessage("There was a problem saving the payment.",false); } view.CloseView(); } private PaymentProcessor GetPaymentProcessorFor(PaymentType paymentType) { return paymentProcessors[paymentType]; } }

 

Of course, another option altogether is to offset the strategy for processing a payment off to the service layer (backed up by a rich domain model, with strategies for handling different payment types). With such a scenario in place you could end up with a presenter that looks like the following:

 

public class ProcessPaymentDTO { private decimal amount; private PaymentType paymentType; public ProcessPaymentDTO(decimal amount,PaymentType paymentType) { this.amount = amount; this.paymentType = paymentType; } public PaymentType PaymentType { get{return this.paymentType;} } public decimal Amount { get{return this.amount;} } } public interface IModifiedPaymentTask { void Process(ProcessPaymentDTO paymentToProcess); } public class ModifiedPaymentPresenter { private IModifiedPaymentTask task; private IPaymentView view; public ModifiedPaymentPresenter(IPaymentView view,IModifiedPaymentTask task) { this.task = task; this.view = view; HookupEventHandlersTo(view); } private void HookupEventHandlersTo(IPaymentView view) { view.Save+=delegate{ ProcessPayment(); }; } public void ProcessPayment() { try { task.Process(new ProcessPaymentDTO(view.Amount,view.PaymentType)); } catch (Exception) { view.ShowMessage("There was a problem saving the payment.",false); } view.CloseView(); } }

Again, this is just another option you could choose,based on the requirements and architecture you already have in place. The nice thing about this solution is that the presenter and view do not need to change in order to handle new types of payments. All the work is done in the back end!!