Jonathan Crossland
Posts: 630
Nickname: jonathanc
Registered: Feb, 2004
|
Jonathan Crossland is a software architect for Lucid Ocean Ltd
|
|
|
|
Refactoring complex classes using Composition Part 2
|
Posted: Apr 9, 2009 5:06 AM
|
|
From Part 1, I showed you how I initially split a large class into a few more, splitting the complexity of having the code in one place. However, you will still find that for a really complex class, there will still be quite a lot to do. Here are a few more composition based things to consider for continuing the refactoring process.
Extract surrounding Exceptions
You will have to decide where you do this carefully, according to your own code, but I try to pull out the larger surrounding Try/Catch blocks into the wrapper, like Start method below. I now can remove the code from RuntimeExecutor for exceptions on as many methods as possible.
There may well be places you do not want to do this, but if your RuntimeExecutor class is private and only used by this wrapper, you should be able to remove most Exception handling in the wrapped class.
However, if the class is complex in this area, you may need an Exception Handling strategy instead. If your wrapped class, throws a complex amount of Exceptions in weird and wonderful areas within the code, you will have to think about it harder, but for most classes you can get away with extracting it.
//abbreviated version of the class.
public class Runtime : IActionExecutor
{
public void Start()
{
try
{
_Executor.Start();
}
catch (Exception ex)
{
LastException = new RuntimeException("Execute Failed. See inner exception for details.", ex);
Status = RuntimeStatus.Errored;
}
}
public RuntimeException LastException
{
get
{
return _Exception;
}
protected set
{
_Exception = value;
}
}
}
Splitting by lifespan and age
Think of your class in three main stages, Constructing, Running, Deconstructing and refactor based on it.
For example, move Initialization code from RuntimeExecutor to Runtime. My class has an Initialize method and some code in its constructor. I now refactor this into the wrapper Runtime. I also move initilization checks to the wrapper.
//abbreviated version of the class.
public class Runtime : IActionExecutor
{
public void Start()
{
try
{
if (_IsInitialized)
_Executor.Start();
}
catch (Exception ex)
{
LastException = new RuntimeException("Execute Failed. See inner exception for details.", ex);
Status = RuntimeStatus.Errored;
}
}
private void Initialize()
{
try
{
//Initialization code, sets RuntimeState (see Part 1)
_IsInitialized = true;
}
catch (Exception ex)
{
LastException = new RuntimeException("Execute Failed. See inner exception for details.", ex);
Status = RuntimeStatus.Errored;
}
}
}
I then do the same with any deconstruction code, so that they too exist in the Runtime class, and extracted from the RuntimeExecutor
Conclusion
The Utility class we created to move those more stateless methods into, can be expanded a little more, by moving even more methods from the main class into it. If you need to, you can even pass the RuntimeState instance via the constructor through to the class, so it can now have state. This will allow you to move even more methods into the Utility class. Remember this class is private and owned by the RuntimeExecutor. If some of these methods are more generic than that, you need to move it out into other classes.
We created four classes from one bulky one. Here is an example in code, of what we did.
///
/// We refactored UP, and created a Wrapper
/// We moved wrapper methods, exceptions to this class
///
public class MyTypeWrapper
{
MyType _MyType;
MyTypeState _MyState;
public MyTypeWrapper()
{
_MyState = new MyTypeState();
_MyType = new MyType(_MyState);
}
}
///
/// This is the original Bulky Class
/// Some of its members moved UP (Wrapper)
///
internal class MyType
{
MyTypeState _MyState; //all fields now wrapped in MyTypeState
MyTypeUtility _MyUtility;
public MyType(MyTypeState myState)
{
_MyState = myState;
_MyUtility = new MyTypeUtility();
}
}
///
/// All the fields moved from MyType to here
///
internal class MyTypeState
{
public MyTypeState()
{
}
}
///
/// Methods moved here are stateless
/// and/or if you add MyState, it can also be stateful.
///
internal class MyTypeUtility
{
public MyTypeUtility()
{
}
}
Cleaning up
You now need to make sure that all is tidy. I usually do a mixture of the following.
- Rename methods to suit the new classes
- Split large methods at least into two
- Rename classes, to make sure they make sense
- Make RuntimeState, RuntimeExecutor classes private and check every method is private if its not called outside of its surroundings. If it is, I attempt to move the method to the utility class.
- See if the Interface we made in Part 1, is still required (most of the time it wont be, only useful for refactoring)
- If there are still large classes, I attempt it all from the start (Part 1)
Read: Refactoring complex classes using Composition Part 2
|
|