The degree of difference between answers here shows why it would be a difficult concept to grasp but to put it as simply as I can describe it:
In order for me to know that if I throw a ball to you, then you can catch it I really dont need to know how old you are. I dont need to know what you ate for breakfast, and I really dont care who your first crush was. All I need to know is that you can catch. If I know this, then I dont care if its you I am throwing a ball to you or your brother.
With non-dynamic languages like c# or Java etc, we accomplish this via Interfaces. So lets say we have the following interface:
public ICatcher
{
public void Catch();
}
And now lets say we have the following classes:
public CatcherA : ICatcher
{
public void Catch()
{
console.writeline("You Caught it");
}
}
public CatcherB : ICatcher
{
public void Catch()
{
console.writeline("Your brother Caught it");
}
}
Now both CatcherA
and CatcherB
implement the Catch
method, so the service that requires a Catcher can use either of these and not really give a damn which one it is. So a tightly coupled service might directly instantiate a catched i.e.
public CatchService
{
private CatcherA catcher = new CatcherA();
public void CatchService()
{
catcher.Catch();
}
}
So the CatchService
may do exactly what it has set out to do, but it uses CatcherA
and will always user CatcherA
. Its hard coded in, so its staying there until someone comes along and refactors it.
Now lets take another option, called dependency injection:
public CatchService
{
private ICatcher catcher;
public void CatchService(ICatcher catcher)
{
this.catcher = catcher;
catcher.Catch();
}
}
So the calls that instantiate CatchService
may do the following:
CatchService catchService = new CatchService(new CatcherA());
or
CatchService catchService = new CatchService(new CatcherB());
This means that the Catch
service is not tightly coupled to either CatcherA
or CatcherB
.
There are several other strategies for loosely coupling services like this such as the use of an IoC framework etc.