Like many other things in software development, the degree of coupling in software systems is a tradeoff between competing objectives.
Consider this constructor method that takes a Customer as a parameter.
public CustomerProcessor(Customer customer)
As you can see, you're tightly-bound to the Customer class. You can inject it from somewhere else, but you're limited to that specific type.
Now consider this constructor:
public CustomerProcessor(ICustomer customer)
So nowNow we're specifying an Interface instead of a Class. We can pass whatever object we wish to this constructor method, so long as that object conforms to the ICustomer
interface (including a mock or stub, for testing purposes).
But what if we did this?
public CustomerProcessor(string customer)
What's in customer
? A JSON string? An XML string? Just a name? It could be anything. How it's handled depends on what code is in the CustomerProcessor
object, but if your CustomerProcessor
object was actually passed into some other class as an ICustomerProcessor
, you could have a family of CustomerProcessors that could work out what the format is and correctly parse the string automatically, including a change from int
to int[]
. Now you're even more loosely coupled.
What does this cost you? Compile-time type safety, of course. That, and significantly increased complexity. It also costs you performance.
So why would you ever do this? Well, there are valid use cases. Consider a system of sensors. Each sensor has a value or a set of values that it emits. There are hundreds of different sensor types. Would you write a class for each new one? Some systems like this merely pass such information as strings along a data bus. Each string has a sensor type embedded, so that the sensor can be properly identified, and its data correctly decoded from the string.
As to your ripple effect, changing a member of a type should only affect that code that actually touches your int
. When you make the decision to change from an int
to an int[]
, you commit to refactoring all of the code in your system that touches that int
. That's by-design. It has more to do with type systems than it does with the degree of coupling between software modules; since you're changing your fundamental declaration of what a customer is, the type system is going to complain when your old code doesn't meet the new expectations.