Some coupling must exist, otherwise a program would not be able to do anything (if nothing knows about anything, no calls can be made). This kind of coupling certainly exists in your “manager” classes. The thing that’s bad is coupling that makes your code hard to change, and most of it is kind of accidental.
You start with certain best practices, and a certain way of doing things; this is OK to get you off the ground, but as the project evolves, accidental coupling creeps in, and in a big part because of inertia – people blindly following generic “best practices” and “established conventions” way beyond the point of their usefulness, without stopping to rethink/redesign before it’s all a complicated mess. Best practices, conventions, linters, style checkers – that all has it’s place, but none of it automatically gives you good code. Nobody wants to write bad code, however, bad code, in large part, gets written very systematically.
So, when it comes to bad coupling, you have this kind of coupling even within your “manager” classes, as well as in and between classes that use them – it’s just not as obvious until a change causes you to modify 20 different files. They are coupled in subtle ways – like depending on a specific data format, on a specific field being there, on calling order, on things needing to match or follow the same pattern in different places, etc.
The thing that you want to do is not to eliminate coupling completely, but to minimize it and control it, and be strategic about it.
“but then we have very strong coupling and dependency for Customer with PaperBoy”
Sure, but, putting a type name in a constructor parameter list makes that type explicitly a part of the public interface of that class. It explicitly states what depends on what. Being explicit is good, and if this coupling is deliberate, and if
PaperBoy are fairly small and focused classes, and not these large procedural bags of functions, than that’s a way of controling coupling.
Note that on a most basic level, the
Customer class doesn’t depend on the internal implementation of
PaperBoy, just on its public interface (the set of public methods, including their parameter and return types). Behind that, you can change how
PaperBoy is implemented – you can change its internal code, its internal data structures, you can pull out parts of
PaperBoy into a separate class, you can do all kinds of things – without affecting
If you need more flexibility than that, maybe you can derive a subtype of
PaperBoy, or perhaps make the constructor parameter type be an interface instead of a concrete class.
“and changes in paperboy will cause changes in Customer”
The most likely cause of that is that you’re treating
PaperBoy as just a data structure, and your
Customer is pulling data out of
PaperBoy to manipulate it itself, instead of telling
PaperBoy what to do (by calling a method – almost in a fire and forget style). What’s happening there is that you’re not defining/constraining the way
Customer talks to
PaperBoy – you’re not thinking carefully (or at all) about the public interface of
PaperBoy, you’re just saying “Here’s some data, hope the format doesn’t change! (wink, wink)”. This problem is not solved by having “manager” classes – changes in the data structures they depend on will have the same effect.
“if i will need my Customer in other project”
Don’t get this the wrong way, I’m by no means trying to talk down to you or anything like that, but:
When people say “reusable”, it really means the ability to use the same code/function/object in a different context: perhaps in a test, perhaps in a different part of the same project, or as a thing you can plug your newly developed component into. Or, if the function or class or module (or whatever) turns out to be more generally useful, perhaps in a different project, or maybe in a library.
When you can’t easily reuse something in a different context, it’s not because it’s a class coupled to some other class, it’s because it wasn’t designed to be reusable.
Now, I’m not saying that the only way to deal with this is OOP; however, whatever paradigm you’re using, these same principles apply (it’s just that their expression may take a different form). Also note that I’m not advocating for (or against) purity: some aspects of different paradigms can be mixed and can complement each other nicely. So I’m not necessarily saying that your domain model must not be anemic – just that that you have to control the coupling; if it happens in functions that work on anemic data structures, then you have to control it there.
I’ve been thinking recently, the usual advice regarding going from an anemic domain model to a proper OO design is to put behavior into those data structures; however, I think is not necessarily immediately obvious how to go about this change, even to experienced designers, but they can find their way by relying on their experience and design skills. But for someone who designed themselves into an anemic domain model, the path from one point to the other might be completely obscured.
Instead, maybe a better way to approach things is to look where your behavior is now, inside your “manager” classes, and try to decouple that. That’s where your objects hide. Keep treating your domain model as inputs and outputs for the time being, but in your procedural code, try to find groups of things that are not really related to other things, and isolate them. Find groups of lines that have a comment on top explaining what they do – extract each into a meaningfully named function. Reduce conceptual duplication (code doing the same thing, even though it’s written slightly differently) – extract those into methods. Find those member variables that are used to track things internally, but are only used by some methods. That looks like a class with some state; combine those and pull out a class.
Notice when a change causes you to modify several files. Try to understand why, and see if you can restructure things so that this proliferation doesn’t happen, especially if the kind of change that caused it is a common one – if there’s a history of similar change requests, and you can expect more in the future. Maybe some of the steps above will make such restructuring easier.
Keep doing this over time, gradually arriving to a more decoupled OO system. And remember, it’s OK for a class to be small – they should be small, and focused on a specific responsibility. Small means less opportunity for coupling, but doesn’t prevent your code from doing “bigger” things – objects achieve this through collaboration. Small and focused, with a well defined public interface and the associated rules, is what’s reusable.