What are some common situations where using an object is a good solution?
It depends on what you mean by “object”
Some kind of structured data?
Some named type which fulfills an interface?
When you have some kind of structured data, having a class to represent it is fine. If you’re able to give it type annotations, that’s much better than passing around random dictionaries.
When you need polymorphism and have an interface where some method on an object needs to exist (e.g. car.honk()), that’s also fine as long as you avoid creating subclasses and using inheritance. If you need some car that can honk like a truck and drive like a racecar, use composition.
What I would consider a good use of classes (more specifically, nominal types) is dependent types. The idea is that you use the type system to enforce invariants for data.
For example, suppose you have a string for a user email. It might be a valid email string, or it might be garbage like “z#%@(”=))??". You have a function for updating the user email in a database, and it requires the email string to be valid.
One approach is to validate the email string after receiving it from the user. That works, but what if your coworker creates a new form and forgets to validate the email string there? Bad data gets passed downstream to functions that expect well-formed data.
Another approach is to validate the email string at the top of every function that expects well-formed data. That also works, but now you’re validating the same string multiple times and pasting validate_email(email) everywhere.
With a dependent type, you have a ValidatedEmail type and a constructor for it. The constructor will return an instance of the ValidatedEmail if and only if the email string is valid. Any function that expects a valid email will only accept a ValidatedEmail, and not a string. If your coworker creates a new form and forgets to validate the email, the type system will complain about a string being passed instead of a ValidatedEmail. You also shift the responsibility of validating the email to wherever there is a boundary between validated and unvalidated data, avoiding unnecessary validation since you know a ValidatedEmail is already valid.
It’s an extremely useful paradigm for avoiding logic errors, but it’s unfortunately not as common as it should be.
That’s good advice but I would add that Java really sucks at using “the type system to enforce invariants for data” and that this approach doesn’t have much to do with what most (especially Java programmers) would consider OOP. I die inside a little bit every time I need to use code generators or runtime reflection to solve a problem that really should not require it.
It depends on what you mean by “object”
When you have some kind of structured data, having a class to represent it is fine. If you’re able to give it type annotations, that’s much better than passing around random dictionaries.
When you need polymorphism and have an interface where some method on an object needs to exist (e.g.
car.honk()
), that’s also fine as long as you avoid creating subclasses and using inheritance. If you need some car that can honk like a truck and drive like a racecar, use composition.What I would consider a good use of classes (more specifically, nominal types) is dependent types. The idea is that you use the type system to enforce invariants for data.
For example, suppose you have a string for a user email. It might be a valid email string, or it might be garbage like “z#%@(”=))??". You have a function for updating the user email in a database, and it requires the email string to be valid.
One approach is to validate the email string after receiving it from the user. That works, but what if your coworker creates a new form and forgets to validate the email string there? Bad data gets passed downstream to functions that expect well-formed data.
Another approach is to validate the email string at the top of every function that expects well-formed data. That also works, but now you’re validating the same string multiple times and pasting
validate_email(email)
everywhere.With a dependent type, you have a
ValidatedEmail
type and a constructor for it. The constructor will return an instance of theValidatedEmail
if and only if the email string is valid. Any function that expects a valid email will only accept aValidatedEmail
, and not a string. If your coworker creates a new form and forgets to validate the email, the type system will complain about a string being passed instead of aValidatedEmail
. You also shift the responsibility of validating the email to wherever there is a boundary between validated and unvalidated data, avoiding unnecessary validation since you know aValidatedEmail
is already valid.It’s an extremely useful paradigm for avoiding logic errors, but it’s unfortunately not as common as it should be.
Very cool! Thanks for this
That’s good advice but I would add that Java really sucks at using “the type system to enforce invariants for data” and that this approach doesn’t have much to do with what most (especially Java programmers) would consider OOP. I die inside a little bit every time I need to use code generators or runtime reflection to solve a problem that really should not require it.