// Refactoring is the process of improving code after it has been // written, with the goal of achieving a better design. We like to // think of it as sort of applying the concepts of Feng Shui to code. // // The seminal work in this area is "Refactoring: Improving the // Design of Existing Code", by Martin Fowler, // ISBN: 0-201-48567-2, published by Addison-Wesley. // // Fowler's book includes examples of refactoring in Java. Clipcode // Snippets For Refactoring below complements Fowler's book by // providing examples of the same refactorings in C#. We have quoted // the brief problem/solution statement from each of Fowler's // refactorings, along with the page number in his book where you will // find his detailed discussion of the motiviation and mechanics of // each type of refactoring. While reading the C# examples below, // we recommend having Fowler's book open beside you and learning // about the background to each refactoring. // using System; using System.Collections; using System.Diagnostics; using System.Runtime.InteropServices; namespace ClipcodeSnippetsForRefactoring { class Launcher { // =============================================================================================== // Composing Methods // =============================================================================================== // Extract Method (page 110) // "Problem: You have a code fragment that can be grouped together. // Solution: Turn the fragment into a method whose name explains the purpose of the method." Int32 ProductListPrice = 10; Int32 TaxRate = 10; Int32 ShippingPrice = 20; // BEFORE // ------ void GenerateQuote(Customer customerX) { if (!CanWeExportToThisCountry(customerX.Country)) // handle failed export licensing issue // ... return; // Calculate price Int32 price = ProductListPrice - AvailableDiscount(customerX.Category); price += TaxRate * price; price += ShippingPrice; MakeQuote(customerX, price); } void MakeQuote(Customer x, Int32 p){} Boolean CanWeExportToThisCountry(String c){return true;} Int32 AvailableDiscount(Int32 c) { return 4;} // AFTER // ----- void GenerateQuote2(Customer customerX) { if (!CanWeExportToThisCountry(customerX.Country)) // handle failed export licensing issue // ... return; Int32 fullPrice = CalculatePrice(customerX); MakeQuote(customerX, fullPrice); } Int32 CalculatePrice(Customer customerX) { Int32 price = ProductListPrice - AvailableDiscount(customerX.Category); price += TaxRate * price; price += ShippingPrice; return price; } // Inline Method (page 117) // "Problem: A method's body is just as clear as its name. // Solution: Put the method's body into the body of its callers and remove the method." // BEFORE // ------ void DisplayBookPopularity() { DumpSalesCount(); } void DumpSalesCount() { Console.WriteLine("This month's sales = " + volume); } // AFTER // ----- void DisplayBookPopularity2() { Console.WriteLine("This month's sales = " + volume); } // Inline Temp (page 119) // "Problem: You have a temp that is assigned to once with a simple expression, and // the temp is getting in the way of other refactorings. // Solution; Replace all references to that temp with the expression." // BEFORE // ------ void ShowFees() { Int32 x = CalculateOutstandingFees(); Console.WriteLine("Outstanding Fees = " + x); } // AFTER // ----- void ShowFees2() { Console.WriteLine("Outstanding Fees = " + CalculateOutstandingFees()); } // Replace Temp with Query (page 120) // "Problem: You are using a temporary variable to hold the result of an expression. // Solution: Extract the expression into a method. Replace all references to the temp with the expression. // The new method can then be used in other methods. " // BEFORE // ------ void ChargeInterest() { Int32 interestDue = loanAmount * interestRate; BankBalance -= interestDue; } void GenerateReport() { Int32 interestDue = loanAmount * interestRate; Console.WriteLine("Interest This month = " + interestDue); } // AFTER // ----- Int32 CalcInterest() { return loanAmount * interestRate; } void ChargeInterest2() { BankBalance -= CalcInterest(); } void GenerateReport2() { Console.WriteLine("Interest This month = " + CalcInterest()); } // Introduce Explaining Variable (page 124) // "Problem: You have a complicated expression. // Solution: Put the result of the expression, or parts of the expression, in a temporary variable with a // name that explains the purpose." // BEFORE // ------ void AtmProcessWithdrawal() { if ((Card.GetCard(Machine.CurrentReading).Validate()) && (Card.GetCard(Machine.CurrentReading).CheckPin(Machine.EnteredPin)) && (Account.Check())) { // allow withdrawal // ... } } // AFTER // ----- void AtmProcessWithdrawal2() { Boolean IsCardValid = Card.GetCard(Machine.CurrentReading).Validate(); Boolean IsPinCodeRecognised = Card.GetCard(Machine.CurrentReading).CheckPin(Machine.EnteredPin); Boolean IsAccountInCredit = Account.Check(); if (IsCardValid && IsPinCodeRecognised && IsAccountInCredit) { // allow withdrawal // ... } } // Split Temporary Variable (page 128) // "Problem: You have a temporary variable assigned to more than once, but it is not a loop variable // nor a collecting temporary variable. // Solution: Make a separate temporary variable for each assignment." // BEFORE // ------ void MoneyMatters() { Int32 sumOfMoney; sumOfMoney = Account.CurrentBalance(); // use 'value' sumOfMoney = MoneyInVault(); // use 'sumOfMoney' } // AFTER // ----- void MoneyMatters2() { Int32 balance; Int32 reserve; balance = Account.CurrentBalance(); // use 'balance' reserve = MoneyInVault(); // use 'reserve' } // Remove Assignment to Parameters (page 131) // "Problem: The code assigns to a parameter. // Solution: Use a temporary variable instead." // BEFORE // ------ void ProcessCustomer(Customer c) { c.OutputDetails(); c = GetAnotherCustomer(); // ... // later in code, could easily get confused about what c represents } // AFTER // ----- void ProcessCustomer2(Customer c) { c.OutputDetails(); Customer anotherCustomer; anotherCustomer = GetAnotherCustomer(); // ... // later in code, there can be no confusion over parameters } // Replace Method with Method Object (page 135) // "Problem: You have a long method that uses local variables in such a way that you cannot applied Extract Method (110). // Solution: Turn the method into its own object so that all the local variables becomne fields on that object. // You can then decompose the method into other methods on the same object." // BEFORE // ------ Int32 LongComplicatedMethod() { Int32 x = 3; Int32 y = 5; Int32 z = 7; // do something complicated x += 1; // do something complicated y += x; // do something complicated z = y * x; return 42; } // AFTER // ----- class MethodObject { Int32 x; Int32 y; Int32 z; public MethodObject(Int32 x, Int32 y, Int32 z) { this.x = x; this.y = y; this.z = z; } public Int32 Compute() { // do something complicated x += 1; // do something complicated y += x; // do something complicated z = y * x; return 42; } } Int32 ShortSimpleMethod() { return new MethodObject(3,5,7).Compute(); } // Note: The real goal of a method object is to refactor a complicated // method, so we might later get: class MethodObject2 { Int32 x = 3; Int32 y = 5; Int32 z = 7; public MethodObject2(Int32 x, Int32 y, Int32 z) { this.x = x; this.y = y; this.z = z; } void Compute() { // do something complicated x += 1; // do something complicated y += x; } void SimpleCompute() { // do something complicated z = y * x; // since the parameters are part of the class, it is easy to // access them from any method of the class (but not externally) // hence method extraction is much simpler } } // // Substitute Algorithm (page 139) // Problem: You want to replace an algorithm with one that is clearer. // Solution: Replace the body of the method with the new algorithm. // BEFORE // ------ static Double CalculateAverage(Double x, Double y, Double z) { Double avg; avg = x/3.0 + y/3.0 + z/3.0; return avg; } // AFTER // ----- static Double CalculateAverage2(Double x, Double y, Double z) { Double avg; avg = (x + y + z) / 3.0; return avg; } // Note: The important point with SubstituteAlgorithm is to maintain the method signature. All // you unit tests that worked with the previous algorithm should continue to work with the new // algorithm - if not, the either the new or the old algorithm is incorrect. If engaging in // changing algorithms, should also consider use of interfaces, and allow algorithms to be // pluggable. // =============================================================================================== // Moving Features Between Objects // =============================================================================================== // Move Method (page 142) // Problem: A method is, or will be, using or used by more features of another class than by the class on which // it is defined. // Solution: Create a new method with a similar body in the class it uses most. Either turn the old method // into a simple delegation, or remove it altogether. // BEFORE // ------ class ClassA { static public void Method1() { // important functionality } // rest of FirstClass // .. } class ClassB { void XMethod() { // do some work // ... ClassA.Method1(); } void YMethod() { // do some work // ... ClassA.Method1(); } } // AFTER // ----- class ClassA_2 { // rest of FirstClass // .. } class ClassB_2 { static public void Method1() { // important functionality } void XMethod() { // do some work // ... Method1(); } void YMethod() { // do some work // ... Method1(); } } // Move Field (page 146) // Problem: A field is, or will be, used by another class more than the class on which it is defined. // Solution: Create a new field in the target class, and change all its users. // BEFORE // ------ class ClassC { public const Int32 UsefulInfo = 4; } class ClassD { void DoSomething() { Int32 val = ClassC.UsefulInfo; // use val Console.WriteLine("val = " + val); } } // AFTER // ----- class ClassC_2 { } class ClassD_2 { // Another benefit of moving the field closer to where it will // be used is that we can remove the public (or internal) declaration // - a design goal is to have shy objects, that communicate as little // as possible with other objects (hence are much easier to evolve in // future, and when changed, will not cause ripple effects throughout // our framework. const Int32 UsefulInfo = 4; void DoSomething() { Int32 val = UsefulInfo; // use val Console.WriteLine("val = " + val); } } // Extract Class (page 149) // "Problem: You have one class doing work that should be done by two. // Solution: Create a new class and move the relevant fields and methods from the old class into the new class." // BEFORE // ------ class Book { String isbn="123"; String title="ABC"; String authorName="ZYX"; Int32 authorYearOfBirth=2100; void DisplayBookInfo() { Console.WriteLine("Book's Title = " + title); Console.WriteLine("ISBN = " + isbn); } void DisplayAuthorInfo() { Console.WriteLine("Book's Author = " + authorName); Console.WriteLine("Author's year of birth = " + authorYearOfBirth); } } // AFTER // ----- class Book2 { String isbn="123"; String title="ABC"; void DisplayBookInfo() { Console.WriteLine("Book's Title = " + title); Console.WriteLine("ISBN = " + isbn); } } class Author { String authorName="ZYX"; Int32 authorYearOfBirth=2100; void DisplayAuthorInfo() { Console.WriteLine("Book's Author = " + authorName); Console.WriteLine("Author's year of birth = " + authorYearOfBirth); } } // Also need to consider how to expose author to client's og the Book class // Perhaps expose Author as a public property of the Book class, or have author // as a private field in Book, and maintain Book.DisplayAuthorInfo, which // internally calls Author.DisplayAuthorInfo(). // Inline Class (page 154) // "Problem: A class isn't doing much. // Solution: Move all its features into another class and delete it." // BEFORE // ------ class Big { Int32 Subtract (Int32 x, Int32 y) { return x - y; } Int32 Multiply (Int32 x, Int32 y) { return x * y; } Int32 Add (Int32 x, Int32 y) { return Little.AddNumbers(x, y); } } class Little { public static Int32 AddNumbers(Int32 x, Int32 y) { return x + y; } } // If classes tryly represent distinguishable constructs, even if small, // they it still makes sense to have them exist. However, there is a certain // amount of leaway, and this refactoring is saying that in cases where there // does not seem a great need for a small class to exist, its fields/methods // can be added to another class (usually the caling class). // AFTER // ----- class Big2 { Int32 Subtract (Int32 x, Int32 y) { return x - y; } Int32 Multiply (Int32 x, Int32 y) { return x * y; } Int32 Add (Int32 x, Int32 y) { return x + y; } } // Hide Delegate (page 157) // "Problem: A client is calling a delegate class of an object. // Solution: Create methods on the server to hide the delegate." // BEFORE // ------ public class Client { // somehow get MarketingRegion MarketingRegion region = new MarketingRegion("Australia"); void DisplayDetails() { Console.WriteLine("regionName = " + region.RegionName); Console.WriteLine("Sales figure = " + region.SalesManager.CalculateMonthlySales()); // the problem here is that the client is now tied to both the // MarketingRegion and SalesManager classes. } } public class MarketingRegion { SalesManager salesManager = new SalesManager(); String regionName; public String RegionName { get{ return regionName;} } public SalesManager SalesManager { get{return salesManager;} } public MarketingRegion(String regionName) { this.regionName = regionName; } } public class SalesManager { public Int32 CalculateMonthlySales() { // do calculation here return 42; } } // AFTER // ----- public class Client2 { // somehow get MarketingRegion MarketingRegion2 region = new MarketingRegion2("Australia"); void DisplayDetails() { Console.WriteLine("regionName = " + region.RegionName); Console.WriteLine("Sales figure = " + region.GetSalesFigure()); // Now the SalesManager classe can change without having to // alter the Client2 class } } public class MarketingRegion2 { SalesManager2 salesManager = new SalesManager2(); String regionName; public String RegionName { get{ return regionName;} } public MarketingRegion2(String regionName) { this.regionName = regionName; } // As part of the publically accessible class, MarketingRegion2, // we provide a method that delegates the real work to a method // in the internal class, SalesManager. public Int32 GetSalesFigure() { return salesManager.CalculateMonthlySales(); } } // note we mark SalesManager as internal, as it // no longe needs to be acceisble from outside internal class SalesManager2 { public Int32 CalculateMonthlySales() { // do calculation here return 42; } } // Remove Middle Man (page 160) // "Problem: A class is doing too much simple delegation. // Solution: Get the client to call the delegate directly." // BEFORE // ------ // Remove Middle Man is the inverse of Hide Delegate. If you have // too much delegation, as some point you need to simply expose the // delegated class directly. public class Client3 { // somehow get MarketingRegion MarketingRegion3 region = new MarketingRegion3("Australia"); void DisplayDetails() { Console.WriteLine("regionName = " + region.RegionName); Console.WriteLine("Sales figure = " + region.GetSalesFigure()); } } public class MarketingRegion3 { SalesManager3 salesManager = new SalesManager3(); String regionName; public String RegionName { get{ return regionName;} } public MarketingRegion3(String regionName) { this.regionName = regionName; } public Int32 GetSalesFigure() { return salesManager.CalculateMonthlySales(); } } internal class SalesManager3 { public Int32 CalculateMonthlySales() { // do calculation here return 42; } } // AFTER // ----- public class Client4 { // somehow get MarketingRegion MarketingRegion4 region = new MarketingRegion4("Australia"); void DisplayDetails() { Console.WriteLine("regionName = " + region.RegionName); Console.WriteLine("Sales figure = " + region.SalesManager.CalculateMonthlySales()); } } public class MarketingRegion4 { SalesManager4 salesManager = new SalesManager4(); String regionName; public String RegionName { get{ return regionName;} } public SalesManager4 SalesManager { get{return salesManager;} } public MarketingRegion4(String regionName) { this.regionName = regionName; } } public class SalesManager4 { public Int32 CalculateMonthlySales() { // do calculation here return 42; } } // Introduce Foreign Method (page 162) // "Problem: A server class you are using needs an additional method, but you can't modify the class. // Solution: Create a method in the client class with an instance of the server class as its first argument." // BEFORE // ------ class Caller { Caller() { Callee callee = new Callee(Int32.MaxValue, Int32.MinValue); } } class Callee { // For some reason, e.g. it is someone else's code, cannot change callee class public Callee(){} public Callee(Int32 x, Int32 y){ /* do something */ } } // AFTER // ----- class Caller2 { Caller2() { Callee callee = MakeNewBoundaryCallee(); } Callee MakeNewBoundaryCallee() { // This is a foreign method that should be in Callee return new Callee(Int32.MaxValue, Int32.MinValue); } } class Callee2 { // For some reason, e.g. it is someone else's code, cannot change callee class Callee2(){} Callee2(Int32 x, Int32 y){ /* do something */ } } // Introduce Local Extension (page 164) // "Problem: A server class you are using needs several additional methods, but you can't modify the class. // Solution: Create a new class that contains these additional methods. Make this extension class a // subclass or a wrapper of the original." // BEFORE // ------ class Caller3 { Caller3() { Callee3 callee = MakeNewBoundaryCallee(); } Callee3 MakeNewBoundaryCallee() { // This is a foreign method that should be in Callee return new Callee3(Int32.MaxValue, Int32.MinValue); } } class Callee3 { // For some reason, e.g. it is someone else's code, cannot change callee class public Callee3(){} public Callee3(Int32 x, Int32 y){ /* do something */ } } // AFTER // ----- class Caller4 { Caller4() { Callee4 callee4 = ExtensionClass.MakeNewBoundaryCallee(); } } class ExtensionClass : Callee4 { static public Callee4 MakeNewBoundaryCallee() { // This is a foreign method that should be in Callee return new Callee4(Int32.MaxValue, Int32.MinValue); } } class Callee4 { // For some reason, e.g. it is someone else's code, cannot change callee class public Callee4(){} public Callee4(Int32 x, Int32 y){ /* do something */ } } // =============================================================================================== // Organising Data // =============================================================================================== // Self Encapsulate Field (page 171) // "Problem: You are accessing a field directly, but the coupling to the field is becoming awkward. // Solution: Create getting and setting methods for the field and use only those to access the field." // BEFORE // ------ class ShowMe { Double pi = 3.1416; public void Print() { Console.WriteLine("value = " + pi); } } // AFTER // ----- class ShowMe2 { Double pi = 3.1416; public Double PI { get {return pi;} } public void Print() { Console.WriteLine("value = " + PI); } } // This is useful when a property may be computed, and may be different in subclass // Hence using the Property, and not the field, inside the class methods. // Replace Data Value with Object (page 175) // "Problem: You have a data item that needs additional data or behaviour. // Solution: Turn the data item into an object." // BEFORE // ------ class PupilInfo { String pupilGivenName=" "; String pupilFamilyName=" "; Int32 classId=4; String streetAddress=" "; String town=" "; void ShowName() { Console.WriteLine("Name = " + pupilGivenName + " " + pupilFamilyName + " (class = " + classId + ")"); } void ShowAddress() { Console.WriteLine(streetAddress + " " + town); } } // AFTER // ----- class PupilInfo2 { String pupilGivenName=" "; String pupilFamilyName=" "; Int32 classId=4; void ShowName() { Console.WriteLine("Name = " + pupilGivenName + " " + pupilFamilyName + " (class = " + classId + ")"); } } class Address { String streetAddress=" "; String town=" "; void ShowAddress() { Console.WriteLine(streetAddress + " " + town); } } // Change Value to Reference (page 179) // "Problem: You have a class with many equal instances that you want to replace with a single object. // Solution: Turn the object into a reference object." // BEFORE // ------ class CastleSecuritySystem { CastleGate gateUsedByKing; CastleGate gateUsedByCommoners; CastleGate gateUsedBySlaves; CastleSecuritySystem() { // different objects are created for each, though in // reality the same object can serve multiples times gateUsedByKing = new CastleGate("right"); gateUsedByCommoners = new CastleGate("left"); gateUsedBySlaves = new CastleGate("left"); } } class CastleGate { String name; public CastleGate(String name) { this.name = name; } } // AFTER // ----- class CastleSecuritySystem2 { CastleGate2 gateUsedByKing; CastleGate2 gateUsedByCommoners; CastleGate2 gateUsedBySlaves; CastleSecuritySystem2() { gateUsedByKing = CastleGate2.GateFactory("right"); gateUsedByCommoners = CastleGate2.GateFactory("left"); gateUsedBySlaves = CastleGate2.GateFactory("left"); } } class CastleGate2 { String name; static Hashtable h = new Hashtable(); String gateColor; // Note it is when the object becomes editable that we really // need to make it into a reference object - so that the changes // are available to all users of this instance public String GateColor { get { return gateColor;} set {gateColor = value;} } public static CastleGate2 GateFactory(String name) { // search hashtable for existing object CastleGate2 gate = (CastleGate2) h[name]; if (gate != null) { gate = new CastleGate2(name); h[name] = gate; } return gate; } // Note we make the constructor private - all external access is via the factory method private CastleGate2(String name) { this.name = name; } } // Change Reference to Value (page 183) // "Problem: You have a reference object that is small, immutable, and awkward to manage. // Solution: Turn it into a value object." // BEFORE // ------ class GridPosition { Int32 x; Int32 y; public Int32 X { get { return x;} } public Int32 Y { get {return y;} } private GridPosition (Int32 x, Int32 y) { this.x = x; this.y = y; } // factory to create } // AFTER // ----- class GridPosition2 { Int32 x; Int32 y; public Int32 X { get { return x;} } public Int32 Y { get {return y;} } public GridPosition2 (Int32 x, Int32 y) { this.x = x; this.y = y; } public override bool Equals(object obj) { if (obj.GetType() != this.GetType()) return false; GridPosition2 grid2 = (GridPosition2)obj; if (x == grid2.X && y == grid2.y) return true; else return false; } public override int GetHashCode() { return x.GetHashCode() ^ y.GetHashCode(); } } // Replace Array with Object (page 186) // "Problem: You have an array in which certain elements mean different things. // Solution: Replace the array with an object that has a field for each element." // BEFORE // ------ class Dog { // fields and methods for Dog } class Car { // fields and methods for Car } class MyProperty { object[] things = new object[2]; MyProperty() { things[0]= new Dog(); things[1] = new Car(); } // it is very dangerous to have different types stored in the same array, and having // to use special indices to access them - we are strongly in favour of strong-typing } // AFTER // ----- // car, dog as before class MyProperty2 { Dog dog = new Dog(); Car car = new Car(); MyProperty2() { } } // Duplicate Observed Data (page 189) // "Problem: You have domain data available only in a GUI control, and domain methods need access. // Solution: Copy the data to a domain object. Set up an observer to synchronise the two pieces of data." // BEFORE // ------ class UIWidget { // A major area where this refactoring is applicable is user interfaces - to keep the code snippet here // simple, we do not load in a windowsing library - we invent our own - UIWidget. } class UIForm { Int32 length=0; Int32 width=0; UIWidget lengthTextBox=new UIWidget(); UIWidget widthTextBox=new UIWidget(); public void Display() { // use length and width here Console.WriteLine(length + width + lengthTextBox.ToString() + widthTextBox.ToString()); } } // AFTER // ----- class UIForm2 { Int32 length=0; Int32 width=0; UIWidget lengthTextBox = new UIWidget(); UIWidget widthTextBox = new UIWidget(); DomainObject domainObject = new DomainObject(); public void Display() { // use length and width here } void EventHandler(Int32 newLength) { // called when widget is changed (e.g. text edited) length = newLength; domainObject.Update(length*width); } } class DomainObject { Int32 area; // Often the data in the UI widgets is in some way processed before // being provided to the DOmian Object. Here we assume the UI is // handle length of width, whereas the domain object is only interested in area public void Update(Int32 area) { this.area = area; } } // Change Unidirectional Association to Bidirectional (page 197) // "Problem: You have two classes that need to use each other's features, but there is only a one-way link. // Solution: Add back pointers, and change modifiers to update both sets." // BEFORE // ------ // the following is fine if we only need to decide who author wrote a book, but // what happens when we wish to discover which book an author wrote? class Book3 { Author1 author = new Author1(); } class Author1 { } // AFTER // ----- class Book4 { Author2 author = new Author2(); } class Author2 { Book4 book = new Book4(); } // One often will have multiples of each - a book can by written by multiple authors, and each author // can write multiple books, hence a hashtable is often used here. // Change Bidirectional Association to Unidirectional (page 200) // "Problem: You have a two-way association but one class no longer needs features from the other. // Solution: Drop the un-needed end of the association." // BEFORE // ------ class Book5 { Page[] pages = new Page[3]; } class Page { Int32 pageNumber=0; Book5 book=null; void Display() { Console.WriteLine(pageNumber); if (book != null) Console.WriteLine(book.ToString()); } } // Every page must be part of one book, and in most cases can be accessed via a book // reference,. so we can remove the bidirectional association // AFTER // ----- class Book6 { Page2[] pages=null; void Display() { if (pages != null) Console.WriteLine(pages[0].ToString()); } } class Page2 { Int32 pageNumber=0; void Display() { Console.WriteLine(pageNumber); } } // Replace Magic Number with Symbolic Constant (page 204) // "Problem: You have a literal number with a particular meaning. // Solution: Create a constant, name it after the meaning, and replace the number with it." // BEFORE // ------ class BufferManager { char[] myBuffer=new char[1024]; } // AFTER // ----- class BufferManager2 { public const Int32 MaxPathLength = 1024; char[] myBuffer = new char[MaxPathLength]; } // Encapsulate Field (page 206) // "Problem: There is a public field. // Solution: Make it private and provide accessors." // BEFORE // ------ class Footballer { public String GivenName=null; } // AFTER // ----- class Footballer2 { // note: when no visibility indicator given, it defaults to private. String givenName=null; public String GivenName { get {return givenName;} } } // An Aside: the package visibility indicator, internal, is not use often enough // We are in favour of packages exposing as little surface area as possible // If needed later, it is easy to open up more - it is much more difficult to // do the reverse later. // Encapsulate Collection (page 208) // "Problem: A method returns a collection. // Solution: Make it return a read-only view and provide add/remove methods." // BEFORE // ------ class Car2 { // car details } class MyGarage { Car2[] cars = new Car2[10]; public Car2[] GetCars() { return cars; } } // AFTER // ----- class MyGarage2 { CarCollection carCollection = new CarCollection(); public CarCollection Cars { get{ return carCollection;} } } class CarCollection { Car2[] cars = new Car2[10]; public void Add(Car2 car){/* logic for add */} public void Remove(String name){ /* logic for remove */ } // other methods for access etc. } // see Clipcode Knowledge Platform for examples of complete collection classes // .NET v2 will add generics - when available, we stringly recommend their use. // Replace Record with Data Class (page 217) // "Problem: You need to interface with a record structure in a traditional programming environment. // Solution: Make a dumb data object for the record." // BEFORE // ------ [StructLayout(LayoutKind.Sequential, Pack=1)] public struct SYSTEM_INFO { public ushort wProcessorArchitecture; public ushort wReserved; public uint dwPageSize; public IntPtr lpMinimumApplicationAddress; public IntPtr lpMaximumApplicationAddress; public IntPtr dwActiveProcessorMask; public uint dwNumberOfProcessors; public uint dwProcessorType; public uint dwAllocationGranularity; public ushort wProcessorLevel; public ushort wProcessorRevision; }; [DllImport("KERNEL32", CharSet=CharSet.Auto)] public static extern void GetSystemInfo(out SYSTEM_INFO si); class GetNumberOfProcesses { void func() { SYSTEM_INFO si; GetSystemInfo(out si); uint numberOfProcessors = si.dwNumberOfProcessors; Console.WriteLine("Number Of Processors = "+numberOfProcessors.ToString()); } } // AFTER // ----- class SysInfo { SYSTEM_INFO si; public UInt32 NumberOfProcessors { get{return si.dwNumberOfProcessors;} } public UInt32 PageSize { get{return si.dwPageSize;} } internal SysInfo() { // makes sense to populate date here GetSystemInfo(out si); } } class GetNumberOfProcesses2 { void func() { SysInfo sysInfo = new SysInfo(); Console.WriteLine("Number Of Processors = "+sysInfo.NumberOfProcessors.ToString()); } } // Replace Type Code with Class (page 218) // "Problem: A class has a numeric type code that does not affect its behaviour. // Solution: Replace the number with a new class." // Comment: In our example below, we start with an enumeration (we defined the numeric value of each field), // and evolve it into a new class. Fowler has these values as numeric fields in the main class, but // some people might think the problem is solved simply by using an enumeration - it is not. // In C#, with the exception of the number 0 (zero), conversion between an enumeration and its underlying type // requires an explicit cast. The compiler does not check to see that the value used actually represents a enumeration value. // BEFORE // ------ enum OfficeFloor { GroundFloor=0, FirstFloor=1, SecondFloor=2 } class Office { OfficeFloor floor; public Office(OfficeFloor floor) { this.floor = floor; } void BuggyCode() { floor = (OfficeFloor) 45; // this floor des not exist! } } // AFTER // ----- class OfficeFloor2 { const Int32 GroundFloor=0; const Int32 FirstFloor=1; const Int32 SecondFloor=2; const Int32 MinCode=GroundFloor; const Int32 MaxCode=SecondFloor; Int32 code; public Int32 Code { get{return code;} set{code = value;} } OfficeFloor2(Int32 code) { if (code>= MinCode && code <= MaxCode) this.code = code; else throw new ArgumentOutOfRangeException("code provided = " + code.ToString() + " ; was invalid. Code must be between " + MinCode + " and " + MaxCode + "."); } } class Office2 { OfficeFloor2 floor; public Office2(OfficeFloor2 floor) { this.floor = floor; } public static Office2 BuggyCode() { Office2 office = null; // compiler catches this error! // new Office2(OfficeFloor2)3); // 3 cannot be converted to an instance of OfficeFloor2 return office; } } // Replace Type Code with Subclasses (page 223) // "Problem: You have an immutable type code that affects the behaviour of a class. // Solution: Replace the type code with subclasses." // BEFORE // ------ class Vehicle { public const Int32 Car = 0; public const Int32 Truck = 1; public const Int32 SUV = 2; Int32 vehicleCode; public Vehicle(Int32 code) { vehicleCode = code; } public void DisplayType() { switch (vehicleCode) { case Car: Console.WriteLine("VEHICLE = CAR"); break; case Truck: Console.WriteLine("VEHICLE = TRUCK"); break; case SUV: Console.WriteLine("VEHICLE = SUV"); break; default: throw new Exception("Unexpected switch type"); } } } // AFTER // ----- abstract class Vehicle2 { public const Int32 Car = 0; public const Int32 Truck = 1; public const Int32 SUV = 2; abstract public void DisplayType(); //Int32 vehicleType; // Factory method public Vehicle2 GetVehicle(Int32 code) { switch (code) { case Car: return new CarType(); case Truck: return new TruckType(); case SUV: return new SUVType(); default: throw new Exception("Unexpected switch type"); } } // could also think about using Replace Conditional With Polymorphism (255). } class CarType : Vehicle2 { override public void DisplayType() { Console.WriteLine("VEHICLE = CAR"); } } class TruckType : Vehicle2 { override public void DisplayType() { Console.WriteLine("VEHICLE = Truck"); } } class SUVType : Vehicle2 { override public void DisplayType() { Console.WriteLine("VEHICLE = SUV"); } } // Replace Type Code with State/Strategy (page 227) // "Problem: You have a type code that affects the behaviour of a class, but you cannot use subclassing. // Solution: Replace the type code with a state object." // BEFORE // ------ class Vehicle3 { public const Int32 Car = 0; public const Int32 Truck = 1; public const Int32 SUV = 2; Int32 vehicleCode; public Vehicle3(Int32 code) { vehicleCode = code; } public void DisplayType() { switch (vehicleCode) { case Car: Console.WriteLine("VEHICLE = CAR"); break; case Truck: Console.WriteLine("VEHICLE = TRUCK"); break; case SUV: Console.WriteLine("VEHICLE = SUV"); break; default: throw new Exception("Unexpected switch type"); } } } // AFTER // ----- class Vehicle4 { VehicleType vehicleType; public Vehicle4() { vehicleType = new SUVType2(); vehicleType.Display(); } } abstract class VehicleType { public const Int32 Car = 0; public const Int32 Truck = 1; public const Int32 SUV = 2; abstract public Int32 GetCode(); // Factory method public void Display() { switch (GetCode()) { case Car: Console.WriteLine("CAR"); break; case Truck: Console.WriteLine("TRUCK"); break; case SUV: Console.WriteLine("SUV"); break; default: throw new Exception("Unexpected switch type"); } // Now need to think about using Replace Conditional With Polymorphism (255). } } class CarType2 : VehicleType { override public Int32 GetCode() { return VehicleType.Car; } } class TruckType2 : VehicleType { override public Int32 GetCode() { return VehicleType.Truck; } } class SUVType2 : VehicleType { override public Int32 GetCode() { return VehicleType.SUV; } } // Replace Subclass with fields (page 232) // "Problem: You have subclasses that vary only in methods that return constant data. // Solution: Change the methods to superclass fields and eliminate the subclasses." // BEFORE // ------ abstract class Customer2 { abstract public String GetCustomerTypeDescriptor(); } class CorporateCustomer: Customer2 { override public String GetCustomerTypeDescriptor() { return "CORPORATE"; } } class ResidentialCustomer : Customer2 { override public String GetCustomerTypeDescriptor() { return "RESIDENTIAL"; } } // AFTER // ----- class Customer3 { String customerType; private Customer3(String customerType) { this.customerType = customerType; } public String GetCustomerTypeDescriptor() { return customerType; } // factory methods static public Customer3 CreateResidentialCustomer() { return new Customer3("RESIDENTIAL"); } static public Customer3 CreateCorporateCustomer() { return new Customer3("CORPORATE"); } } // =============================================================================================== // Simplifying Conditional Expressions // =============================================================================================== // // Decompose Conditional (page 238) // "Problem: You have a complicated conditional (if-then-else) statement. // Solution: Extract methods from the condition, then part and else parts." // BEFORE // ------ class Complicated { const Int32 MaxPermittedThickness = 100; const Int32 MinPermittedThickness = 10; Int32 maxMeasuredThickness; Int32 minMeasuredThickness; public Complicated(Int32 maxMeasuredThickness, Int32 minMeasuredThickness) { this.maxMeasuredThickness = maxMeasuredThickness; this.minMeasuredThickness = minMeasuredThickness; } void DoSomething() { if ((maxMeasuredThickness < MaxPermittedThickness) && (minMeasuredThickness > MinPermittedThickness)) Console.WriteLine("something complicated"); else Console.WriteLine("something complicated"); } } // AFTER // ----- class Simple { const Int32 MaxPermittedThickness = 100; const Int32 MinPermittedThickness = 10; Int32 maxMeasuredThickness; Int32 minMeasuredThickness; void DoSomething() { // A goal is to make top-level methods almost read like comments if (WithinTolerances()) PassDownAssemblyLine(); else Rework(); } public Simple(Int32 maxMeasuredThickness, Int32 minMeasuredThickness) { this.maxMeasuredThickness = maxMeasuredThickness; this.minMeasuredThickness = minMeasuredThickness; } Boolean WithinTolerances() { return ((maxMeasuredThickness < MaxPermittedThickness) && (minMeasuredThickness > MinPermittedThickness)); } void PassDownAssemblyLine() { Console.WriteLine("something complicated"); } void Rework() { Console.WriteLine("something complicated"); } } // Consolidate Conditional Expression (page 240) // "Problem: You have a sequence of conditional tests with the same result. // Solution: Combine them into a single conditional expression and extract it." // BEFORE // ------ class MultiConditionWorker { Boolean OnHolidays = false; Boolean IsSick = false; Boolean OnTrainingCourse = false; Int32 HoursAvailableForWorkToday() { if (OnHolidays) return 0; if (IsSick) return 0; if (OnTrainingCourse) return 0; return 8; } } // AFTER // ----- class SingleConditionWorker { Boolean OnHolidays = false; Boolean IsSick = false; Boolean OnTrainingCourse = false; Boolean AtWork() { return (OnHolidays || IsSick || OnTrainingCourse); } Int32 HoursAvailableForWorkToday() { if (AtWork()) return 0; else return 8; } } // Consolidate Duplicate Conditional Fragments (page 243) // "Problem: The same fragment of code is in all branches of a conditional expression. // Solution: Move it outside of the expression." // BEFORE // ------ class MapEngine { static public String TownFinder(Double longitude, Double latitude) { // complicate map searching here ... return "North Pole"; } } class WhereAmI { String nearestTown; void Display(Boolean InOuterSpace, Double longitude, Double latitude) { if (InOuterSpace) { nearestTown = "NONE"; Console.WriteLine("Nearest Town = " + nearestTown); } else { nearestTown = MapEngine.TownFinder(longitude, latitude); Console.WriteLine("Nearest Town = " + nearestTown); } } } // AFTER // ----- class WhereAmI2 { String nearestTown; void Display(Boolean InOuterSpace, Double longitude, Double latitude) { if (InOuterSpace) nearestTown = "NONE"; else nearestTown = MapEngine.TownFinder(longitude, latitude); Console.WriteLine("Nearest Town = " + nearestTown); } } // Remove Control Flag (page 245) // "Problem: You have a variable that is acting as a control flag for a series of boolean expressions. // Solution: Use a break or return instead." // BEFORE // ------ class Room { Boolean isAvailable; Int32 roomNumber; public Boolean IsAvailable { get{return isAvailable;} } public Int32 RoomNumber { get{return roomNumber;} } public Room(Boolean isAvailable, Int32 roomNumber) { this.isAvailable = isAvailable; this.roomNumber = roomNumber; } } class TrainingLab { Room[] rooms; Int32 roomCount=0; public TrainingLab() { roomCount = 3; rooms = new Room[roomCount]; rooms[0] = new Room(true, 10); rooms[1] = new Room(false, 23); rooms[2] = new Room(true, 43); } Int32 DisplayFreeTrainingRoom() { Boolean found=false; Int32 freeRoomNum=-1; for(Int32 i=0; i < roomCount; i++) { if (!found) if (rooms[i].IsAvailable) { found = true; freeRoomNum = rooms[i].RoomNumber; // other work for found room } } return freeRoomNum; } // AFTER // ----- class TrainingLab2 { Room[] rooms; Int32 roomCount=0; public TrainingLab2() { roomCount = 3; rooms = new Room[roomCount]; rooms[0] = new Room(true, 10); rooms[1] = new Room(false, 23); rooms[2] = new Room(true, 43); } Int32 FindFreeTrainingRoom() { Int32 freeRoomNum=-1; for(Int32 i=0; i < roomCount; i++) { if (rooms[i].IsAvailable) { freeRoomNum = rooms[i].RoomNumber; // other work for found room break; } } return freeRoomNum; } } } // Replace Nested Conditional with Guard Clauses (page 250) // "Problem: A method has conditional behavior that does not make clear the normal path of execution. // Solution: Use guard clauses for all the special cases." // BEFORE // ------ class Unclear { const String Usa = "Usa"; const String EuropeanUnion = "European Union"; const String RestOfWorld = "Rest of World"; String AmericanWay() { return "A"; } String EuropeanWay() { return "B"; } String RestOfWorldWay() {return "C"; } String Evaluate(String area) { String data=null; if (area == Usa) data = AmericanWay(); else { if (area == EuropeanUnion) data = EuropeanWay(); else data = RestOfWorldWay(); } return data; } } // AFTER // ----- class Clear { const String Usa = "Usa"; const String EuropeanUnion = "European Union"; const String RestOfWorld = "Rest of World"; String AmericanWay() { return "A"; } String EuropeanWay() { return "B"; } String RestOfWorldWay() {return "C"; } String Evaluate(String area) { // flow of control is much clearer here if (area == Usa) return AmericanWay(); if (area == EuropeanUnion) return EuropeanWay(); return RestOfWorldWay(); } } // Replace Conditional with Polymorphism (page 255) // "Problem: You have a conditional that chooses different behaviour depending on the type of the object. // Solution: Move each leg of the conditional to an overriding method in a subclass. Make the original method abstract." // BEFORE // ------ // The BEFORE for this sample is the AFTER code from State/Strategy (227). // It contains a switch conditional statement, and in the following refactoring we wish to remove it using polymorphism. // AFTER // ----- class Vehicle5 { VehicleType2 vehicleType; public Vehicle5() { vehicleType = new SUVType3(); vehicleType.Display(); } } abstract class VehicleType2 { public const Int32 Car = 0; public const Int32 Truck = 1; public const Int32 SUV = 2; abstract public Int32 GetCode(); // Factory method abstract public void Display(); } class CarType3 : VehicleType2 { override public Int32 GetCode() { return VehicleType.Car; } override public void Display() { Console.WriteLine("CAR"); } } class TruckType3 : VehicleType2 { override public Int32 GetCode() { return VehicleType.Truck; } override public void Display() { Console.WriteLine("TRUCK"); } } class SUVType3 : VehicleType2 { override public Int32 GetCode() { return VehicleType.SUV; } override public void Display() { Console.WriteLine("SUV"); } } // Introduce Null Object (page 260) // "Problem: You have repeated checks for a null object. // Solution: Replace the null value with the null object." // BEFORE // ------ class NewCar { public void DisplayBrand(){Console.WriteLine("BWW");} } class CarDealer { void DisplayDetails(NewCar car) { if (car != null) { car.DisplayBrand(); } } } // AFTER // ----- // Note: System.Data.SQLTypes.INullable does exist in the .NET Framework, but it requires the System.Data assembly. // This is really a core interface, not tied to SQL, and so would be more useful within System. Too late t ochange now. // Therefore we define uor own. interface INullable { Boolean IsNull(); } class NewCar2 : INullable { static NullNewCar nullCar=null; public virtual void DisplayBrand(){Console.WriteLine("BWW");} static public NewCar2 NewNull() { // We use the Singleton design pattern here - we only need one instance if (nullCar == null) nullCar = new NullNewCar(); return nullCar; } public Boolean IsNull(){return false;} } class NullNewCar : NewCar2 { new public Boolean IsNull(){return true;} public override void DisplayBrand(){Console.WriteLine(" ");} } class CarDealer2 { void DisplayDetails(NewCar2 car) { // now we can remove the if from here car.DisplayBrand(); } } // Introduce Assertion (page 267) // "Problem: A section of code assumes something about the state of the program. // Solution: Make the assumption explicit with an assertion." // BEFORE // ------ class Rectangle { Int32 x1; Int32 y1; Int32 x2; Int32 y2; public Rectangle(Int32 x1, Int32 y1, Int32 x2, Int32 y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } } // AFTER // ----- class Rectangle2 { Int32 x1; Int32 y1; Int32 x2; Int32 y2; public Rectangle2(Int32 x1, Int32 y1, Int32 x2, Int32 y2) { Debug.Assert(x2 > x1); Debug.Assert(y2 > y1); this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } } // =============================================================================================== // Making Method Calls Simpler // =============================================================================================== // // Rename Method (page 273) // "Problem: The name of a method does not reveal its purpose. // Solution: Change the name of the method." // BEFORE // ------ class Sq { Int32 l; Sq(Int32 l) { this.l = l; } Int32 GetA() { return l*l; } } // AFTER // ----- class Square { Int32 length; Square(Int32 length) { this.length = length; } Int32 GetArea() { return length*length; } } // Add Parameter (page 275) // "Problem: A method needs more information from its caller. // Solution: Add a paraemter for an object that can pass this information." // BEFORE // ------ class Color { public void MyFavoriteColor() { Console.WriteLine("RED"); } } // AFTER // ----- class Color2 { public void MyFavoriteSeasonalColor(String season) { if (season == "WINTER") Console.WriteLine("RED"); else Console.WriteLine("GREEN"); } } // Remove Parameter (page 277) // "Problem: A parameter is no longer used by the method body. // Solution: Remove it." // this refactoring is the reverse of Add Parameter. // Separate Query from Modifier (page 279) // "Problem: You have a method that returns a value but also changes the state of the object. // Solution: Create two methods, one for the query and one for the modification." // If you are using descriptive names (as recommended), often the method name will contain And. // BEFORE // ------ class MySquare { const Int32 MinBigSquare=10; Int32 area = 4; public Boolean SetSizeAndDetermineIfBig(Int32 length) { this.area = length * length; return (area > MinBigSquare); } } // AFTER // ----- class MySquare2 { const Int32 MinBigSquare=10; Int32 area = 4; public void SetSize(Int32 length) { this.area = length * length; } public Boolean DetermineIfBig() { return (area > MinBigSquare); } } // Parameterize Method (page 283) // "Problem: Several methods do similar things but with different values contained in the method body. // Solution: Create one method that uses a parameter for the different values." // BEFORE // ------ class StringManager { String data; public StringManager(String data){this.data = data;} public void AppendLargeSuffix(String suffix){data += suffix;} public void AppendShortSuffix(String suffix){data += suffix;} } // AFTER // ----- class StringManager2 { String data; public StringManager2(String data){this.data = data;} public void AppendSuffix(String suffix){data += suffix;} } // Replace Parameter with Explicit Methods (page 285) // "Problem: You have a method that runs different code depending on the values of an enumerated parameter. // Solution: Create a separate method for each value of the parameter." // BEFORE // ------ class MyCar { String brand; String manufacturer; public MyCar(String brand, String manufacturer) { this.brand = brand; this.manufacturer = manufacturer; } public void SetDescriptor(String category, String newDescriptor) { if (category == "BRAND") brand = newDescriptor; else if (category == "MANUFACTURER") manufacturer = newDescriptor; } } // AFTER // ----- class MyCar2 { String brand; String manufacturer; public MyCar2(String brand, String manufacturer) { this.brand = brand; this.manufacturer = manufacturer; } public void SetBrnad(String brand) { this.brand = brand; } public void SetManufacturer(String manufacturer) { this.manufacturer = manufacturer; } } // Preserve Whole Object (page 288) // "Problem: You are getting several values from an object and passing these values as parameters in a method call. // Solution: Send the whole object instead." // BEFORE // ------ class MyCar3 { public String Brand="SMART"; public String Manufacturer="Mercedes"; public String CoolnessFactor="Ultra"; } class ReportGenerator { public void Demo() { MyCar3 myCar = new MyCar3(); Display(myCar.Brand, myCar.Manufacturer, myCar.CoolnessFactor); } public void Display(String brand, String manufacturer, String coolnessFactor) { Console.WriteLine("brand = " + brand + "; manufacturer = " + manufacturer + "; coolness factor = " + coolnessFactor); } } // AFTER // ----- class ReportGenerator2 { public void Demo() { MyCar3 myCar = new MyCar3(); Display(myCar); } public void Display(MyCar3 myCar) { Console.WriteLine("brand = " + myCar.Brand + "; manufacturer = " + myCar.Manufacturer + "; coolness factor = " + myCar.CoolnessFactor); } } // Replace Parameter with Method (page 292) // "Problem: An object invokes a method, then passes the result as a parameter for a method. The receiver // can also invoke this method. // Solution: Remove the parameter and let the receiver invoke the method." // BEFORE // ------ class CarDealer3 { String brand = "PRIUS"; String manufacturer = "Toyota"; String GetIdentifier() { return brand+manufacturer; } public void Display(String title) { DateTime today = DateTime.Today; String identifier = GetIdentifier(); Int32 price = 10; // complicated code PrintReport(today.ToShortTimeString(), identifier, price); } void PrintReport(String dateToday, String carIdentifier, Int32 price) { // do something } } // AFTER // ----- class CarDealer4 { String brand = "PRIUS"; String manufacturer = "Toyota"; String GetIdentifier() { return brand+manufacturer; } public void Display(String title) { Int32 price = 10; // complicated code PrintReport(price); } // note much shorter parameter list // you may be wondering why have the Display method at all - it is // needed if there is some complicated code in there and it simplfies // if we separate that from PrintReport. void PrintReport(Int32 price) { DateTime today = DateTime.Today; String identifier = GetIdentifier(); // do something } } // Introduce Parameter Object (page 295) // "Problem: You have a group of parameters that naturally go together. // Solution: Replace them with an object." // BEFORE // ------ class CarDealer5 { void Display(String carBrand, String carManufacturer) { Console.WriteLine("brand = " + carBrand + "; Manufacturer = " + carManufacturer); } } // AFTER // ----- class Car3 { String brand; String manufacturer; public String Brand { get{return brand;} } public String Manufacturer { get{return manufacturer;} } public Car3(String brand, String manufacturer) { this.brand = brand; this.manufacturer = manufacturer; } } class CarDealer6 { void Display(Car3 car) { Console.WriteLine("brand = " + car.Brand + "; Manufacturer = " + car.Manufacturer); } } // Remove Setting Method (page 300) // "Problem: A field should be set at creation time and never altered. // Solution: Remove any setting method for that field." // BEFORE // ------ class MyCar7 { String brand; public String Brand { get{return brand;} set{brand = value;} } } // AFTER // ----- class MyCar8 { String brand; public String Brand { // remove setters when not needed get{return brand;} } public MyCar8(String brand) { this.brand = brand; } } // Hide Method (page 303) // "Problem: A method is not used by any other class. // Solution: Make the method private." // BEFORE // ------ class Plane { public void StartPlane() { OpenFuelPump(); // rest of work to start plane } public void OpenFuelPump() { // only called from within the Plane class! } } // AFTER // ----- class Plane2 { public void StartPlane() { OpenFuelPump(); // rest of work to start plane } private void OpenFuelPump() { // only called from within the Plane class! } } // Replace Constructor with Factory Method (page 304) // "Problem: You want to do more than simple construction when you create an object. // Solution: Replace the constructor with a factory method." // BEFORE // ------ class CastleSecuritySystem3 { CastleGate3 gateUsedByKing; CastleGate3 gateUsedByCommoners; CastleGate3 gateUsedBySlaves; CastleSecuritySystem3() { // different objects are created for each, though in // reality the same object can serve multiples times gateUsedByKing = new CastleGate3("right"); gateUsedByCommoners = new CastleGate3("left"); gateUsedBySlaves = new CastleGate3("left"); } } class CastleGate3 { String name; public CastleGate3(String name) { this.name = name; } } // AFTER // ----- class CastleSecuritySystem4 { CastleGate4 gateUsedByKing; CastleGate4 gateUsedByCommoners; CastleGate4 gateUsedBySlaves; CastleSecuritySystem4() { gateUsedByKing = CastleGate4.GateFactory("right"); gateUsedByCommoners = CastleGate4.GateFactory("left"); gateUsedBySlaves = CastleGate4.GateFactory("left"); } } class CastleGate4 { String name; static Hashtable h = new Hashtable(); public static CastleGate4 GateFactory(String name) { // search hashtable for existing object CastleGate4 gate = (CastleGate4) h[name]; if (gate != null) { gate = new CastleGate4(name); h[name] = gate; } return gate; } // Note we make the constructor private - all external access is via the factory method private CastleGate4(String name) { this.name = name; } } // Encapsulate Downcast (page 308) // "Problem: A method returns an object that needs to be downcasted by its callers. // Solution: Move the downcast to within the method." // BEFORE // ------ class SpaceTransporter { // fields + methods } class TroopCarrier : SpaceTransporter { // fields + methods } class LogisticsCarrier : SpaceTransporter { // fields + methods } class SpacePort { // collection of transporter types attached to docking pods protected object[] transporters = new object[10]; // should use System.Collections for a growable array // When generics arrive (in C# v2), use them! } class MiltarySpacePort : SpacePort { // assume MiltartySpacePort only allows TroopCarriers public MiltarySpacePort() { transporters[0] = new TroopCarrier(); } public object GetFirst() { return transporters[0]; } void DisplayMapOfAttachedTransporters() { TroopCarrier t= (TroopCarrier) GetFirst(); // do something with t } } // AFTER // ----- class MiltarySpacePort2 : SpacePort { // assume MiltartySpacePort only allows TroopCarriers public MiltarySpacePort2() { transporters[0] = new TroopCarrier(); } public TroopCarrier GetFirst() { return (TroopCarrier)(transporters[0]); } void DisplayMapOfAttachedTransporters() { TroopCarrier t= GetFirst(); // do something with t } } // Replace Error Code with Exception (page 310) // "Problem: A method returns a special code to indicate an error. // Solution: Throw an exception instead." // BEFORE // ------ class Engine { // fields + methods public void Start(){ /* do something */ } } class ZeroEnergyConceptSpaceMachine { Engine engine=null; public Int32 StartEngine() { // engine should have been installed before here if (engine == null) return -1; engine.Start(); return 1; } } // AFTER // ----- class EngineException : Exception { public EngineException(String message): base(message) { } } class ZeroEnergyConceptSpaceMachine2 { Engine engine=null; public void StartEngine() { if (engine == null) throw new EngineException("engine should have been installed before here"); engine.Start(); } } // Replace Exception with Test (page 315) // "Problem: You are throwing a checked exception on a condition the caller could have checked first. // Solution: Change the caller to make the test first." // Note: File IO is a classic case where exceptions are definitely not recommended at EOF. // This condition will always happen in the course of normal operation, and hence the caller // should be checking the return value against an EOF constant, not expecting an exception. // BEFORE // ------ class DiskManager { static public Int32 AvailableBytes(){/* do disk sector buffering etc. */ return 10;} static public Int32 ReadBytes(Byte[] data, Int32 offset, Int32 count){ return 0; } } class File { // exceptions should be reserved for genuine error conditions // by using an if statement, we can often avoid the exception public Int32 Read(Byte[] buffer, Int32 offset, Int32 count) { Int32 availableBytes = DiskManager.AvailableBytes(); if (availableBytes == 0) throw new Exception("End Of File"); Int32 bytesToRead = (availableBytes > count) ? count : availableBytes; DiskManager.ReadBytes(buffer, offset, availableBytes); return bytesToRead; } } // AFTER // ----- class File2 { public const Int32 EOF = -1; public Int32 Read(Byte[] buffer, Int32 offset, Int32 count) { Int32 availableBytes = DiskManager.AvailableBytes(); if (availableBytes == 0) return EOF; Int32 bytesToRead = (availableBytes > count) ? count : availableBytes; DiskManager.ReadBytes(buffer, offset, availableBytes); return bytesToRead; } } // =============================================================================================== // Dealing with Generalisation // =============================================================================================== // Pull Up Field (page 320) // "Problem: Two subclasses have the same field. // Solution: Move the field to the superclass." // BEFORE // ------ class Movie { String title=null; public override String ToString() { return "title = " + title; } } class SciFi : Movie { Int32 duration=0; String jetPropulsionSystem="StringTheory"; public override String ToString() { return "duration = " + duration + "propulsion = " + jetPropulsionSystem; } } class Historical : Movie { Int32 duration=100; Boolean isBC=true; public override String ToString() { return "duration = " + duration + "Set in a year Before Christ (BC) = " + isBC.ToString(); } } // AFTER // ----- class Movie2 { String title="The Day After Tomorrow"; Int32 duration=10; public override String ToString() { return "title = " + title + "duration = " + duration; } } class SciFi2 : Movie2 { String jetPropulsionSystem="ZeroPointEnergy"; public override String ToString() { return "propulsion = " + jetPropulsionSystem; } } class Historical2 : Movie2 { Boolean isBC=true; public override String ToString() { return "Set in a year Before Christ 9BC) = " + isBC.ToString(); } } // Pull Up Method (page 322) // "Problem: You have methods with identical results on subclasses. // Solution: Move them to the superclass." // BEFORE // ------ class Movie3 { String title="Intermission"; protected Int32 duration=120; public override String ToString() { return "title = " + title + "duration = " + duration; } } class SciFi3 : Movie3 { String jetPropulsionSystem="NuclearFusion"; public void DisplayDuration() { Console.WriteLine("Duration = " + duration); } public override String ToString() { return "propulsion = " + jetPropulsionSystem; } } class Historical3 : Movie3 { Boolean isBC=true; public void DisplayDuration() { Console.WriteLine("Duration = " + duration); } public override String ToString() { return "Set in a year Before Christ (BC) = " + isBC.ToString(); } } // AFTER // ----- class Movie4 { String title="Alexandar"; Int32 duration=203; public void DisplayDuration() { Console.WriteLine("Duration = " + duration); } public override String ToString() { return "title = " + title + "duration = " + duration; } } class SciFi4 : Movie4 { String jetPropulsionSystem="TopSecret"; public override String ToString() { return "propulsion = " + jetPropulsionSystem; } } class Historical4 : Movie4 { Boolean isBC=true; public override String ToString() { return "Set in a year Before Christ (BC) = " + isBC.ToString(); } } // Pull Up Constructor Body (page 325) // "Problem: You have constructors on subclasses with mostly identical bodies. // Solution: Create a superclass constructor, call this from the subclass method." // BEFORE // ------ class Clock { protected Int32 hours; protected Int32 minutes; } class DigitalClock : Clock { Boolean display24Hours; public DigitalClock(Int32 hours, Int32 minutes, Boolean display24Hours) { this.hours = hours; this.minutes = minutes; this.display24Hours = display24Hours; DisplayWhetherDuringRegularOpeningHours(); } void DisplayWhetherDuringRegularOpeningHours() { Int32 openingTime = 8; Int32 closingTime = 20; if (hours > openingTime && hours < closingTime) Console.WriteLine("Shop is open"); else Console.WriteLine("Shop is closed"); } } // AFTER // ----- class Clock2 { protected Int32 hours; protected Int32 minutes; public Clock2(Int32 hours, Int32 minutes) { this.hours = hours; this.minutes = minutes; } protected void DisplayWhetherDuringRegularOpeningHours() { Int32 openingTime = 8; Int32 closingTime = 20; if (hours > openingTime && hours < closingTime) Console.WriteLine("Shop is open"); else Console.WriteLine("Shop is closed"); } } class DigitalClock2 : Clock2 { Boolean display24Hours; public DigitalClock2(Int32 hours, Int32 minutes, Boolean display24Hours) : base(hours, minutes) { this.display24Hours = display24Hours; DisplayWhetherDuringRegularOpeningHours(); } } // Push Down Method (328) // "Problem: Behaviour on a superclass is relevant only for some of its subclasses. // Solution: Move it to those subclasses." // BEFORE // ------ class Movie5 { public void StartPropulsionEngine(){} } class SciFi5 : Movie5 { } class Historical5 : Movie5 { } // AFTER // ----- class Movie6 { } class SciFi6 : Movie6 { public void StartPropulsionEngine(){} } class Historical6 : Movie6 { } // Push Down Field (page 329) // "Problem: A field is used only by some subclasses. // Solution: Move the field to those subclasses." // BEFORE // ------ class Movie7 { String jetPropulsionSystem="ZeroPointEnergy"; public override string ToString() { return jetPropulsionSystem; } } class SciFi7 : Movie7 { } class Historical7 : Movie7 { } // AFTER // ----- class Movie8 { } class SciFi8 : Movie8 { String jetPropulsionSystem="ZeroPointEnergy"; public override string ToString() { return jetPropulsionSystem; } } class Historical8 : Movie8 { } // Extract Subclass (page 330) // "Problem: A class has features that are used only in some instances. // Solution: Create a subclass for that subset of features." // BEFORE // ------ class Movie9 { // fields + methods common to all movices // ... // field+method specific to one type of movie String jetPropulsionSystem="ZeroPointEnergy"; public override string ToString() { return jetPropulsionSystem; } } // AFTER // ----- class Movie10 { // fields + methods common to all movices // ... } class SciFi10 : Movie10 { // put fields+methods specific to one type of // movie into a subclass String jetPropulsionSystem="ZeroPointEnergy"; public override string ToString() { return jetPropulsionSystem; } } // Extract Superclass (page 336) // "Problem: You have two classes with similar features. // Solution: Create a superclass and move the common features to the superclass." // BEFORE // ------ class Apple { String color=null; override public String ToString(){return color;} } class Orange { String color=null; override public String ToString(){return color;} } // AFTER // ----- class Fruit { String color=null; override public String ToString(){return color;} } class Apple2 : Fruit { } class Orange2 : Fruit { } // Extract Interface (page 341) // "Problem: Several clients use the same subset of a class' interface, or two classes have part of their interface in common. // Solution: Extract the subset into an interface." // BEFORE // ------ class Bike { public void Start(){} public void Stop(){} public void TurnCorner(){} } class Skateboard { public void Start(){} public void Stop(){} public void TurnCorner(){} } // AFTER // ----- interface IModeOfTransport { void Start(); void Stop(); void TurnCorner(); } class Bike2 : IModeOfTransport { public void Start(){} public void Stop(){} public void TurnCorner(){} } class Skateboard2 : IModeOfTransport { public void Start(){} public void Stop(){} public void TurnCorner(){} } // Collapse Hierarchy (page 344) // "Problem: A superclass and subclass are not very different. // Solution: Merge them together." // BEFORE // ------ class Worker { } class DiligentWorker : Worker { DiligentWorker() { } } // AFTER // ----- class Worker2 { Boolean isDiligent; Worker2 (Boolean isDiligent) { this.isDiligent = isDiligent; } } // Form Template Method (page 345) // "Problem: You have two methods in subclasses that perform similar steps in the same order; yet the steps are different. // Solution: Get the steps into methods with the same signature, so that the original methods become the same. // Then you can pull them up." // BEFORE // ------ class House { protected String color="red"; } class SuburbanHouse : House { Int32 groundFloorSize; Int32 firstFloorSize; SuburbanHouse(Int32 groundFloorSize, Int32 firstFloorSize) { this.groundFloorSize = groundFloorSize; this.firstFloorSize = firstFloorSize; } public void DisplayDetails() { Int32 area = groundFloorSize + firstFloorSize; Console.WriteLine("area = " + area + "color = " + color); } } class BoatHouse : House { Int32 wheelHouseArea; Int32 exposedDeckArea; Int32 belowDeckArea; public BoatHouse(Int32 wheelHouseArea, Int32 exposedDeckArea, Int32 belowDeckArea) { this.wheelHouseArea = wheelHouseArea; this.exposedDeckArea = exposedDeckArea; this.belowDeckArea = belowDeckArea; } public void DisplayDetails() { Int32 area = wheelHouseArea + belowDeckArea + exposedDeckArea /2; //assume we only count half of exposed deck area Console.WriteLine("area = " + area + "color = " + color); } } // AFTER // ----- abstract class House2 { protected String color="red"; public abstract Int32 CalculateArea(); public void DisplayDetails() { Int32 area = CalculateArea(); Console.WriteLine("area = " + area + "color = " + color); } } class SuburbanHouse2 : House2 { Int32 groundFloorSize; Int32 firstFloorSize; SuburbanHouse2(Int32 groundFloorSize, Int32 firstFloorSize) { this.groundFloorSize = groundFloorSize; this.firstFloorSize = firstFloorSize; } override public Int32 CalculateArea() { return groundFloorSize + firstFloorSize; } } class BoatHouse2 : House2 { Int32 wheelHouseArea; Int32 exposedDeckArea; Int32 belowDeckArea; public BoatHouse2(Int32 wheelHouseArea, Int32 exposedDeckArea, Int32 belowDeckArea) { this.wheelHouseArea = wheelHouseArea; this.exposedDeckArea = exposedDeckArea; this.belowDeckArea = belowDeckArea; } override public Int32 CalculateArea() { return wheelHouseArea + belowDeckArea + exposedDeckArea /2; } } // Replace Inheritance with Delegation (page 352) // "Problem: A subclass uses only part of a superclass' interface or does not want to inherit data. // Solution: Create a field for a method for the superclass, adjust methods to delegate to the superclass, // and remove the subclassing." // BEFORE // ------ class ModeOfTransport { public String metalWorkDescription(){return "steel";} Int32 costPerKilometer; Int32 kilometersPerHour; public ModeOfTransport(Int32 costPerKilometer, Int32 kilometersPerHour) { this.costPerKilometer = costPerKilometer; this.kilometersPerHour = kilometersPerHour; } public Int32 OperatingCostsPerHour() { return costPerKilometer * kilometersPerHour; } } class MyOwnTwoFeet : ModeOfTransport { // shoe-specific fields + methods public MyOwnTwoFeet(Int32 costPerKilometer, Int32 kilometersPerHour): base(costPerKilometer, kilometersPerHour) {} // note:MyOwnTwoFeet is a healthy mode of transport, and some fields (e.g. kilometersPerHour) and // methods (OperatingCostsPerHour) from the superclass are applicable but others // (metalWorkDescription) simply do not make sense. } // AFTER // ----- class MyOwnTwoFeet2 // Note: no inheritance! { // shoe-specific fields + methods ModeOfTransport modeOfTransport = new ModeOfTransport(20, 10); public Int32 OperatingCostsPerHour() { return modeOfTransport.OperatingCostsPerHour(); } } // another solution might be to use Extract Interface and re-arrange the hierarchy // Replace Delegation with Inheritance (page 355) // "Problem: You're using delegation and are often writing many simple delegations for the entire interface. // Solution: Make the delegating class a subclass of the delegate." // BEFORE // ------ class Uri { String resource; public Uri(String resource) { this.resource = resource; } public String GetAuthority(){/* parse */ return null;} public String GetHost(){/* parse */ return null;} public String GetPort(){/* parse */ return null;} } class MailToUri { Uri localUri = null; MailToUri(String resource) { localUri = new Uri(resource); } // many methods are delegating to Uri class public String GetAuthority(){return localUri.GetAuthority();} public String GetHost(){return localUri.GetHost();} public String GetPort(){return localUri.GetPort();} public void SendEMail(String subject, String message){/*send email*/} } // AFTER // ----- class MailToUri2 : Uri { MailToUri2(String resource) : base (resource) { } public void SendEMail(String subject, String message){/*send email*/} } // =============================================================================================== // Big Refactorings // =============================================================================================== // Big refactorings can be considered the point where changes to the // code expand from simple localised alterations to more significant // changes to the architecture of the application. // Tease Apart Inheritance (page 362) // "Problem: You have an inheritance hierarchy that is doing two jobs at once. // Solution: Create two hierarchies and use delegation to invoke one // from the other." // // Our commentary // Though inheritance hierarchies often start out with a well-defined // purpose, over time they can accumulate disparate tasks, and it // becomes clear that they do not all need to be part of the same // hierarchy. By separating them out, we have smaller hierarchies that // more focused and that are much easier to evolve at their own pace // into the future. Also, calling code can use one but not the other // hierarchy when needed. // When designing hierarchies, do judiciously use both interface and // implementation inheritance. .NET does support multiple interface // inheritance but not support multiple implementation inheritance // (neither in .NET v1.1 not .NET v2.0). When an object needs to // expose multiple behaviours, then interfaces are a good option. // When there may be multiple types of base classes, then interfaces // provided the needed flexibility. Also consider the use of // delegates. Framework designers should be very cautious about // mandating specific base classes – a base interface is often // more appropriate. The framework can (and often should) provide // a default implementation class for interfaces. Finally, pay // attention to versioning issues with .NET interfaces. // Convert Procedural Design To Objects (page 368) // "Problem: You have code written in a procedural style. // Solution: Turn the data records into objects, break up the behaviour, // and move the behaviour to the objects." // // Our commentary // Procedural coding consists of long intertwined methods, with data // and behaviour spread everywhere. It is a style of programming that // exhibit brittleness in large projects. Such code is very difficult // to maintain and to evolve. Ultimately it leads to a re-write. It has // gone out of fashion for modern programming, but you may still // encounter it in old code and unfortunately all to often in recent // code that was developed without enough attention having being paid // to object concepts. Excuses range from the developers being in too // much of a hurry (but it takes longer!) to not having a solid // understanding of OO. Often this code grew over several iterations, // and now is much longer than original. // When confronted with procedural coding, you need to convert it to // objects. You can start by creating data objects. These are objects // that store and provide protected access to data items. (Sometimes // this will be your starting point, as intermediate OO developers at // least got this part right). // Your next step is assuring behaviour is where it should be. If you // have objects that merely store data, but does not expose methods // that operate on that data, you need to ask where is that data // manipulated. A golden rule is that data and the behaviour that // operates on it should become co-located in the same objects as // much as possible. If behaviours need to talk a lot with a // particular piece of data, that that combination is a good // candidate for an object. // Separate Domain From Presentation (370) // "Problem: You have GUI classes than contain domain logic. // Solution: Separate the domain logic into separate domain classes." // // Our commentary: // For non-trivial projects, it is advisable to separate the user // interface from the core logic. Working against this are wizards // in the development environment, that make it seemingly so easy to // directly hook up between UI controls and data. However, this leads // to inflexibility and problems in the longer term. Separation brings // a number of advantages: // * Developers with the appropriate skills can can work on the // different sections - someone skilled in UI user interface design // skills and a different person skilled in the domain logic // * Developers can work in parallel // * The domain logic and UI code will change at a different pace // (the user interface will need to be updated often, whereas the // domain code should be more stable) // * The test harness needed for each type of functionality is different // * There may be a single implementation of the domain logic, but // multiple implementations of the user interface (ASP.NET, Longhorn’s // “Avalon”, WinForms, MONO’s Gtk#) // * Allows non-UI access to logic (e.g. via web service) // Extract Hierarchy (page 375) // "Problem: You have a class that is doing too much work, at least in // part through many conditional statements. // Solution: Create a hierarchy of classes in which each subclass // represents a special case." // // Our commentary: // We use the term “octopus class” to identify the root of this problem. // When we spot a class that has its tentacles going all over the place // it is clear that it is ripe for refactoring. // A method in a class with a large switch statements often indicate a // hierarchy that needs to surface as classes. If multiple methods in // the same class each have large switch statements, with the same, or // almost the same, list of conditionals are present, this is especially // so. Another telling sign is to see widely different functionality // appear within the same class. The class simply looks cluttered. // Such classes are often found at high-throughput junction-points – // places in the code which are traversed very often, and it is just // expedient to place new functionality there during a rush to meet // a deadline. Later upon reflection, it is clear that we really need // to rebalance the architecture – by teasing out the independent // classes and working on the optimum hierarchy. Each class should // be small and focused on provides a largely independent set of // services. // HELPER FUNCTIONALITY // // The main entry point for the application. // [STAThread] static void Main(string[] args) { // // TODO: Add code to start application here // Console.WriteLine(CalculateAverage(132323234.0, 34232323.0, 3232322.0)); Console.WriteLine(CalculateAverage2(132323234.0, 34232323.0, 3232322.0)); } // The follow is needed to make the examples compile int volume = 10; Int32 loanAmount = 10; Int32 interestRate = 2; Int32 BankBalance = 40; Int32 CalculateOutstandingFees() { return 5; } Int32 MoneyInVault() { return 3; } class Machine { static public String CurrentReading = "12345"; static public String EnteredPin = "12345"; } class Card { public static Card GetCard(String value) { return new Card(); } public Boolean Validate() { // do some validation check here // .. return true; } public Boolean CheckPin(String pin) { // Check pin // .. return true; } } class Account { static public Boolean Check() { // do account checking here // .. return true; } public static Int32 CurrentBalance() { return 45; } } class Customer { public void OutputDetails() { } public Int32 Category { get { return 4; } } public String Country { get { return "Antartic"; } } } Customer GetAnotherCustomer() { return new Customer(); } } }