| People forgot at some point that design patterns are for solving problems. The key being that you should only be solving problems you actually have, and stop making up imaginary problems in your head before you even start coding. My code is typically some dumb data objects, static functions to apply rules to that data, and a bunch of interfaces for abstracting external dependencies. The code that ties everything together lives at the entry points, and is hopefully written like one is reading a requirement. To take the course registration example, the "register" entry point might look something like: register(studentID, courseID):
// Load student from data store interface.
Student student = this.datastore.getStudent(studentID)
// Business rules for whether a student can still register for classes.
// This would check things like course overload and duplicate registrations.
if (!Students.canRegister(student, courseID)):
return CannotRegisterError
// Record registration.
this.datastore.addRegistration(studentID, courseID)
Advantages:* Data objects are dumb. No need to even test them. * Rules are all static functions, so you can call them anywhere, anytime, including for testing. Extremely flexible and allows easy remixing of rules for new requirements. * All external dependencies live behind an interface, so they can all be mocked away for testing. |
IMO, this general idea is very powerful for most code. I believe algebraic effect systems will popularize it further by making it a natural pattern. While OO should be written in this way, we are currently in this weird period of programming history where we don't talk about practices and design as seriously as we should.
But, I'm glad you mentioned this approach. It is not hard to understand, it is elegant, and it is as simple as can be. Just requires a tiny amount of glue and forethought.