I have some data which I need to pass around, which, after it has been processed, needs to be acknowledged. Ignoring the acknowledgement, I would write code like
case class Data(x: Int, y: String)
def process(msg: Any): Unit =
msg match
case Data(x, y) => processData(x, y)
case Other(z) => processOther(z)
To add the acknowledgement, my first thought would be to add the data needed to send the acknowledgment:
case class Data(x: Int, y: String, private ack: Acknowledger):
def acknowledge: Unit = ack.complete(this)
def process(msg: Any): Unit =
msg match
case data @ Data(x, y) =>
processData(x, y)
data.acknowledge
case Other(z) => processOther(z)
This has a few problems.
- As discussed in Why does scala allow private case class fields?, the generated
unapplymethod will unpack theackfield, so I needcase data @ Data(x, y, _). - Someone might try to use the
ackfield directly. - The user might forget to call
acknowledge.
If the ack field truly was private, that would resolve the first two issues. The solutions that I can see are to manually define unapply methods, or make Data a plain class and implement most of the methods that a case class automatically gives me.
Is there a better option?
I think that resolving the third issue is a separate question, but I won't object to quick pointers to a design pattern that addresses it.
While I don't agree with "mixing logic with data" always being a bad idea, as suggested in comments ("encapsulation" - incorporating data and the logic that manipulates it into an object – is one of characteristic features of object-oriented programming, which, while having somewhat fallen out of favor lately, still has its place even in scala), what you are trying to do seems to also be a violation of the Single Responsibility Principle (which, in my opinion is a much more significant problem).
If the purpose of your class is carrying a message, it should not also have a responsibility of acknowledging the receipt. Acknowledging is the job of whoever receives the message. So, I think something like this makes the most sense:
Let's see if this approach addresses your concerns:
Solved (no more ack field)
Also solved (no more ack field), but for the record, I don't think this is a real concern ... What does it even mean "someone might try ..."? Like, try to do what? Send an ack? Well, they could just use your acknowledge method for that, with the same effect? Why would they opt for using ack directly? Just to irritate you? :D I am actually not a fan of using
privatefields in scala, at least, for immutable members and objects, which 99% of cases are supposed to be anyway for this exact reason: they unnecessarily complicate design, and make it less flexible with zero benefit.This is actually the only real issue with your original design (another, related one is being able to call acknowledge twice and/or prematurely), and it is the important one, making that design "bad". With the approach I suggest, this is easily resolved with a unit test: