xxxxxxxxxx
// Coupling = How much work is involved to change something
// Tight coupling example1 = engine is tightly coupled with car (changing engine requires strict specification)
// Loose coupling example1 = Tire is loosely coupled with car (changing tire does not require too much specification)
// Tight coupling example2 = PC is tightly coupled with home (Bringing a PC with you at work is stupid)
// Loose coupling example2 = Laptop is loosely coupled with home (Bringing a laptop with you a work is too easy)
// Tight coupled code has a class hard-coded with it (Less accepting to change in requirements!!!)
// Loose coupled code has an interface hard-coded with it (Totally disconnected from the class. change does not affect the code)
// Tight coupling (No interface)
// MarioGame.java
public class MarioGame{
public void up() { System.out.println("Jump");}
public void down() {System.out.println("Go into a hole");}
}
// SuperContraGame.java
public class SuperContraGame{
public void up() { System.out.println("up");}
public void down() { System.out.println("Sit down");}
}
// GameRunner.java (Takes instance of a Game and run that game)
public class GameRunner {
private MarioGame game; // Hard-coded instance of MarioGame
public GameRunner(MarioGame game) {
// Only instance of MarioGame can be run, running SuperContraGame instance will requires either new parameter or change the existing parameter class "MarioGame" to "SuperContraGame"
// Penny for a thought :: if there are 1000 different game class (eg: PacmanGame, CadillacsGame), should you create 1000 different "GameRunner" class for each?????
// DRY : use a way so that only one parameter can handle 1000 different classes
this.game = game;
}
public void run() {
game.up();
game.down();
}
}
// AppGameRunner.java
public class AppGameRunner {
public static void main(String[] args) {
var mario = new MarioGame();
var contra = new SuperContraGame();
var gameRunner1 = new GameRunner(mario); // Successfully run mario game. Gamerunner knows everything about MarioGame class.
var gameRunner2 = new GameRunner(contra); // compiler error. Gamerunner knows nothing about SuperContraGame class
gameRunner1.run(); // ok
gameRunner2.run(); // error!!!
}
}
// Loose coupling (Introducing interface)
// GameInterface.java
public interface GameInterface {
void up();
void down();
}
// MarioGame.java
public class MarioGame implements GameInterface{
public void up() { System.out.println("Jump");}
public void down() {System.out.println("Go into a hole");}
}
// SuperContraGame.java
public class SuperContraGame implements GameInterface{
public void up() { System.out.println("up");}
public void down() { System.out.println("Sit down");}
}
// GameRunner.java (Takes instance of a Game and runs that game)
public class GameRunner {
private GameInterface game; // TYPE is now coded to interface instead of a classname
public GameRunner(GameInterface game) { // object of any class that implements GameInterface can now be provided as dependency
this.game = game;
}
public void run() {
game.up();
game.down();
}
}
// AppGameRunner.java
public class AppGameRunner {
public static void main(String[] args) {
var mario = new MarioGame();
var contra = new SuperContraGame();
var gameRunner1 = new GameRunner(mario); // Successfully run mario game. Gamerunner has no information about MarioGame class, but it knows whatever method is required, the instance can successfully provide them
var gameRunner2 = new GameRunner(contra); // Successfully run supercontra game. Gamerunner has no information about SuperContraGame class, but it knows whatever method is required, the instance can successfully provide them
gameRunner1.run(); // ok
gameRunner2.run(); // ok
}
}