11

Following on from this thread I started

The Singleton Pattern

It got me thinking about how coupled my classes are and how best to achieve loose coupling. Please bear in mind I am a new programmer (4 months into my first job) and this is really the first consideration I have given to this, and am very keen to understand the concept.

So, what exactly constitutes loose coupling vs heavy coupling? In my current(and first project), I am working on a c# winforms project, where the GUI section creates objects and subcribes to their events, when they are triggered, the GUI creates another object (in this example, a datagridview (a class that I have created that wraps around a standard datagridview and adds additional functionality) and attaches it to the GUI. Is this bad coupling or good?

I really don't want to get into bad habits and start coding poorly, hence I would appreciate your responses.

7 Answers 7

11

TO make your code loosely coupled here are a few simple things to remember:

Part 1:

Technically known as "Separation of Concern". Each class has a specific role, it should be handling business logic or application logic. Try and steer clear of class that combine both responsibilities. i.e. A class that manages (broad term) data is application logic while a class that uses data is business logic.

Personally I refer to this (in my own little world) as create it or use it. A class should create an object or use an object it should never do both.

Part 2:

How to implement separation of concern.
As a starting point there are two simple techniques:

Note: Design patterns are not absolute.
They are supposed to be customized to the situation but have an underlying theme that is similar to all applications. So don't look at the examples below and say that I must follow this rigidly; these are just examples (and slightly contrived at that).

Dependency Injection:

This is where you pass in an object that a class uses. The object that you pass in based on an interface so your class knows what to do with it but does not need to know the actual implementation.

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

Here we inject the stream into a Tokenizer. The tokenizer does not know what type the stream is as long as it implements the interface of std::istream.

Service Locator Pattern:

The service locator pattern is a slight variation on dependency injection. Rather than give an object that it can use, you pass it an object that knows how to locate(create) the object you wants to use.

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

Here we pass the application object a persistor object. When you perform a save/load action it uses the persistor to create an object that actually knows how to do the action. Note: Again the persistor is an interface and you can provide different implementations depending on the situation.

This is useful when a potentially unique object is required each time you instantiate an action.

Personally I find this is particularly useful in writing unit tests.

Note of Patterns:

Design patterns is a huge subject unto itself. This is by no means an exclusive list of patterns you can use to help with loose coupling; this is just a common starting point.

With experience you will realize you are already using these patterns its just that you did not use their formal names. By standardizing their names (and getting everybody to learn them) we find that it is easy and quicker to communicate ideas.

7
  • 3
    @Darren Young: Thanks for accepting it. But your question is only three hours old. I would come back in a day or so a verify that others have not provided better answers. Commented Jan 18, 2011 at 14:15
  • Thanks for the excellent response. Regarding seperation of concern....I have classes that require some data for it's use and then do something with that data. This is how in the past I have designed classes. Would it therefore be better to create a class that obtains the data and adapts it to the correct form and then another class to actually use the data? Commented Jan 18, 2011 at 14:16
  • @Darren Young: As with all programing the line is grey and blurry not well defined. Its hard to give an exact response without reading your code. But when I talk about managing the data I am referring to the variables (not the actual data). So things like pointers need to be managed so that they do not leak. But data can be injected or how the data is retrieved can be abstracted (so that your class can be re-used with different data retrieval methods). I am sorry I can not be more precise. Commented Jan 18, 2011 at 14:23
  • 1
    @Darren Young: As noted by @StuperUser in his answer. Don't go overboard (AKA minutae of loose coupling (love that word minutae)). The secret of programming is to learn when to use the techniques. Overuse can lead to a tangle of code. Commented Jan 18, 2011 at 14:43
  • @Martin - thanks for the advice. I think that's where I am struggling at present.....I tend to constantly worry about the architecture of my code and also trying to learn the specific language I am using C#. I guess it will come with experience and my desire to learn this stuff. I appreciate your comments. Commented Jan 18, 2011 at 14:50
6

I'm a ASP.NET developer so don't know much about WinForms coupling, but know a little from N-Tier web apps, assuming a 3 tier application architecture of UI, Domain, Data Access Layer (DAL).

Loose coupling is about abstractions.

As @MKO states if you can replace an assembly with another (e.g. a new UI project that uses your Domain project, a new DAL that saves to a spreadsheet rather than a database) then there is loose coupling. If your Domain and DAL depend on projects further down the chain, then the coupling could be looser.

One aspect of something being loosely coupled is whether you can replace an object with another that implements the same interface. It doesn't depend on the actual object, but the abstract description of what it does (its interface).
Loose coupling, interfaces and Dependency Injectors (DI) and Inversion of Control (IoC) are useful for the isolation aspect of designing for testability.

E.g. An object in the UI project calls a Repository object in the Domain project.
You can create a fake object that implements the same interface as the repository that the code under test uses, then write special behaviour for tests (stubs for preventing production code that saves/deletes/gets being called and mocks that act as stubs and keep track of the state of the fake object for testing purposes).
The means the only production code being called is now only in your UI object, your test will be against that method only and any test failures will isolate the defect to that method.

Also, in the Analyse menu in VS (depending on the version you have) there are tools to Calculate Code Metrics for your project, one of which is Class Coupling, there will be more information on this in the MSDN documentation.

Don't get TOO bogged down in the minutae of loose coupling though, if there's NO chance that things are to get reused (e.g. a domain project with more than one UI) and the life of the product is small, then loose coupling becomes less of priority (it will still be taken into account), but it will still be the responsibilty of architects/tech leads who will be reviewing your code.

3

Coupling refers to the degree of direct knowledge that one class has of another. This is not meant to be interpreted as encapsulation vs. non-encapsulation. It is not a reference to one class's knowledge of another class's attributes or implementation, but rather knowledge of that other class itself. Strong coupling occurs when a dependent class contains a pointer directly to a concrete class which provides the required behavior. The dependency cannot be substituted, or its "signature" changed, without requiring a change to the dependent class. Loose coupling occurs when the dependent class contains a pointer only to an interface, which can then be implemented by one or many concrete classes. The dependent class's dependency is to a "contract" specified by the interface; a defined list of methods and/or properties that implementing classes must provide. Any class that implements the interface can thus satisfy the dependency of a dependent class without having to change the class. This allows for extensibility in software design; a new class implementing an interface can be written to replace a current dependency in some or all situations, without requiring a change to the dependent class; the new and old classes can be interchanged freely. Strong coupling does not allow this. Ref Link.

3

Have a look at the 5 SOLID principles. Adhering to the SRP, the ISP and the DIP will signifficantly lower coupling, the DIP being by far the most powerful one. It is the fundamental principle beneath the already mentioned DI.

Also, GRASP is worth taking a look at. It is a strange mix between abstract concepts (you will find hard to implement at first) and concrete patterns (that might actually be of help), but beauty is probably the least of your concerns right now.

And last, you might find this section on IoC quite useful, as an entry point to common techniques.

In fact, I found a question on stackoverflow, where I demonstrate the application of SOLID on a concrete problem. Might be an interesting read.

1

According to Wikipedia:

In computing and systems design a loosely coupled system is one where each of its components has, or makes use of, little or no knowledge of the definitions of other separate components.

The problem with tight coupling makes it hard to make changes. (Many writers seem to suggest that this primarily causes problems during maintenance, but in my experience it is relevant during initial development, too.) What tends to happen in tightly coupled systems is that a change to one module in the the system requires additional changes in the modules to which it is coupled. More often than not, this requires more changes in other modules and so on.

In contrast, in a loosely coupled system, changes are relatively isolated. They are therefore less costly and can be made with greater confidence.

In your particular example, the event handling stuff does provide some separation between the GUI and the underlying data. However, it does sound as though there are other areas of separation that you could explore. Without more details of your particular situation it is difficult to be specific. However, you might begin with a 3-tier architecture that separates out:

  • Data storage and retrieval logic
  • Business logic
  • Logic required to run the UI

Something to consider is that for small, one-off applications with a single developer, the benefits of enforcing loose coupling at every level may not be worth the effort. On the other hand, larger more complex applications with multiple developers they are a must. Initially, there is a cost incurred in introducing the abstractions and educating developers unfamiliar with the code regarding its architecture. In the long run, however, loose coupling offers advantages that far outweigh the costs.

If you're serious about designing loosely coupled systems, read up on SOLID principles and design patterns.

The important thing to realise, however, is that these patterns and principles are just that - patterns and principles. They are not rules. This means that they need to be applied pragmatically and intelligently

Where patterns are concerned, it is important to understand that there is no single "correct" implementation of any of the patterns. Nor are they cookie-cutter templates for designing your own implementation. They are there to tell you what shape a good solution might have, and to provide you with a common language for communicating design decisions with other developers.

All the best.

1

Use dependency injection, strategy patterns and events. In general: read up on design patterns, they're all about loose coupling and reducing dependencies. I'd say events are about as loosely coupled as you'd get while dependency injection and strategy patters require some interfaces.

A good trick is to place classes in different libraries/assemblies, and have them depend on as few other libraries as possible, that will force you to refactor to use less dependencies

8
  • 4
    Is condom some abstract IT term or am I simply not getting the pun? Commented Jan 18, 2011 at 10:59
  • 3
    It's another name for dependency injection container.
    – Mchl
    Commented Jan 18, 2011 at 11:37
  • 2
    Also a great way to prevent the spread of viruses. Are we talking about the same thing?
    – sova
    Commented Jan 18, 2011 at 11:40
  • 1
    Heavy coupling is often done on the backend
    – Homde
    Commented Jan 18, 2011 at 11:44
  • 1
    Setting an headline is hardly abusing formatting
    – Homde
    Commented Jan 18, 2011 at 14:34
0

Let me provide an alternate view. I just think of this in terms each class being a good API. The order the methods are called in is obvious. What they do is obvious. You've reduced the number of methods to the minimal needed. For examples,

init, open, close

versus

setTheFoo, setBar, initX, getConnection, close

The first is obvious and looks like nice API. The second might cause errors if called in the wrong order.

I don't worry too much about having to modify and recompile callers. I maintain a LOT of code some of it new and some 15 years old. I usually want compiler errors when I make changes. Sometimes I'll purposely break an API for that reason. It gives me a chance to consider the ramifications on each caller. I'm not a big fan of dependency injection because I want to be able to visually trace through my code without black boxes and I want the compiler to catch as many errors as possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.