how interfaces are used for loose coupling?

2019-08-12 12:13发布

问题:

I don't seem to grasp the concept of how interfaces will implement loose coupling? You might find this question to be a duplicate of some other question but I've read many answers related to this topic and I haven't found a satisfactory explanation.

Below is an example of how many developers implement loose coupling.

interface shape {
   public function sayName();
}

class Circle implements shape {

     public function sayName(){
          echo 'Hello my name is circle';
      }
}

class Square implements shape {

     public function sayName(){
          echo 'Hello my name is square';
      }
}

class Creator {
       public $shape = null;
       public function __construct(Shape $shape){
          $this->shape = $shape;
       }
}

$circle = new Creator(new Circle());
$circle->sayName();

$square = new Creator(new Square());
$square->sayName();

In the above example we are using polymorphism of interface to achieve loose coupling. But I don't see that this code is loosely coupled. In the above example the calling code (client) is directly referring to "Circle" and "Square" classes using "new" operator hence creating tight coupling.

To solve this problem we can do something like this.

interface shape { public function sayName(); }

class Circle implements shape {

     public function sayName(){
          echo 'Hello my name is circle';
      }
}

class Square implements shape {

     public function sayName(){
          echo 'Hello my name is square';
      }
}

class Creator {
       public $shape = null;
       public function __construct($type){
          if(strtolower($type) == 'circle'){
             $this->shape = new Circle();
          } else if(strtolower($type) == 'square'){
             $this->shape = new Square();
          } else {
             throw new Exception('Sorry we don\'t have '.$type.' shape');
          }
       }
}

$circle = new Creator('Circle');
$circle->sayName();

$square = new Creator('Square');
$square->sayName();

This example fixes the problems of previous example but we don't use interface at all.

My question is why should I use interface if I can implement loose coupling without it? what benefits would the interface offer in the above scenario? or what problems would I face if I don't use interface in the above example?

Thanks for your help.

回答1:

As others have noted, what you're doing is more along the lines of dependency injection, which is a related, but separate topic from loose coupling. I'll try to give some insight into the simple application of loose coupling via interfaces.

I tend to think of interfaces as a handy way of defining/enforcing contracts. Instead of the simplistic example in which every shape can say hello, consider a case where you need to render each shape to an image.

This pseudo-code shows how you might deal with Squares and Circles with no interface.

class Circle {
  function renderCircle() { ... }
}

class Square {
  function renderSquare() { ... }
}

// then when we use them
Circle[] circlesArray = loadCircles
Square[] squaresArray = loadSquares

foreach(circle in circlesArray){
  circle.renderCircle
}

foreach(square in squaresArray){
  square.renderSquare
}

If instead, we say that we don't so much care about what type of shape it is, but only that you can render it, we end up with the following interface:

interface renderableShape {
  function render()
}

And so on cases where you are only concerned about the ability to render the shape, you program against that interface.

You could have something like this:

function renderShapes(Array[renderableShape] shapes){
  foreach(shape in shapes){
    shape.render()
  }
}

You're now ensuring that your renderShapes function has no visibility into any of the specific details of the Circle or Square implementations. All it needs to know is that you can call render.