28

Coupling is defined as the knowledge one object has about another one, which describes how dependent they are. The more dependent, the worse, since changes in one would impact in the second. High coupling is bad, low coupling good. There are different coupling types.

Let's assume we talk about call coupling.

In Java, when A creates an object B and calls one of its methods it's said to be tightly coupled. But if we create an interface IB, used from A where B implements IB it's said to be loosely coupled. I don't see why since one change in the interface would have an impact in A and B. And one change in B would have an impact in IB and A. They still seem to be call coupled.

The same rationale applies to the Facade GoF design pattern. It's said it promotes low coupling, since we put an intermediary between a subsystem and the client code. In this case, it looks like we transferred the problem from the client code to the Facade. Since a change in the subsystem would have an impact on the Facade instead of the client code. The client code is no longer coupled to the subsystem but the Facade is.

I don't see how coupling is reduced.

This has been asked in: How is loose coupling achieved using interfaces in Java when an implementation class is mandatory and bound to interface contract?

But the answers are not specific enough. First, since encapsulation is also about treating objects as black boxes, the first answer does not specify any gain by using interfaces compared to regular classes (= tight coupling, in case it's all about black boxes). Therefore the answer is invalid. What interfaces provide is decoupling between the interface and the implementation when multiple implementations exist. But doesn't solve anything related to call coupling. May be the link provided above should add a new category called "implementation coupling". Regardless of this, the solution is still call coupled. In the second answer it mentions data coupling, but as far as I know the issue is about call coupling.

The rest of the answers are irrelevant.

Regarding "Design Patterns - Understanding Facade Pattern", I understand the Facade pattern. I'm only asking about the coupling reduced by the pattern. Which based on my reasoning is not reduced but transferred.

This subject has been treated but no proper answer has been given.

7
  • 11
    You're totally right - interfaces are about polymorphism, not coupling. They remove coupling between a component and a specific concrete implementation of an interface, but not the contract. And yes, this is frequently misunderstood and abused.
    – Ant P
    Feb 17, 2020 at 10:40
  • 6
    You're totally wrong - you've limited your analysis to cases where there is exactly one B, when the answer to your linked question is specifically and explicitly about multiple implementations of a common interface. Consider what A would be call-coupled to if it has to support B1, B2, B3 etc. without a common interface IB.
    – Useless
    Feb 17, 2020 at 10:57
  • 2
    But it's an artificial limitation: you're just saying that "the answer about polymorphism doesn't address this other question no-one asked". Patterns are pragmatic classifications of things that worked in practice, so learning about them in a vacuum does have some pitfalls.
    – Useless
    Feb 17, 2020 at 11:15
  • 1
    Every time one sees "good" (or "bad"), one should ask "good for what, exactly?". If there's time, then ask "and why/how is it good for that?".
    – Pablo H
    Feb 17, 2020 at 12:45
  • 3
    And my point is that limiting your investigation to call coupling with exactly two modules is artificial, and probably uninformative. It's reasonable to say that interface abstraction isn't useful in specific unusual case X, but not to say that no-one justifies it properly because they don't address specific unusual case X.
    – Useless
    Feb 17, 2020 at 12:48

8 Answers 8

30

High coupling is bad, low coupling good.

I won't put it so black and white. Some coupling is necessary. Plus some roundabout ways to get rid of coupling can introduce too much overhead. In particular for applications that need to respond in real time. It is trade-offs.

Yes, you want to avoid high coupling. However, if introducing patterns in an attempt to lower coupling is preventing you from meeting your requirements first (which might include response time, time budgets, etc…) then it is not worth it.


In Java, when A creates an object B and calls one of its methods it's said to be tightly coupled. But if we create an interface IB, used from A where B implements IB it's said to be loosely coupled. I don't see why since one change in the interface would have an impact in A and B. And one change in B would have an impact in IB and A. They still seem to be call coupled.

Yes, they are still call coupled. Well, depends on how you define the metric. However, interfaces are not the right tool if you want to deal with that.

Regardless, with the interfaces, the classes would be loosely coupled in that A is not coupled directly to B. You could have other implementations of the interface.

A common anti-pattern is making an interface that matches what the consumed class offers. You should make an interface that matches what the consumer class needs. See interface segregation. The interface becomes a requirement for consumed class.

If you conceptualize the interface that way, the interface would change only when it is necesary for its consumers. When changing A, you can decide to change the interface or use a new one.

If we decide to change the interface when changing A, the change would propagate from A to B. Instead of it propagating from B to A. However, we do not have to decide to change the interface, or we can introduce an adapter that implement the interface and wraps B. That is, we have opportunities to stop the change from propagating. That is what we want. And that is what loose coupling (which is still coupling) buys us. We design to have more options, not less.

Again, that is not solving call coupling.


The same rationale applies to the Facade GoF design pattern. It's said it promotes low coupling, since we put an intermediary between a subsystem and the client code. In this case, it looks like we transferred the problem from the client code to the Facade. Since a change in the subsystem would have an impact on the Facade instead of the client code. The client code is no longer coupled to the subsystem but the Facade is.

The facade hides whatever you do behind it. It is similar to how an object encapsulates its state. The facade encapsulates a (sub)system. The consumer code only needs to talk to the facade and it is unaware of the details behind it.

Of course, it is still coupled. And yes, you have moved the problem to the Facade. However, thanks to the Facade, the consumer code does not have to change because of the changes of what is behind the facade.


But the answers are not specific enough. First, since encapsulation is also about treating objects as black boxes, the first answer does not specify any gain by using interfaces compared to regular classes (= tight coupling, in case it's all about black boxes)

If you use the class directly, then whatever the consumer code needs must be implemented by the class. If the consumer code uses an interface instead, then it does not have to be implemented by any particular class. You can change the class without the consumer code being aware of it. The consumer code has less knowledge, thus it is less coupled.


What interfaces provide is decoupling between the interface and the implementation when multiple implementations exist. But doesn't solve anything related to call coupling.

Correct, this is not about call coupling. You are the one narrowing the discussion to call coupling between two classes and an interface. And then wondering why they provide nothing.

Interfaces are not the right tool to deal with call coupling. Instead you want an event driven architecture, a consumer subscriber pattern, or similar. That way, there might not even be an implementation on the other side. Of course, some infrastructure might be required, if not provided by the language and runtime. Oh, this is Java, yeah, some infrastructure required.

2
  • 5
    +1: You should make an interface that matches what the consumer class needs.
    – jmoreno
    Feb 17, 2020 at 18:12
  • 4
    +1. Your last paragraph has taught me more about coupling than a few years of software engineering. Feb 18, 2020 at 13:26
10

And one change in B would have an impact in IB and A.

This is not correct. A change to its signature would impact IB and A, but changing its implementation would not. You could change your underlying data store, or the algorithm used to do the operation, or add in some extra logging - all without impacting A.

In this case, it looks like we transferred the problem from the client code to the Facade.

Yes, that is the point. Instead of maintaining the same sort of client code in a variety of places, the Facade serves as an abstraction. Instead of clients knowing how to deal with whatever is behind the Facade (high coupling) they know how to deal with the Facade (which should be lower since the Facade hides implementation details). The Facade knows how to deal with what's behind it, but it is one place, with a well defined contract. That surface area is reduced, allowing you to change the implementation more easily.

4
  • 1
    "...- all without impacting A.". The same applies to encapsulation. I thought interfaces offered sth more when dealing with call coupling. But it seems it doesn't. It only decouples the interface from the implementation for multiple implementations to exist.
    – Asier Naiz
    Feb 17, 2020 at 9:00
  • I might be way off the mark, but would it be worth clarifying what kind of impact is being discussed here? That a change to B, which affected its implementation but not its interface, would not effect A in as far as the relationships can be defined by types? I've seen changes which don't alter the interface but cause breaking changes, and things like the reasoning behind the order of a list are difficult to capture in types. Feb 17, 2020 at 9:58
  • impact in A = change in method calls
    – Asier Naiz
    Feb 17, 2020 at 10:49
  • 3
    @AsierNaiz "Liskov substitution principle". You can write an entirely new class C that implements IB and A wouldn't even notice. That's "low coupling". If you need to change IB every time you change B, your problem is poor interface design, not coupling.
    – Agent_L
    Feb 17, 2020 at 14:21
6

You are absolutely correct that introducing an interface or a facade does not in itself reduce coupling.

Introducing an interface can be used to reduce coupling in a few ways. If multiple classes implement the same interface, then the caller is decoupled from any specifics of the classes. But if the interface is only implemented by a single class, then you don't necessarily get this.

An interface can also decouple the caller from specifics of the class if the interface is on a higher abstraction level or hides some implementation details which is exposed by the class itself. But you don't get that abstraction automatically by introduction an interface, you still have to carefully design the interface for this purpose.

In short, interfaces or facades does not automatically reduce coupling. But they can be used to reduce coupling.

4

You cannot remove required coupling. If you need to call .read() to read from a file, and .close() when you finish, you will continue to do so.

But consider other changes to class File. If anything at all changes in its implementation, you have to recompile any code that uses File, because the compiler has to make sure that anywhere a File instance is mentioned, every method of it is called correctly — even if your code never calls anything but .read() and .close(). If File is used widely, this becomes a pain.

Now consider an interface Readable, which only exposed two methods, .read() and .close().

The File class implements this method. If anything about its implementation, even in the public methods, changes, it does not matter: it still implements Readable, and the compiler checks that.

Your code can now depend on Readable instead of File. You are guaranteed to not use any methods that File exposes but you don't need. Any changes to File's implementation don't matter for your code, too. It does not need to be recompiled. You can just throw a .jar / .dll / .so with the new implementation of File at your code, and it should work.

See, you've just reduced coupling.

2
  • The client code still uses the same methods as before. Regardless File is exposed as a class or via an interface the call coupling remains the same: read and close methods are called. You seem to explain that coupling is reduced reducing the number of exposed methods of File via Readable. But the exposed methods are the dependencym and they were before creating the interface. Nothing changed, coupling is the same.
    – Asier Naiz
    Feb 17, 2020 at 8:38
  • 1
    In this case, both A and B are indeed individually coupled to the Readable interface. But they are not at all coupled to each other. So the total "amount" of coupling doesn't get reduced, but the coupling between A and B does. That's the main point, because now B can change (or disappear completely) without having any effect on A. Feb 17, 2020 at 13:48
0

Since the current answers don't seem to provide the information you are looking for, I will try yet another approach to this topic.

Consider the following pseudo-code (I'm not a java dev):

class Logging
{
  public Logging(IWritableDatastore writable_ds)
  {
    ds = writable_ds;
    dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
  }

  public void write_entry(string message)
  {
    ds.write(dtf.format(LocalDateTime.now()) + message);
  }

  private IWritableDatastore ds;
  private DateTimeFormatter dtf;
}

interface IWriteableDatastore
{
  void write(string data);
}

class FileWriter implements IWriteableDatastore
{
  public void write(string data)
  {
    // Do whatever java stuff needed to write to file...
  }
}

The class FileWriter is loosely coupled to the class Logging. Logging doesn't even know FileWriter exists, due to the use of dependency injection. This gives us the freedom of changing FileWriter. And it gives us the freedom to add different data stores, in case we want to write our logging to a database instead of a text file.

Maybe this example makes sense to you.

0

In Java, when A creates an object B and calls one of its methods it's said to be tightly coupled.

Says who? It's certainly more tightly coupled than the situation below, but tightness and looseness are relative terms, not absolutes.

But if we create an interface IB, used from A where B implements IB it's said to be loosely coupled.

More loosely (or less tightly) coupled.

I don't see why since one change in the interface would have an impact in A and B

You're completely ignoring the other change: you removed the bit where

... A creates an object B ...

So the second form is less tightly-coupled because A no longer creates and calls B, it only calls it. Creation is also coupling (it also involves a call, and possibly constructor parameters).

If we just remove the construction of B from A, and keep the direct call, we have still reduced coupling. Now we can hide changes to B's constructor from A, which shouldn't need to care about it, either inside a factory or by passing B in from outside.

Facade and Polymorphism

Now, about interfaces in general and Facade in particular. Your reading of the answers to your linked question is wrong, because you're reading through the lens of your focus one particular case: call coupling between exactly two modules.

The accepted answer is very clear that Facade is about dealing with multiple implementations of the same interface. In that case, without using an interface, the calling module ends up coupled to each possible implementation.

If we replace B with IB, we reduce coupling because A can now operate on multiple IB implementations without needing to know about them (ie, without coupling to the concrete subclass or subclasses).

This doesn't reduce call coupling, as you notice, except that A is now call-coupled only to IB, and if we add multiple subclasses, then the call-coupling of A does not increase as it would without the interface.

Interfaces and Monomorphism

What about the degenerate case where we really do have only one implementation of IB?

Here, as you observed, call coupling is not reduced. However, your focus on call coupling is reductive: when A depends on B, it also depends in some sense on all of B's dependencies and implementation details. You will need to do extra work to mock B in order to test A in isolation. These are also forms of coupling that are - at least sometimes - worth avoiding.

0

I agree that the answer is not black and white. In general it is good to avoid very high complexity and long chain of dependencies.

Theory:

Imagine coupling code without interfaces:

class A -> class B -> class C -> ...-> class Z

reusing class A will bring entire chain of dependencies into your code.

Code is as it is and cannot be reused by someone else in slightly different manner.

So you have to break that chain of inevitable decencies by some patter for example inversion of control:

class A -> class B -> class IC

So you can provide your own implementation of IC and does not need reuse entire code, but only the part you want.

The reality:

In reality, the code might be very coupled even if it is theoretically decoupled.

Imagine chain of decoupled (inverted) dependencies

class A -> class IB -> class IC -> ...-> class IZ

In this case any reference is passed via interface, so theoretically you could implement it, but in reality it is long chain/tree of never ending dependencies that is it practically impossible to replace the interface.

So decoupling does not only mean to replace direct dependency by contract, it also means to make the contract so easy to implement that one can do it in reasonable time and effort.

Design requirements sometime cannot meet business requirements. If your business logic is really difficult and complex, you can hardly implement it in very decoupled code. It is because the problem itself is very coupled. You could split it to many decoupled layers into many files, but in reality the code base will not be much maintainable. I do not convince you into writing monolithic code, I am just trying to say that the code cannot be more decoupled then business problem which is being solved.

0

Let me answer it via story from common life.

On railway station Vagabond ask Mr. Coupled: "Do you have cigarette?"

Mr. Coupled replies: "Yes, you can take my cigarette, but you also have to take my bag, my car, my job, my wife, my mortgage and my dog."


On railway station Vagabond ask Mr. Decoupled: "Do you have cigarette?"

Mr. Decoupled replies: "*Yes, you can take one, no problem"

1
  • As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Oct 25, 2023 at 21:18

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.