+1

Loose Coupling & Dependencies Management

What is depedencies?

Object Oriented applications modeling real world problem by using objects and the interactions between those objects provide solutions. These interactions are inescapable. A single object cannot know everything, so inevitably it will have to talk to another object. Well designed objects have a single responsibility, their very nature requires that they collaborate to accomplish complex tasks. To collaborate, an object must know something know about others. Knowing creates a dependency. If not managed carefully, these dependencies will strangle your application. An object depends on another object if, when one object changes, the other might be forced to change in turn.

class Document
  attr_accessor :title, :content

  def initialize(title, content)
    @title, @content = title, content
  end

  def present
    formater = HTMLFormater.new
    formater.print(title, content)
  end
end

class HTMLFormater
  def print(title, content)
    puts "<h1>#{title}</h1>"
    puts "<p>#{body}</p>"
  end
end

Consider above code snippet in which Document might be force to change because of a change to HTMLFormater. It has at least four dependencies on Document. Document doesn't need to know all of them. It only needs to know enough to do its job. These depedencies make Document harder to change.

Recognizing Dependencies

An object is said to have dependencies when it knows

  1. The name of another class. Document knows and expects HTMLFormater to exist.
  2. The name of the message(method) that it intends to send to someone other that itself(self). Document expects HTMLFormater to respond to new and print.
  3. The arguments that a message requires. Document knows that HTMLFormater.print requires two arguments that is title, and content.
  4. The order of those arguments. Document knows the first argument to HTMLFormater.print must be title and the second one must be content.

Each of these dependencies creates a chance that Document will be forced to change because of a change to HTMLFormater. Some degree of dependency between these two classes is inevitable, after all, they must collaborate, but most of the dependencies listed above are unnecessary. it is your job to recognize those unneeded dependencies and keep each class to have as little as possible.

Writing Loosely Coupled Code

Every dependency is like a little dot of glue that causes your class to stick to the things it touches. A few dots are necessary, but apply too much glue and your application will harden into a solid block. Reducing dependencies means recognizing and removing the ones you don’t need.

Inject Dependencies

The solution to our first problem listed above, instead of referencing other class by its name we inject it as an dependency.

class Document
  attr_accessor :title, :content, :formater

  def initialize(title, content, formater)
    @title = title
    @content = content
    @formater = formater
  end

  def present
    formater.print(title, content)
  end
end

Now consider the new version of code snippet. Document now doesn't tie HTMLFormater. It can work with any other formaters and in fact it can work with any object that respond to print. The obvious consequence of referencing the name of the class directly is that if HTMLFormater change its name then Document.present is force to change as well. In truth, dealing with the name change is a relatively minor issue. You likely have a tool that allows you to do a global find/replace within a project. If HTMLFormater’s name changes to HTMLPrinter, finding and fixing all of the references isn’t that hard. The fact that Document.present above must change if the name of the HTMLFormater class changes is the least of the problems with this code. The more destructive but less visible problem exist. The fact that Document hard code a reference to HTMLFormater means that it is only willing to work with HTMLFormater and refuse to work with other object.

Well Define Public Interface

During the application lifecycle, it is unavoidable for one object to want to send message to another object after all they are here to collaborate. Completely remove the name of the message in this case is impossible, but we can make it into a more easy to manage manner by define a public interface that each object agreed upon. Since ruby is a dynamic language we can take advantage of this by using duck typing which you can read on my previous post.

Remove Argument Order

When you send a message that requires arguments, you, as the sender, cannot avoid having knowledge of those arguments. This dependency is unavoidable. However, passing arguments often involves a second, more subtle, dependency. Many method signatures not only require arguments, but they also require that those arguments be passed in a specific, fixed order. This is quite a common problem especially early on when the design isn't quite nailed down, as you progress on you may go through serveral cycles of changing the arguments list and fixing these after every is quite a hassle. Even worse you may find yourself avoiding making changes even when your design calls for them because you can't bear to pain to change all the depedents yet again.

class HTMLFormater
  def print(options)
    puts "<h1>#{options[:title]}</h1>"
    puts "<p>#{options[:content]}</p>"
  end
end

Using above technique has serveral advantages. First is that is removes depedency on argument order. HTMLFormater is now free to add or remove arguments with the knowledge that no change will have side effects on other code. This technique also has its drawback. We have removed depdency on argument order, but we gained a dependency on argument names. However this depedency is more stable than the old one, thus it also face less risk of being force to change. As a bonus hash key also serve as good documentation for future maintenance.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí