Tuesday, March 29, 2016

POODR Notes - Chapter 3: Managing Dependencies


Welcome to the 2nd post in the POODR review series; here, we'll discuss my highlights from Chapter 3: Managing Dependencies. As discussed in the last post, object-oriented programming is about objects and behavior, and the Single Responsibility Principle (SRP) guides us to write objects with their own narrow and clearly delimited scope of data and behavior. In order for the app or program to achieve its mission, it needs those objects to collaborate with each other - this is where managing dependencies comes in, and it is another part of writing classes that are TRUE (Transparent, Reasonable, Useable, and Exemplary).

Identifying Dependencies

Your object has a dependency whenever it has to rely on another object in order to do its own work. To identify these situations, consider whether an object knows the:
  • Name of another class,
  • Name of a message/method it intends to send to someone other than self,
  • Arguments that a message/method requires, or
  • Order of the arguments required.
Each of these kinds of "knowledge" represent a dependency; we call it "managing" dependencies because some reliance on other objects is expected and required, but we don't want it to get out of hand. So if your object knows any of the things in that list, it knows too much, and refactoring requires a harm reduction approach. POODR describes various techniques for managing dependencies and making code as TRUE as possible.

Injecting Dependencies

One way to manage a dependency is to inject it by passing the object in as a parameter. Building the method this way abstracts out references to the type of the object so we can focus on the behavior we are trying to get to, instead of on the kind of thing that can do that behavior. For our examples, we'll continue within the domain of the fully automated library that runs on Ruby.

Here we are writing the signature so that we are passing in an object that responds to length, width, and depth. This makes our code re-useable; if we expand our library collection to include DVDs, our code still works and nothing has to change (except maybe the name of the argument; something like "storable" would work nicely). This way, we are still expecting Shelf to know the name of a message (length) it wants to send to something else, but it's not concerned with what kind of thing that something else is - anything that can respond to that message will work.

Removing Argument-Order Dependencies - WITH RUBY 2.0 UPDATE!!

This technique addresses the 3rd and 4th kind of dependency traps in our list above, and it's pretty straightforward, as Metz describes it, and even more so thanks to updates in Ruby 2.0 and 2.1. In previous versions, the way to get away from argument-order dependencies was to use a hash, which also had the benefit of giving us a way to assign default values, as demonstrated in the example below:

Metz pointed out that even though this adds some verbosity, adding a few extra lines of code for the sake of clarity and reusability is warranted. However, in Ruby versions 2.0 and greater, we can get this functionality for free, thanks to first-class support for keyword arguments. Version 2.1 added in support for required keyword arguments. Read this great post that explains more about Ruby 2+ keyword args, and see the gist below for the updated syntax for our Book initialize method.

Abstracting External Messages with Wrapper Methods

Yet another way to deal with dependencies, especially in cases where we can only change certain parts of the code we are using, is to make them more explicit by abstracting into a wrapper method whatever behavior we are trying to get at - I think of this as leaving a warning sign for the next developer. Metz doesn't talk about this yet, but this will also help with testing; we'll discuss this more in future posts.

This is a bit of a contrived example, but the idea is that we want to compose our methods so that we're relying on messages sent to self as much as possible. In the version of class without a wrapper, buried in the complicated store method is a message sent to book. This is potentially dangerous because it is easy to overlook that external message in a method with a lot of other things going on. In order to be able to focus on just the essential work of the store method, we can abstract out the message sent to book, as we do in the second version of Shelf.

Conclusion

So far, we can sum up what we've learned from POODR this way: TRUE code is a pleasure to work on and relatively inexpensive for the business - this is our aim. If the code is TRUE, a class has one and only one responsibility, and when it must collaborate with other objects, it should know as little as possible about its neighbor in order to get the job done. It may know the name of a message it wants to send to its neighbor, but having to know the order of arguments for that message is asking too much. Using the techniques in this post, we can manage those dependencies and go on blissfully changing our code whenever we get a new requirement.

No comments:

Post a Comment