Sometimes I look back at my past programming mistakes, in order to gain better insight into my current understanding of what I consider current best practices. This often happens when I'm reviewing someone else's code, and I see my past mistakes in their code. I begin wondering what I learned to get over that hump, and I think about what's an easy way to explain that one thing, to someone who hasn't gotten over the hump yet. I try to make up little rules to live by. One of those rules is that objects should be composable. Sure, it's not an original thought, but it is one gained through much pain.
I've quite often run across magic objects. Magic objects are those that don't use constructors, or construct themselves, or configure themselves, or take things like hashes or data rows as a single parameter to the constructor, or write themselves to the database, or do any number of things that don't related directly to their core responsibility. I've heard justifications like "OO says the object should be smart", or "OO is about the object doing their own work", basically justifications about why they wrote something they way they did, but more importantly, insight into that thing they still don't know. And the problem with these approaches is always the same, some set of domain objects that are usable only within a very specific context and fail miserably when one attempts to reuse them somewhere else.
This has always seemed a symptom of rote memorization of some set of rules. Trying to learn something by remembering the patterns, but not understanding them. The solution is always the same, ask "why" should I follow this rule? If you don't know why the rule exists, you stand little chance of applying the rule in the correct context. When it comes to patterns, context is everything. The answer to almost any question, honestly, is "it depends".
There was a time when I was into anti-patterns. I'd try to learn by looking at the common mistakes people make when doing something new. But over time I've come to see anti-patterns, as an anit-pattern. Teaching people what not to do, is a very hard way to teach someone what they should be doing. Anti-patterns are akin to treating the symptoms of a problem, rather than treating the problem itself. Anti-patterns themselves are simply the obvious result, of a misunderstanding of some core principle. The trick is to fix that misunderstanding. One of those misunderstandings that I've seen over and over in the code of someone learning object oriented code, is the magic object.
Magic objects seem to be a symptom of programmers not understanding that the intention of constructors, is to make the object ignorant of where its data came from. The object shouldn't know it came from disk, or database, or configuration file, you should. Objects should never construct themselves, because doing so kills their composable nature. The whole point of object oriented programming is to take a bunch of composable objects, wire them up in a particular context, and let the virtual machine you just built, do its thing. If an object knows where its data comes from, or where it goes, it becomes tied down to that one context, literally, you can no longer pick it up, and use it somewhere else, because it's tied down. The reason you don't put a save method on a business object is because it would tie that object to a particular persistence strategy. That's something you can't know for sure ahead of time. The reason you pass in configuration data, is because you don't know ahead of time, where that data will come from. It might come from a web.config file today, and an app.config file tomorrow, and from hand crafted code the next day when you find out you need this object somewhere new, yet again.
Another thing often missed, is that objects are data. When an object is created, it becomes a new primitive type, and should be used as such. How many times have you seen email addresses, social security numbers, credit cards, addresses, and companies passed around as strings? How many times have you seen money passed around with doubles and decimals and pennies lost here and there because of rounding errors? Objects should be composable, that means using all your objects as primitives themselves, and building yet more objects out of little bundles of them. How many times in a program are you going to validate that email string, or credit card string?
For some reason, when people first learn OO, they don't put their objects on par with whatever objects are native to the programming language they're using. I can't tell you how many times I've seen one object, pass its id, to another object, only for that object to use the id to go find the original object again, by looking it up in the database. There's so much focus on the primitive types of the language, people fail to see how much extra work they're creating for themselves by always programming in those built in native types and not simply passing around their own business objects.
The backwards nature of the relational paradigm, children having keys to their parents rather than parents having collections of their children, leads to object models that don't compose well because the children become bound to the context of their parent. Sometimes you want this, most times you don't, and it's usually a mistake. Addresses are much more usable, if they don't know what customer, or what order they're attached to, else the same one can't be used for both. Nor does an address need to know all the customers it's attached to, it's almost always more correct to look at the customers addresses, than the addresses customers.
Relational databases are like politicians, they do everything in their power, to avoid have an opinion on how the data should be modeled. They want to allow random queries from any direction. This is OK for the database, but once you have the data in memory, into an object model, the model itself needs to be the exact opposite. It needs to be very opinionated about how things are done and who points to whom, because it's a model, it's supposed to be machine that can deterministically solve a problem. You have to make choices about how to access the data, these choices become the domain model. You shouldn't be able to solve the problem 10 different ways. Building a good model, is just as much about knowing what not to build, as what to build. Composable parts, mean independent parts, which naturally lead you down the path of good solutions, even when you don't know what that path is.
I feel a little silly every time I say it, but objects are supposed to be Legos, a bunch of small, very focused parts, each good at doing one thing really well, that you compose into larger solutions. Just like the Unix metaphor of many little stateless utilities wired together via pipes into a data stream, objects are meant to be wired together into a stateful virtual machine. The end result being that problems are solved quickly and easily via composition of existing parts. All paradigms, be it OO, functional, data flow, or procedural, work best when the programmer keeps composition of parts, first and foremost in his mind.