Dependency management: Coupling and cohesion

Dependency management turns ugly when Assembly A gets a transitive dependency (in red, the slash means ‘derived’) to Assembly C even if Assembly A apparently does not depend directly to nothing in Assembly C:

The evolution of large-scale designs with decoupling and cohesion principles in mind becomes a real challenge if you are not aware of dependency management (that is, object-oriented design).

Interface inheritance (implementing an interface) or implementation inheritance imply a much coupled relationship.

If you do not want the above transitive dependency consider other types of relationships between types (see Type relationships); choosing aggregation instead of any form of inheritance makes the transitive dependency disappear:

Perhaps, implicit interface implementation would be good for inter-assemblies dependencies.

A configuration of dependencies that offers some more stability could be like this:

The dependencies follow the direction of stability; that is, Assembly C contains only interfaces, those interfaces that Assembly A uses and the same interfaces that Assembly B implements. In effect, interface users and interface implementation do not know each other, and can evolve independently.

Assembly Z is the less abstract of those assemblies; it knows all other three and wires them up together.

Important is that Assembly C doesn’t depend on more concrete types, because changes in Assembly C propagates quickly and vastly.

As assemblies grow in functionality this configuration also grows, the assemblies start to split and regroup and new assemblies act as clients of Assembly Z, in order to keep coupling and cohesion balance new clients (Assembly X) of Assembly Z can depend on it directly with no derived/transitive dependencies:

As design evolution goes on, the new client can take the role of Assembly A and the Assembly Z takes the role of Assembly B implementing a new all-interface Assembly Y on which new clients (Assembly X) depends on, like this: