The book
Clean Code makes a suggestion early on: types should either be:
- 'Data types' that are just properties and simple methods that act on the properties.
- 'Logic types' that represent operations performed on data types.
It strongly recommends against mixing the two. I didn't like this stance at first. It's been 2 years since I read that book, and now I agree.
I would write Policy such that it's just a collection of properties that describe whatever a "policy" is in your domain, and methods that answer questions/make changes that the properties don't neatly represent. I would have a PolicySerializer class responsible for saving it to a file, database, etc.
It turns out the flexibility this affords is invaluable, but not visible in many people's careers.
On one project, I didn't even know which database I'd be using when I started. So I wrote an IDatabase interface and, for the first couple of months, used a "MemoryDatabase" that was little more than an array. When I figured out I wouldn't have access to any of our networked databases, I chose SQLite. So I wrote a new "SqliteDatabase" that supported this. 5 months later, I found out I'd have to run on Linux instead of Windows, and that the SQLite API I'd chosen only worked on Windows. It took about 8 changes in 2 files to make that swap, because I took care to hide API-specific details behind another layer of interfaces. If I had put a Save() method on every one of my data classes in that program, it would've taken dozens of lines of updates across as many files to adapt to each change. Because I put that logic somewhere else, my changes were isolated to one corner of the program and nothing else had to know.
Using more classes and depending on interfaces instead of concretion is a technique for isolating yourself from change. It pays off in many other ways, most importantly enabling unit tests. The myth goes it increases project complexity, but in my experience that's only true in the eyes of novices. You should spend far more of your life as a 'veteran' than a 'novice', so it seems silly to optimize towards novice practices in all work.
If you don't feel like you'll ever be facing change, you can bend those rules and write larger classes that depend on concretions instead of abstractions.
But my personal experience, having written code in that manner for almost 10 years, is we're usually wrong about whether change will happen. And code that does not separate its concerns spends more time in the debugger than the 'more complex' code that focuses on composition of smaller units.
Put another way, which seems better: having a hammer and a pair of boots, or owning a pair of 5 pound boots with a steel heel?