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)
Now 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)
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. 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.