Reader small image

You're reading from  Refactoring with C#

Product typeBook
Published inNov 2023
Reading LevelIntermediate
PublisherPackt
ISBN-139781835089989
Edition1st Edition
Languages
Right arrow
Author (1)
Matt Eland
Matt Eland
author image
Matt Eland

Matt Eland is a Microsoft MVP in Artificial Intelligence (AI) who has been working with .NET since 2001. Matt has served as a senior engineer, software engineering manager, and .NET programming instructor. He is currently an AI specialist and senior consultant at Leading EDJE near Columbus, Ohio, where he helps companies with their software engineering and data science needs using C# and related technologies. Matt speaks and writes in his community and co-organizes the Central Ohio .NET Developers Group while pursuing a master's degree in data analytics.
Read more about Matt Eland

Right arrow

Object-Oriented Refactoring

In the last chapter, we saw how refactoring can help improve classes and their methods. In this chapter, we’ll explore the bigger picture with creative uses of object-oriented programming (OOP) to refactor a series of classes into more maintainable forms. These tools will help you perform larger and more impactful refactorings and make a bigger difference in improving your code.

We’ll cover the following topics in this chapter:

  • Organizing classes via refactoring
  • Refactoring and inheritance
  • Controlling inheritance with abstract
  • Refactoring for better encapsulation
  • Improving classes with interfaces and polymorphism

Technical requirements

The starting code for this chapter is available from GitHub at https://github.com/PacktPublishing/Refactoring-with-CSharp in the Chapter05/Ch5BeginningCode folder.

Refactoring the flight search system

This chapter’s code focuses on a flight scheduling system for Cloudy Skies Airlines.

The flight scheduling system is a simple one that tracks all active flights through a FlightScheduler class and allows external callers to search for flights of interest. This class in turn tracks flights through a collection of IFlightInfo instances, which may either be a PassengerFlightInfo or a FreightFlightInfo instance, depending on whether the flight carries passenger or freight.

The high-level interactions of these classes can be seen in Figure 5.1:

Figure 5.1 – Classes involved in the Cloudy Skies Airline flight scheduling system

Figure 5.1 – Classes involved in the Cloudy Skies Airline flight scheduling system

The code currently works and even uses polymorphism effectively to track a variety of different flights. That being said, there are some opportunities for improvement, as we’ll see. Throughout this chapter, we’ll make targeted improvements while demonstrating the...

Organizing classes via refactoring

It’s not uncommon for solutions to have organizational challenges such as misnamed files or types existing in the wrong file or namespace.

These problems may seem small, but they can make it harder for developers to find the code they’re looking for – particularly when first joining the project.

Let’s look at a few refactorings that help developers navigate code more easily.

Moving classes to individual files

One common mistake I’ve seen teams make is putting multiple types inside of the same file. Usually, a file starts with a single class or interface and then a developer decides to add a related type. Instead of putting the new type in a file of its own, the class gets added to the existing file. Once this happens for a few small classes, it tends to snowball after that with developers continuing to add new types to the file as time goes on.

Types

If you’re not familiar with the use of...

Refactoring and inheritance

Now that we’ve covered some of the ways refactoring can help organize your code, let’s dive into refactorings related to inheritance. This is a collection of refactorings that involve either overriding methods, introducing inheritance, or altering in-place inheritance relationships to improve the maintainability of code.

Overriding ToString

ToString is one of the four methods that any .NET object is guaranteed to have due to the virtual definition of ToString on System.Object. This method is used whenever an object is converted to a string and can be particularly handy for logging and debugging purposes.

Sometimes overriding ToString can simplify your code in unexpected ways.

Let’s look at the BuildFlightIdentifier method in FreightFlightInfo.cs. This method relies on the DepartureLocation and ArrivalLocation properties of type Airport to produce a string:

FreightFlightInfo.cs

public string BuildFlightIdentifier() ...

Controlling inheritance with abstract

Now that we’ve covered some refactoring patterns around inheritance, let’s look at using abstract classes and other C# features to restrict our classes and ensure they’re used appropriately.

Communicating intent with abstract

One quirk about our current design is that it is possible to instantiate a new instance of FlightInfoBase simply by writing the following code:

FlightInfoBase flight = new FlightInfoBase();

While it might not make sense to you – for a new flight to exist that isn’t explicitly a passenger or freight flight, because the FlightInfoBase class is not marked as abstract – there’s nothing preventing anyone from instantiating it.

To mark a class as abstract, add the abstract keyword to its signature:

FlightInfoBase.cs

public abstract class FlightInfoBase : IFlightInfo {
  public Airport ArrivalLocation { get; set; }
  public DateTime ArrivalTime...

Refactoring for better encapsulation

Another core tenet of object-oriented programming is encapsulation. With encapsulation, you assert control of the data in your classes and ensure others work with data in ways that make sense both immediately and as the code grows over time.

The following refactorings deal with the various pieces of data composing classes along with the data passed along to methods as parameters.

Encapsulating fields

The simplest encapsulation refactoring allows you to wrap all uses of a field into a property.

In the following code example, the PassengerFlightInfo class has a _passengers field storing the count of passengers on the flight, and this field is used throughout the class when referring to the passenger count:

public class PassengerFlightInfo : FlightInfoBase {
  private int _passengers;
  public void Load(int passengers) =>
    _passengers = passengers;
  public void Unload() =>
...

Improving classes with interfaces and polymorphism

We’re nearly at the close of this chapter on object-oriented refactoring. However, before we close the chapter, let’s discuss a few places where introducing interfaces and polymorphism can help further improve our code.

Extracting interfaces

At the moment, our CharterFlightInfo class stores a list of CargoItems representing its cargo:

public class CharterFlightInfo : FlightInfoBase {
  public List<CargoItem> Cargo { get; } = new();
  // Other members omitted...
}

Each cargo item the charter flight includes must be a CargoItem or something that inherits from it. For example, if we were to create the HazardousCargoItem we discussed in the last section and try to store it in the cargo collection, it must inherit from CargoItem to compile.

In many systems, you don’t want to force people to inherit from your classes if they want to customize the system’s behavior. In these...

Reviewing and testing our refactored code

With these changes made, let’s take a step back and look at the result.

We took a flight search system and used object-oriented programming techniques to improve its flexibility and maintainability by doing the following:

  • Reorganizing the code into appropriate files and namespaces
  • Introducing a base class and improving code reuse in flight information
  • Controlling a large number of parameters by moving them into a new class
  • Introducing another new class to manage common information about airport events including both an airport and a time component
  • Adding a charter flight class with a flexible cargo tracking system
  • Introducing a polymorphic way of searching flights that will be more flexible and maintainable over time

Refactored code

The final refactored code from this chapter is available in the https://github.com/PacktPublishing/Refactoring-with-CSharp repository inside of the Chapter05/Ch5RefactoredCode...

Summary

In this chapter, we explored the various ways that object-oriented programming techniques such as inheritance, encapsulation, and polymorphism can be used to refactor code toward more maintainable forms.

Refactoring can be a complex endeavor, and yet so many of the fundamental concepts of object-oriented programming can come together to build elegant, flexible, and maintainable solutions.

This concludes Part 1 of the book. In the next part of the book, we’ll look at how testing can give you the safety and freedom needed to safely refactor your code and move forward with confidence that your changes have improved the application without breaking anything.

Questions

  1. Does your code follow a well-structured and consistent namespace hierarchy with not too many or too few classes in each namespace?
  2. Is there any part of your code that might be improved by using inheritance to promote code reuse?
  3. Can you think of any repetitive rules or other structures in your code that might benefit from polymorphism?

Further reading

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Refactoring with C#
Published in: Nov 2023Publisher: PacktISBN-13: 9781835089989
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Matt Eland

Matt Eland is a Microsoft MVP in Artificial Intelligence (AI) who has been working with .NET since 2001. Matt has served as a senior engineer, software engineering manager, and .NET programming instructor. He is currently an AI specialist and senior consultant at Leading EDJE near Columbus, Ohio, where he helps companies with their software engineering and data science needs using C# and related technologies. Matt speaks and writes in his community and co-organizes the Central Ohio .NET Developers Group while pursuing a master's degree in data analytics.
Read more about Matt Eland