3

According to Why define a Java object using interface (e.g. Map) rather than implementation (HashMap), I know I should declare the most abstract type possible, so for example:

public interface Fruit{
}

public class Orange extends Fruit{
}

when declaring Orange, I should write "Fruit obj=new Orange();" instead of "Orange obj=new Orange();". However, does the "declare the most abstract type" version has higher coupling actually? Because I think "coupling" of a class is about counting how many other class names appeared in the source file, for example:

Declare the most abstract type : other class names appeared : Fruit, Orange, String (3 classes):

public static void main(String[] args){
    Fruit obj=new Orange();
    .
    .
    .
}

Declare the exact type : other class names appeared : Orange, String (2 classes):

public static void main(String[] args){
    Orange obj=new Orange();
    .
    .
    .
}

As "declare the most abstract type" contains 3 other class names, so I think the "declare the most abstract type" has higher coupling than the one that declare the exact type (only 2 class names). Is it true? If not, what is the misconception here? How can a class contains the name of other class but not depending on that class?

Note: the question is just about coupling, neither encourage nor discouraging "declaring most abstract type". Also another reason that I think "Fruit obj=new Orange();" has more coupling is, when one day Orange doesn't extends from Fruit anymore:

public class Orange{
}

"Fruit obj=new Orange();" needs changing to "Orange obj=new Orange();" to recompile, while "Orange obj=new Orange();" doesn't need to.

14
  • First, this is mainly important for your inputs and outputs, not local variables. Second, softwareengineering.stackexchange.com/questions/237113/… Oct 25, 2023 at 5:34
  • 4
    "Because I think "coupling" of a class is about counting how many other class names appeared in the source file" Spoiler: it's not, at least not as simplistically as you do it here. Oct 25, 2023 at 7:26
  • Well, you can't really understand this by looking at the main function, as main is really "glue" code. You'd actually have something like a separate class (or even just a function) that takes in a Fruit, and expresses all of it's logic in terms of Fruit (never mentioning any derived type). That other class is decoupled from the concrete types. Note however, that just having a class extend an interface is not enough to decouple its clients from it. It's the kinds of methods you choose to put on that interface that allow for decoupling (or not). Oct 25, 2023 at 16:31
  • 1
    Another thing to consider here is that the Orange class is already coupled to the Fruit interface. That is that changes to the Fruit interface will generally require changes to the Orange class. By depending on Orange, you've got a transitive dependency on Fruit whether you import it or not.
    – JimmyJames
    Oct 25, 2023 at 20:04
  • @JimmyJames By depending on Fruit and not in any explicit way on Orange you are so loosely coupled to Orange that you are independently deployable. Oct 26, 2023 at 16:51

4 Answers 4

4

When you declare the variable by the most specific type Orange, the compiler will allow you to use (i.e. couple to) properties and/or methods declared in the Orange class, in addition to those declared in the Fruit class.

That means that if you later want to swap the Orange for a different fruit you will have more work to do, and if you or the maintainer of Orange wants to change or remove one of those properties or methods they may not be able to without breaking your new code.

When you declare the variable by the type Fruit, you only staticly couple to the things declared in the Fruit class, plus the constructor of Orange.

As Jacob said in comments this doesn't matter so much for local variables. If you don't want to couple to any property of Orange you can just not use it. It's more important for things passed between functions, like parameters, return values and properties.

I'm not a Java developer so I may be bringing in habits from PHP and Typescript, but personally I would be happy to let the compiler infer the type as Orange with var obj=new Orange();

6
  • 1
    Using var obj = new Orange() introduces the same coupling as Orange obj = new Orange(). Type inference doesn't guess which derived abstract type your code will end up using later on. It basically looks at the type returned from the right of the assignment operator. Oct 25, 2023 at 12:14
  • 1
    @GregBurghardt Yes I realise that - it just removes the repetition of Orange. I'm agreeing with Jacob that for a local variable using the Orange type is basically fine. Do you think my answer is misleading or unclear?
    – bdsl
    Oct 25, 2023 at 13:08
  • I suppose if you can edit the Orange class you could also give it a static factory method with return type of Fruit and call that instead of new Orange().
    – bdsl
    Oct 25, 2023 at 13:10
  • A constructor is basically a static factory method that returns a concrete type. Assigning it to a Fruit variable implicitly casts it up from Orange to Fruit. The static factory method doesn't add anything useful. Oct 25, 2023 at 13:41
  • 1
    If you use orange methods and later change the object to apple, of course you have more work. But if declared as fruit, you wouldn’t have had access to the extra functionality in the first place.
    – gnasher729
    Oct 25, 2023 at 18:24
3

You're right. Coupling comes in many forms. And the number of classes your code knows about is absolutely one of those forms.

So if that form of coupling was the only thing to consider then the "Declare the most abstract type" advice loses to "Declare the exact type".

But you don't have to do it that way.

void ninja(Fruit fruit) {
.
.
.
}

There. For the cost of thinking of a name (ninja) you can avoid that whole coupling issue. No one has mentioned Orange. Nothing in this code cares if Orange exists. If an Orange comes along that can support the Fruit things ninja wants to do then fine, pass it in. But in this code we're certainly not running up your class count.

The main advantage of code like Fruit obj=new Orange(); is that it sweeps the Orange aware code into a tight little corner that makes getting rid of the explicit reference to Orange easier later. Making the code work with this proves that the abstract Fruit works just fine for ninja. You can think of it as a first step to getting rid of references to Orange. But yeah, while Orange is still here you're still coupled to it.

Now something, somewhere will always have to know we're really dealing with an Orange. But if that code can be pushed off to another file then here at least we're free of that knowledge. Push it somewhere up the call stack away from our behavior code. It can go live with some boring structural configuration code.

Until you're ready to do all that, "declare the most abstract type", is a good way to get ready.

4
  • @wcminipgasker2023 And, if you designed the Fruit interface right (maybe after refining it through several iterations), passing in, say, a new CandiedOrange would not break the ninja method in any way. That's what makes it decoupled. Oct 25, 2023 at 16:35
  • @FilipMilovanović true. Your ninja sword will need to be sugar tolerant. ; ) Oct 25, 2023 at 17:56
  • Obviously I want an Orange. I just don’t want to write Orange specific code, so my mate who wants an Apple can use my code unchanged - except for initially requesting an Apple.
    – gnasher729
    Oct 25, 2023 at 18:26
  • Until we introduce OrangeCandy. No ninja sword can defeat that.
    – JimmyJames
    Oct 25, 2023 at 20:54
2

If we consult the Dependency Inversion Principle, whose goal is to reduce coupling, it states:

  1. High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).

  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

This seems to indicate that coupling happens when we depend on concrete classes and not in interfaces, i.e., we depend on the design and not in the implementations. Of course in early stages an interface can change and will affect everything but after those initial stages interfaces are expected not to change as much as the implementations, and if you segregate the interfaces (Interface Segregation Principle) you protect yourself against that.

The more abstract something is, the less prone to change it is. So we should strive to depend on the things that change the less often, i.e., the more abstract components.

So the code that declares the variable as being a Fruit has less coupling than the one that declares it as being an Orange, provided that you use some creational pattern to keep instantiations in a separate place, perhaps a factory.

0

After thinking about this for a while and posting a comment which maybe wasn't clear, I think this assertion is incorrect:

Because I think "coupling" of a class is about counting how many other class names appeared in the source file, for example:

Perhaps there's a type of coupling which has been defined by the number of other classes referenced in a class, but if so, I don't think it's a useful or especially meaningful metric. The reset of the answer will be an attempt to explain why.

In your original example, you compare the following two statements:

// option A
Fruit obj=new Orange();
// option B
Orange obj=new Orange();

Where Orange implements Fruit.

And you ask whether option A introduces more coupling than option B. I would assert that at the very least there is no additional coupling in A versus B. The reason is that in option B, the code is still coupled to the Fruit interface through a transitive dependency. While it's possible that when using option B, you could use parts of Orange that are not part of Fruit, if you do that, A was never a viable option anyway. I'll come back to this later.

To make this clear, consider what happens if the Fruit interface is modified when using option B: you will likely need to modify Orange as well, and those changes will potentially modify how you use Orange in your classes. And if Fruit changes in a way that doesn't require changes to Orange (such as a default method) those still become part of Orange. None of that changes when you explicitly type as Fruit. Using Orange means using Fruit. Using option B doesn't make you any less coupled to Fruit.

But wait, there's more. I would actually argue that using Option A can increase your coupling. The simple reason is that by using the Fruit type, you eliminate the possibility of coupling to other interfaces and classes aside from Fruit. Let's say Orange also implements Citrus. If you type your variable as Orange you are now transitively coupled to both interfaces, especially if you use those Citrus methods. By using Fruit, you narrow the coupling to what Fruit provides.

I kind of hate these sorts of examples because trying to come up with meaningful methods that would make sense for a Citrus interface hurts my brain. So instead, let's talk about Collection types. This kind of thing is very relevant when using Collections in code.

// option A
Collection items = new ArrayList();
// option B
List items = new ArrayList();
// option C
ArrayList items = new ArrayList();

Where ArrayList implements List, and List implements Collection.

Which should we use? As usual, the answer is (IMO) that you should use the most common (or more abstract) type that supports your needs. If all you need is a Collection (i.e, you don't care about order or whether duplicate items can be added,) you should prefer Collection as your variable type. If you need List capabilities as well, you should prefer List. If for some reason you need to use ArrayList specific methods such as trimToSize(), then you should use that as your variable type (you have no choice, really.)

This minimizes your coupling to the simplest of the three that meets your needs. This makes the code more obvious and simpler to modify later.  

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.