Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A checklist to help you apply the principles described in Practical Object-Oriented Design in Ruby to your code.

Class Cohesion (Single Responsibility Principle)

  • Pretend you're talking to a sentient instance of your class. Pretend each of its methods is a question you're asking it. Do those questions all make sense? ("Mr. Gear, what is your tire size?" probably does not.)
  • Describe the class's purpose in one sentence. If that sentence contains an "and" or an "or", it probably has more than one responsibility.
  • Classes should call their own attribute reader and writer methods instead of reading and writing instance variables directly. (Controllers might be an acceptable exception to this rule.)
  • Are there any complex data structures (2-dimensional arrays, etc.) that should be encapsulated in classes with named accessor methods?
  • Are there any methods with more than one responsiblity? If you break them down into smaller methods, the new methods may be self-documenting (through their names) and may reveal additional beneficial refactorings.

Managing Dependencies

"An object depends on another object if, when one object changes, the other might be forced to change in turn."

  • Do classes get instantiated within your code (thus creating a dependency on one particular class)? Consider using dependency injection so that you can also use other classes with the same interface (using duck typing).
  • If your class has to reference another class or its methods directly, try isolating those references in separate methods. If the other class or its methods change later, you'll only have to change one method of yours.
  • Use keyword arguments to avoid creating a dependency on a specific argument count or order.
  • When you have a choice about dependency direction (which class depends on which), the class that is more likely to change (an immature or concrete class) should depend on the class that is less likely to change (a mature or abstract class).

Creating Flexible Interfaces

"Classes implement methods; some of those methods are intended to be used by others, and these methods make up its public interface."

  • Public methods should be stable. Isolate code that is more likely to change in private methods.
  • Public methods should allow the sender to ask for what it wants, not describe how to do it. E.g.: clean(bike), lube(bike), and inspect(bike) should be private; a public prepare(bike) method can call the private methods.
  • Consider whether Law of Demeter violations should be refactored. E.g.: don't write customer.bicycle.wheel.rotate, give Customer a depart method.

Duck Typing

"Duck types are public interfaces that are not tied to any specific class."

  • If your code is passing different messages to objects depending on their class or the methods they respond to, consider adding the same method to all of those objects, so they can all be called using duck typing.

Inheritance

  • If you find instances of your class fall into more than one category, and that their behavior varies based on that category, consider extracting subclasses and placing the unique behavior into those subclasses.
  • A subclass "is-a" specialization of its superclass. A subclass should be everything its superclass is, plus more. If there is behavior in a superclass that is inappropriate for a subclass, consider the following two-part refactoring. (This requires moving code twice, but promoting abstract behavior is less error-prone than trying to demote concrete behavior.)
    • Move all code in the superclass to a new concrete subclass.
    • Promote code that needs to be shared by all subclasses back up to the superclass.
  • If you find yourself repeating code among multiple subclasses but with small changes, try the "Template Method" pattern: move common code to the superclass, and call methods within that code to get subclass-specific contributions.
  • Subclasses sending super creates a dependency on the superclass's method. Instead, consider having the superclass send "hook" messages: methods that subclasses can optionally override to provide subclass-specific contributions.

Composition

  • If a Foo object relies on a Bar object, and you can say that a Foo "has-a" Bar, then Foo can be modeled using composition.
  • The downside of using composition instead of inheritance is that messages must be explicitly passed from the owner object to the objects it's composed of, whereas with inheritance, message passing is automatic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment