16

I am trying to learn GRASP and I found this explained (here on page 3) about Low Coupling and I was very surprised when I found this:

Consider the method addTrack for an Album class, two possible methods are:

addTrack( Track t )

and

addTrack( int no, String title, double duration )

Which method reduces coupling? The second one does, since the class using the Album class does not have to know a Track class. In general, parameters to methods should use base types (int, char ...) and classes from the java.* packages.

I tend to diasgree with this; I believe addTrack(Track t) is better than addTrack(int no, String title, double duration) due to various reasons:

  1. It is always better for a method to as fewer parameters as possible (according to Uncle Bob's Clean Code none or one preferably, 2 in some cases and 3 in special cases; more than 3 needs refactoring - these are of course recommendations not holly rules).

  2. If addTrack is a method of an interface, and the requirements need that a Track should have more information (say year or genre) then the interface needs to be changed and so that the method should supports another parameter.

  3. Encapsulation is broke; if addTrack is in an interface, then it should not know the internals of the Track.

  4. It is actually more coupled in the second way, with many parameters. Suppose the no parameter needs to be changed from int to long because there are more than MAX_INT tracks (or for whatever reason); then both the Track and the method need to be changed while if the method would be addTrack(Track track) only the Track would be changed.

All the 4 arguments are actually connected with each other, and some of them are consequences from others.

Which approach is better?

Random42
  • 10,370
  • 10
  • 48
  • 65
  • 2
    Is this a document that was put together by a professor or trainer? Based on the URL of the link you provided, it looks like it was for a class, though I don't see any credit in the document as to who created it. If this was part of a class, I'd suggest you ask these questions of the person who provided the document. I do agree with your reasoning, by the way - it would seem apparent to me that an Album class would want to inherently know about a Track class. – Derek Jun 27 '13 at 15:58
  • Honestly, whenever I read about "Best Practices" I take them with a grain of salt! – Khaled Alshaya Jun 27 '13 at 15:59
  • @Derek I found the document by searching Google for "grasp patterns example"; I do not who wrote it but since it was from a university I believe it is reliable. I am looking for an example based on the information given and ignoring the source. – Random42 Jun 27 '13 at 16:03
  • 4
    @m3th0dman "but since it was from a university I believe it is reliable." For me because it is from a university, I consider it unreliable. I don't trust someone who hasn't worked on multiyear projects talking about best-practices in software development. – Khaled Alshaya Jun 27 '13 at 16:05
  • 1
    @AraK Reliable does not mean unquestionable; and that's what I'm doing here, questioning it. – Random42 Jun 27 '13 at 16:06
  • [GRASP](http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)) is apparently something that Craig Larman conceived of, or at least talked about, according to Wikipedia. The paper mimics, but expands on the Wiki page. – JustinC Jun 27 '13 at 16:17
  • No option for Album being a Track factory, the created Track object which you then fill in through the ITrack interface? Maybe I misunderstand the scope of the question. – Patrick Hughes Jun 27 '13 at 17:26
  • As usual, toy examples don't demonstrate the concept very well. Especially in this case since track is probably just a data container. However, if track was implemented using a number of other classes, who could also be pulling in other classes then coupling becomes a big issue. If I want to reuse the Album class somewhere else, but I have to bring the track class and all the things it uses with it, it becomes a royal pain. IMO, coupling means little if the class is only ever used for the 1 specific situation it was created for. It's when you try to use it elsewhere then it's a big deal. – Dunk Jun 27 '13 at 17:58

6 Answers6

15

Well, your first three points are actually about other principles than coupling. You always have to strike a balance between oft-conflicting design principles.

Your fourth point is about coupling, and I strongly agree with you. Coupling is about the flow of data between modules. The type of the container that data flows in is largely immaterial. Passing a duration as a double instead of as a field of a Track doesn't obviate the need to pass it. The modules still need to share the same amount of data, and still have the same amount of coupling.

He is also failing to consider all the coupling in the system as an aggregate. While introducing a Track class admittedly adds another dependency between two individual modules, it can significantly reduce the coupling of the system, which is the important measure here.

For example, consider an "Add to Playlist" button and a Playlist object. Introducing a Track object could be considered to increase coupling if you only consider those two objects. You now have three interdependent classes instead of two. However, that is not the entirety of your system. You also need to import the track, play the track, display the track, etc. Adding one more class to that mix is negligible.

Now consider needing to add support for playing tracks over the network instead of just locally. You just need to create a NetworkTrack object that conforms to the same interface. Without the Track object, you would have to create functions everywhere like:

addNetworkTrack(int no, string title, double duration, URL location)

That effectively doubles your coupling, requiring even modules that don't care about the network-specific stuff to nevertheless still keep track of it, in order to be able to pass it on.

Your ripple effect test is a good one to determine your true amount of coupling. What we are concerned with is limiting the places a change affects.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • 1
    + Coupling to primitives is still coupling no matter how it's sliced. – JustinC Jun 27 '13 at 18:29
  • +1 for mentioning the add a URL option / ripple effect. – user949300 Jun 27 '13 at 18:46
  • 4
    +1 Interesting read on this would also be the discussion of the Dependency Inversion Principle in [DIP in the wild](http://martinfowler.com/articles/dipInTheWild.html) where the use of primitive types is actually seen as a [Primitive Obsession](http://c2.com/cgi/wiki?PrimitiveObsession) "smell" with [Value Object](http://c2.com/cgi/wiki?ValueObject) as the fix. To me that sounds like it would be better to pass a Track object that a whole gaggle of primitive types... And if you want to avoid dependency on / coupling with specific classes, use interfaces. – Marjan Venema Jun 27 '13 at 20:03
  • Accepted answer because of nice explaining about the difference between total system coupling and modules coupling. – Random42 Jun 28 '13 at 13:14
10

My recommendation is:

Use

addTrack( ITrack t )

but be sure that ITrack is an interface and not a concrete class.

Album doesn't know the internals of ITrack implementors. It's only coupled to the contract defined by the ITrack.

I think this is the solution that generates the least amount of coupling.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
  • 1
    I believe that Track is just a simple bean/data transfer object, where it only has fields and getters/setters over them; is an interface required in this case? – Random42 Jun 27 '13 at 16:20
  • 6
    Required? Probably not. Suggestive, yes. The concrete meaning of a track can and will evolve, but what the consuming class requires from it probably will not. – JustinC Jun 27 '13 at 16:21
  • 2
    @m3th0dman Always depend on abstractions, not on concretions. That applies regardless of `Track` being dumb or smart. `Track` is a concretion. `ITrack` interface is an abstraction. That way you will be abled to have different kinds of Tracks in the future, as long as they comply with `ITrack`. – Tulains Córdova Jun 27 '13 at 16:23
  • 4
    I agree with the idea, but lose the 'I' prefix. From Clean Code, by Robert Martin, page 24: "The preceding I, so common in today's legacy wads, is a distraction at best and too much information at worst. I don't want my users knowing that I'm handing them an interface." – Benjamin Brumfield Jun 27 '13 at 18:10
  • 1
    @BenjaminBrumfield You are right. I don't like the prefix either, although I will leave in the answer for clarity. – Tulains Córdova Jun 27 '13 at 19:07
4

I would argue that the second example method most likely increases coupling, since it most likely is instantiating a Track object and storing it in the current Album object. (As suggested in my comment above, I would assume it to be inherent that an Album class would have the concept of a Track class somewhere inside it.)

The first example method assumes that a Track is getting instantiated outside the Album class, so at the very least, we can assume that the instantiation of the Track class is not coupled to the Album class.

If best practices suggested that we never have one class reference a second class, the entirety of object-oriented programming would be thrown out the window.

Derek
  • 328
  • 3
  • 13
  • I don't see how having an implicit reference to another class makes it more coupled than having an explicit reference. Either way, the two classes are coupled. I do think it's better to have the coupling be explicit, but I don't think it's any "more" coupled either way. – TMN Jun 27 '13 at 17:08
  • 1
    @TMN, the extra coupling is in how I imply that the second example would probably end up internally creating a new Track object. The instantiation of the object is being coupled to a method that should otherwise just be adding a Track object to some sort of list in the Album object (breaking the Single Responsibility Principle). Should the way that the Track gets created ever need to be changed, the addTrack() method would also need to be changed. This is not so in the case of the first example. – Derek Jun 27 '13 at 17:24
3

Coupling is just one of many aspects to try to obtain in your code. By reducing coupling, you're not necessarily improving your program. In general, this is a best practice, but in this particular instance, why shouldn't Track be known?

By using a Track class to be passed to Album, you are making your code easier to read, but more importantly, as you mentioned, you're turning a static list of parameters into a dynamic object. That ultimately makes your interface far more dynamic.

You mention that encapsulation is broke, but it is not. Album must know the internals of Track, and if you did not use an object, Album would have to know each and every piece of information passed to it before it could make use of it all the same. The caller must know the internals of Track as well, since it must construct a Track object, but the caller must know this information all the same if it were passed directly to the method. In other words, if the advantage of encapsulation is not knowing an object's contents, it could not possibly be used in this case since Album must make use of Track's information just the same.

Where you wouldn't want to use Track is if Track contains internal logic that you wouldn't want the caller to have access to. In other words, if Album were a class that a programmer using your library were to use, you wouldn't want him to use Track if you use it to say, call a method to persist it on the database. The true problem with this lies in the fact that the interface is entangled with the model.

To fix the problem, you would need to separate Track into its interface components and its logic components, creating two separate classes. To the caller, Track becomes a light class which is meant to hold information and offer minor optimizations (calculated data and/or default values). Inside Album, you would use a class named TrackDAO to perform the heavy lifting associated with saving the information from Track to the database.

Of course, this is just an example. I'm sure this is not your case at all, and so feel free to use Track guilt-free. Just remember to keep your caller in mind when you're constructing classes and to create interfaces when required.

Neil
  • 22,670
  • 45
  • 76
3

Both are correct

addTrack( Track t ) 

is better (as you already argumented) while

addTrack( int no, String title, double duration ) 

is less coupled because the code that uses addTrack does not need to know that there is a Track class. Track can be renamed for example without the need to update the calling code.

While you are talking about more readable/maintainable code the article is talking about coupling. Less coupled code is not necessarily easier to implement and to understand.

k3b
  • 7,488
  • 1
  • 18
  • 31
3

Low Coupling doesn't mean No Coupling. Something, somewhere, has to know about objects elsewhere in the codebase, and the more you reduce dependence on "custom" objects, the more reasons you give for code to change. What the author you cite is promoting with the second function is less coupled, but also less object-oriented, which is contrary to the entire idea of GRASP as being an object-oriented design methodology. The whole point is how to design the system as a collection of objects and their interactions; avoiding them is like teaching you how to drive a car by saying you should ride a bike instead.

Instead, the proper avenue is to reduce dependence on concrete objects, which is the theory of "loose coupling". The fewer definite concrete types a method has to have knowledge of, the better. Just by that statement, the first option is actually less coupled, because the second method taking the simpler types must know about all of those simpler types. Sure they're built-in, and the code inside the method may have to care, but the signature of the method and the method's callers most definitely do not. Changing one of these parameters relating to a conceptual audio track is going to require more changes when they're separate versus when they're contained in a Track object (which is the point of objects; encapsulation).

Going one step further, if Track were expected to be replaced with something that did the same job better, perhaps an interface defining the requisite functionality would be in order, an ITrack. That could allow for differing implementations such as "AnalogTrack", "CdTrack" and "Mp3Track" that provided additional information more specific to those formats, while still providing the basic data exposure of ITrack that conceptually represents a "track"; a finite sub-piece of audio. Track could similarly be an abstract base class, but this requires you to always want to use the implementation inherent in Track; reimplement it as BetterTrack and now you have to change out the expected parameters.

Thus the golden rule; programs and their code components will always have reasons to change. You can't write a program that will never require editing code you've already written in order to add something new or modify its behavior. Your goal, in any methodology (GRASP, SOLID, any other acronym or buzzword you can think of) is simply to identify the things that will have to change over time, and design the system so that those changes are as easy to make as possible (translated; touching as few lines of code and affecting as few other areas of the system beyond the scope of your intended change as possible). Case in point, what's most likely to change is that a Track will gain more data members that addTrack() may or may not care about, not that Track will be replaced with BetterTrack.

KeithS
  • 21,994
  • 6
  • 52
  • 79