I have a class Transaction
that can have one of two states, Unpublished
and Published
. I would like to be able to limit what methods can be called on this class based on its state such that the compiler throws an error if the wrong method is called. Every method changes the state in a predictable way, so there is never any ambiguity at compile time about what state the object is in. I've seen this called an "Indexed Monad", but I'm not sure whether that is actually what this is called.
Here's a (contrived) example:
class Transaction
{
public enum State
{
Unpublished,
Published
}
public State curState = State.Unpublished;
public void GetData()
{
if (curState == State.Unpublished)
{
throw new InvalidOperationException("cannot get data from unpublished transaction!");
}
Console.WriteLine("got data from transaction");
}
public void Publish()
{
if (curState == State.Published)
{
throw new InvalidOperationException("cannot publish already published transaction!");
}
Console.WriteLine("published transaction");
curState = State.Published;
}
}
Transaction myTransaction = new Transaction();
myTransaction.GetData(); // Runtime error!
The problem with this is that someone could create a Transaction
object and try to call the GetData()
method before calling Publish()
, and the compiler wouldn't complain, even though it should be possible to infer what methods are available at compile time.
Is the only way to do this to return a new object of a different class in the Publish()
method? If so, is there any way to stop someone from trying to call Publish()
again on the old object, like this:
UnpublishedTransaction myTransactionVariable = new UnpublishedTransaction();
PublishedTransaction myNewTransactionVariable = myTransactionVariable.Publish(); // Return a new object of a different class that doesn't have Publish().
myTransactionVariable.Publish(); // Runtime Error! Can't publish twice. Or, even worse, return another object that represents the same underlying transaction.
myNewTransactionVariable.GetData(); // *Can* get data from published transaction.
Even if there is, this way of doing it looks ugly since I have to create a new variable for the object returned by Publish()
.
Am I doomed to throwing runtime errors, hoping that other developers are good at predicting the state of the object and read the documentation?