Refactoring: Moving Features Between Objects

moh_moh701

mohamed Tayel

Posted on November 15, 2024

Refactoring: Moving Features Between Objects

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>();
}
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
moh_moh701
mohamed Tayel

Posted on November 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Refactoring: Moving Features Between Objects
singleresponsibilityprinciple Refactoring: Moving Features Between Objects

November 15, 2024