Introduction

In object-oriented programming, the design and architecture of your code can significantly impact its maintainability, flexibility, and readability. As you build complex systems in Ruby, it's crucial to understand and avoid common design pitfalls that can lead to convoluted codebases. In this lesson, we will explore two common pitfalls: the use of fluent interfaces and the excessive use of inheritance over composition. By adopting recommended best practices, you will be equipped to write cleaner, more efficient code with distinct and manageable class responsibilities.

Avoid fluent interfaces

A fluent interface is an object-oriented API that aims to improve the readability of the source code through method chaining. While there are some contexts, frequently builder objects, where this pattern reduces verbosity (such as query building in databases), it often comes with several drawbacks:

  • Breaks encapsulation
  • Complicates the use of decorators
  • Is harder to mock in a test suite
  • Makes diffs of commits harder to read

Here is an example to illustrate the downsides of fluent interfaces:

Example of problematic usage
Improved example without fluent interfaces

By avoiding fluent interfaces, the code becomes simpler, and the individual responsibilities of each method and class are clearer, thus improving code maintainability.

Prefer composition over inheritance

As stated in well-established design principles, favor composition over inheritance when possible. There are good reasons to choose either inheritance or composition, but the essential idea is to consider whether composition can model your problem effectively rather than defaulting to inheritance. Here's when inheritance might be more appropriate than composition:

  • Your inheritance represents an "is-a" relationship rather than a "has-a" relationship (e.g., Human is an Animal vs. User has UserDetails).
  • You can reuse code from base classes (e.g., Humans can move like all animals).
  • You want to make global changes to derived classes by altering a base class (e.g., change the caloric expenditure of all animals when they move).
Example of problematic usage of inheritance
Improved example using composition

By employing composition, the Employee class delegates the responsibility of handling tax data to a separate class, EmployeeTaxData, making the design more flexible and easier to manage.

Summary

In this lesson, we explored common design pitfalls in object-oriented programming, focusing on the drawbacks of fluent interfaces and the misuse of inheritance. We emphasized the importance of avoiding fluent interfaces to maintain clarity, simplicity, and testability in your code. Additionally, we highlighted the advantages of preferring composition over inheritance, using clear examples to demonstrate how composition can lead to more flexible and maintainable designs. By applying these principles, you can enhance the maintainability and scalability of your Ruby applications.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal