Refactoring: Moving Features Between Objects
mohamed Tayel
Posted on November 15, 2024
Meta Description :Learn how to organize your code effectively with the 'Moving Features Between Objects' refactoring technique. Discover how to use Move Method/Field and Extract Class to align functionality with the right objects, improve readability, and simplify maintenance with detailed examples in C#.
Refactoring is an essential skill for maintaining clean, understandable, and scalable code. One effective refactoring technique is "Moving Features Between Objects", which helps organize code better by placing functionality where it truly belongs. Let's explore this concept, including practical examples of two common sub-techniques: Move Method/Field and Extract Class.
Why Move Features Between Objects?
In object-oriented programming, each class should encapsulate functionality and data that directly relate to its purpose. However, you might find scenarios where:
- A method or field is more relevant to another class.
- A class handles responsibilities that belong to two or more separate entities.
In such cases, it's recommended to move or extract features to maintain single responsibility and improve code clarity.
Technique 1: Move Method/Field
Scenario
A method or field is more often used in another class than its current location.
Solution
Move the method or field to the class where it belongs. This improves code readability and aligns functionality with the right object.
Example: Move Method
Imagine we have two classes: Product
and ShoppingCart
. Initially, the Product
class contains a method AddToCart
:
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public void AddToCart(ShoppingCart cart)
{
cart.Items.Add(this);
}
}
public class ShoppingCart
{
public List<Product> Items { get; set; } = new List<Product>();
}
Here, the Product
class is responsible for adding itself to the cart, which isn't appropriate. Adding products to a cart is a responsibility of ShoppingCart
.
Refactored Code
We move the AddToCart
method to the ShoppingCart
class:
public class ShoppingCart
{
public List<Product> Items { get; set; } = new List<Product>();
public void AddToCart(Product product)
{
Items.Add(product);
}
}
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
This refactor makes the code more intuitive: the ShoppingCart
manages its items, and the Product
class focuses solely on representing a product.
Technique 2: Extract Class
Scenario
A class performs the functionality of two or more classes, violating the Single Responsibility Principle.
Solution
Split the functionality into separate classes, so each class is focused on its specific role.
Example: Extract Class
Let's take a Trail
class that manages both trail information and activities:
public class Trail
{
public string Name { get; set; }
public string Location { get; set; }
// Trail activities
private bool hasHiking;
private bool hasMountainBiking;
private bool hasCamping;
private bool hasFishing;
// ...more activity fields
public bool HasHiking => hasHiking;
public bool HasMountainBiking => hasMountainBiking;
public bool HasCamping => hasCamping;
public bool HasFishing => hasFishing;
}
The Trail
class is cluttered with fields and properties related to activities. These belong in a separate TrailActivities
class.
Refactored Code
We create a new TrailActivities
class and move the relevant fields:
public struct TrailActivities
{
public bool HasHiking { get; set; }
public bool HasMountainBiking { get; set; }
public bool HasCamping { get; set; }
public bool HasFishing { get; set; }
// Add other activities here
}
public class Trail
{
public string Name { get; set; }
public string Location { get; set; }
public TrailActivities Activities { get; set; }
}
Now, Trail
is cleaner and focuses on its core responsibility, while TrailActivities
encapsulates the details of activities.
When Not to Use These Techniques
- Move Method/Field: Avoid moving functionality if it's frequently used in multiple classes. Instead, consider introducing a helper or utility class.
- Extract Class: If the class is small and there's no planned expansion, it may not be worth splitting it.
Key Benefits
- Improved readability: Each class contains only what it should.
- Simplified maintenance: Changes to functionality are isolated to relevant classes.
- Better adherence to principles: Encourages the Single Responsibility Principle and promotes clean architecture.
By refactoring methods and fields or extracting classes where necessary, you can create a codebase that's easier to understand, extend, and maintain. These small adjustments lead to significant long-term improvements in code quality.
Posted on November 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.