0

I am reading Clean Architecture by Bob Martin. He talks about breaking dependencies with an interface. For example. Class B uses class A. Thus, class B is dependant on class A (B → A). We can add an interface and have B dependent on the interface and also have A dependent on the interface (A → I ← B).

What I am not understanding is that if class A has private member variables that are used in the functions that B needs, wouldn't I have to rewrite the same code in B that A has? Also, wouldn't this just be duplicate code?

Here is an example.

class Car {
    private String color;
    private Integer numberOfTires;
    [...]

    public void printCar()
    {
        System.out.print("Color: " + color);
        System.out.print("Number of tires: " + numberOfTires);
    }
}

class Inventory{
    private Car car;
    private Truck truck; // Left out for brevity

    public void printCar()
    {
        car.printCar();
    }

    public void printTruck()
    {
        truck.printTruck();
    }
}

I don't see how an interface can help with this dependency.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
asmcriminal
  • 93
  • 12
  • Possible duplicate of [Interfaces — What's the point?](https://stackoverflow.com/questions/6802573/interfaces-whats-the-point) – Not a JD Mar 31 '19 at 20:40
  • This is NOT a duplicate. This is about dependencies and interfaces. Not "what is the point of interfaces." I am trying to understand how to break dependencies with interfaces. I am not trying to understand what an interface is and why they're used. – asmcriminal Mar 31 '19 at 21:46
  • If B needs to access a member of A, why don't just add a getter method to the interface? – Nghia Bui Apr 01 '19 at 03:52

5 Answers5

1

Kaue Silveira provided a good example, but missed to mention another important aspect.

In general, the point of interfaces is not to have fewer classes. I suppose you are familiar with the terms coupling and cohesion. You always want to have code that is loosely coupled and highly cohesive.

Meaning, you do not want classes to be dependent on each other (coupled), but to share some logic in the form of inheritance, polymorphism, etc. These concepts are some of the basic pillars of quality ObjectOriented design. It is definitely worth reading about it if you are not familiar with these topics.

To go back at the question, sure you are going to usually have some duplicate if you are dealing with a complex logic that has a lot special cases. But, such issues are more related to other design patterns and principles that are solely designed to adhere to the DRY principle and resolve situations in way that generalize the approach to the solution.

The main idea behind interfaces is to set a logical structure to the classes that contributes to the uniformity of object manipulation.

Imagine a stock exchange system.

This interface has a single method called execute that will perform some application logic.

public interface Order{
     void execute();
}

Other classes that might implement this interface could be Buy and Sell. It would look something like this:

public class Buy implement Order{
     @Override
     public void execute(){
          //TODO: Some logic
     }
}

Now both Buy and Sell would have similar code, maybe even some duplicate, but it is more important that when they implement the same interface you can handle them in an uniform way. You can have some StockManager class that would put both Buy orders and Sell orders in some Queue<Order>. From this you can conclude that when working with such Queue, you would have the ability to call the execute() method on any implementation of an Order interface.

Building on top of the previous argument, by using interfaces, and some frameworks like Spring, you get the possibility to do auto-wiring. This significantly reduces the dependence on lower level implementation classes, giving you the possibility to change low-level classes without impacting the top-level handlers. This type of application design is a common practice in service-oriented architecture (SOA).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
H.G.
  • 415
  • 2
  • 4
  • 17
  • 1
    Please add also an example what is in your interface. First of all it hard conclude from your ex. what exactly you have declared in your interface. you can NOT have both printCar() and printTruck(), it does not make logical sense in the interface. You could have printVehicle() and then add both concrete truck and concrete car to a list of let's say vehicles. Then you create an option to use a for loop on all the elements in your Inventory and to print them. But as I said, add interface! – H.G. Mar 31 '19 at 21:56
  • That's the thing. I don't have an interface. This was an example off the top of my head. The question is, "How can I break that dependency? Or is it even possible?" I am assuming with an interface. But I could be wrong. – asmcriminal Mar 31 '19 at 22:20
  • Yes, by using interfaces you would solve that problem. As I said, using an interface Vehicle, for example, would give you the ability to handle any concrete implementation (Car, Truck, Boat...) of that interface in an uniformed way by simply adding all of them up to some data structure like a List or an Array, and iterating through. – H.G. Apr 01 '19 at 07:49
0

At run time in production, B will use the real class A (or another class implementing the same interface), so there isn't any need to duplicate the code.

Using an interface lets you use another implementation for other uses, for example using a lightweight-in-memory-fake-class-A for the unit test of class B, therefore avoiding the heavy-weight real class A.

You (can only) break the "static, build time" dependency. The "dynamic, run time" dependency (always) remains.

In code:

public interface AInterface {}
public class A implements AInterface {}
public class B {
  AInterface a;
  public B(AInterface a) {
    this.a = a;
  }
}
public class Main {
  B b = B(A());
}
public class AFake implements AInterface {}
public class BTest {
  B b = B(AFake())
}

This example uses constructor dependency injection.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • But wouldn't that only give me use to the function in the interface? If A has a public method makeCake(). And B is using that function. If I want to break the dependency adding an interface won't help, right? If I have MakeCake() in an interface. I would have to implement in both classes. I would then have to write a different method in A and B. Thus, it defeats the purpose. – asmcriminal Mar 31 '19 at 21:27
  • You (can only) break the "static, build time" dependency. The "dynamic, run time" dependency (always) remains. – Kaue Silveira Apr 01 '19 at 12:30
0

Let’s say we have class Bike and Transportation like this:

class Bike{
   public void ride(){
      System.out.println("Riding from point A to B");
   }
}

class Transportation{
   public void commute(Bike b){
      b.ride();
   }
}

Now the Transportation class here is tightly bound to Bike. You cannot commute by any other vehicle, even it is available. Also any change in the Bike class will directly impact the behaviour of Transportation.

Instead of the above code let’s say there was an Interface called Vehicle like this:

public interface Vehicle{
    void move();
}

class Bike implements Vehicle{
    public void move(){
        System.out.println("Riding on Bike");
    }
}

class Transportation{
    public void commute(Vehicle v){
        v.move();
    }
}

Here the Vehicle interface guarantees that whatever class implements it, it will have a move() method with exactly the same signature, no matter what. Also, the commute() method now takes a Vehicle object. This enables to create a Transportation class object later with another implementation of the Vehicle class without the Transportation class even being aware of it. Example:

class Car implements Vehicle{
    public void move(){
      System.out.println("Moving in car");
    }
}

Car c = new Car();
new Transportation(c).commute();

Bike b = new Bike();
new Transportation(b).commute();

The use of Vehicle interface has removed the dependency of Transportation from the concrete classes. Also, any class that needs to use Transportation, must implement the Vehicle interface with a method move() in it. Thus establishing a contract between any Vehicle and Transportation.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
raviiii1
  • 936
  • 8
  • 24
0

Presumably your Inventory is an "inventory of vehicles", not an "inventory of one car and one truck".

With that in mind perhaps this will help:

  • Car is a Vehicle
  • Truck is a Vehicle
  • Inventory depends on Vehicle -- not on Car or Truck, it knows nothing about these types
  • Vehicle::printDetails is implemented by Car and Truck

.

public class Scratch4 {
    public static void main(String args[]) throws Exception {
        Car car = new Car("Blue", 4);
        Truck truck = new Truck();

        Inventory inventory = new Inventory();
        inventory.addVehicle(car);
        inventory.addVehicle(truck);

        inventory.printVehicleDetails();
    }
}

interface Vehicle {
    void printDetails();
}

class Car implements Vehicle {
    private String color;
    private Integer numberOfTires;

    public Car(String color, Integer numberOfTires) {
        this.color = color;
        this.numberOfTires = numberOfTires;
    }

    public void printDetails() {
        System.out.println("Color: " + color);
        System.out.println("Number of tires: " + numberOfTires);
        System.out.println();
    }
}

class Truck implements Vehicle {

    @Override
    public void printDetails() {
        System.out.println("Some kind of truck");
        System.out.println();
    }

}

class Inventory {
    private List<Vehicle> vehicles = new ArrayList<>();;

    public void addVehicle(Vehicle vehicle) {
        vehicles.add(vehicle);
    }

    public void printVehicleDetails() {
        vehicles.forEach(Vehicle::printDetails);
    }
}

Yields

Color: Blue
Number of tires: 4

Some kind of truck
Not a JD
  • 1,864
  • 6
  • 14
  • Yes, you're right. But the issue I am trying to get across is that there is a function in another class A that is being used by class B. The method in A is using private variables to create a return value. Thus there is a dependency going on there. Can that be broken with an interface? – asmcriminal Mar 31 '19 at 22:12
  • Interfaces do not have state, so no. – Not a JD Mar 31 '19 at 22:14
0

Robert talks about breaking dependencies and of course you can even break the dependency between Inventory and Car, but I think that doesn't give you much benefit, because you don't cross an architectural boundary.

It would be better to break the static dependency to System.out, because I/O is an architectural boundary and you can then replace the way a Car or Inventory is printed and that is very useful for tests.

Breaking the static dependency

class Car {
    private String color;
    private Integer numberOfTires;

    private PrintStream output = System.out;

    void setOutput(PrintStream output){
        this.output = Objects.requireNotNull(output);
    }

    public void printCar() {
        output.print("Color: " + color);
        output.print("Number of tires: " + numberOfTires);
    }
}

Now you can replace the output in tests to capture the output result.

Apply the interface segregation principle

public interface Output {
     public void print(String msg);
}


class Car {
    private String color;
    private Integer numberOfTires;

    private Output output = (msg) -> System.out.println(msg);

    void setOutput(Output output){
        this.output = Objects.requireNotNull(output);
    }

    public void printCar() {
        output.print("Color: " + color);
        output.print("Number of tires: " + numberOfTires);
    }
}

Now your only dependency is the Output interface which is even easier to replace or mock in tests.

This little change makes your Car independent from a concrete output system or as Robert would say, a detail. I can also imagine to implement a JTextAreaOutput so that the output can be displayed in a GUI.

The clean architecture tells us that I/O is a detail and our business code should not depend on it. It seems that Car and Inventory is your business code and thus I showed you how to decouple it from a concrete output system - a detail.

    +-----+    uses     +--------+     implements   +--------------+
    | Car |  -------->  | Output |  <-------------  | SystemOutput |
    +-----+             +--------+                  +--------------+

             ---------> control flow  ------------->

We have also applied the dependency inversion principle, since the source code dependency points against the flow of control.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
René Link
  • 48,224
  • 13
  • 108
  • 140