If you really don't want to leak details of "privileged" operations then the cleanest answer in popular OO languages like Java and .NET is multiple interface inheritance.
One that I've used semi-frequently goes something like:
IFooRepository - contains methods
IReadOnlyFooRepository - contains only method
In the example above,
IFooRepository might subclass
IReadOnlyFooRepository. Also, if there is a well-defined way to transition between the read-only and writable state, then you might put an
Edit method on the
IReadOnlyFooRepository that returns an instance of
IFooRepository, and/or an
AsReadOnly method on the
IFooRepository that returns an instance of
This is not without precedent, at least in .NET. One of the most common interfaces,
IList<T>, has a method
AsReadOnly that returns a
ReadOnlyCollection<T>, which as you might guess, does not expose any kind of write operations.
It's not hard to implement - generally you only need one class to implement both interfaces, and the
AsReadOnly methods can just return
self or whatever it is in your chosen language). Of course, if you do this, don't fool yourself into thinking that it's a form of security, as a caller could potentially just cast between types - but assuming you just want to protect against stupid mistakes rather and not intentional abuse, it's good enough.
You can get more granular if you want - for example, you don't necessarily need one interface to derive from the other, you could have
IFooWriter with no methods in common. Or you might define an
IFooComponent (or something along those lines) at the root if you only want to share a few methods, but have most of them be unique. Sky's the limit, but the basic idea stands - use interfaces to carve out specific blocks of functionality that are specific to a particular state.
This takes a lot more design work and adds a lot more types to the code if your states and transitions are complex, so be sure that it's really worth the effort. It's perfectly fine and in fact common in a lot of architectures (like most MVVM frameworks) to just throw an exception if an operation isn't valid for the current state, and also provide convenience "guard" methods like
CanWrite, to avoid strewing try-catch loops all over the place. That's good if you have 2 or 3 discrete states; if you have 10 or 20 then you might want to think about either a state pattern or a design such as the above that makes the transitions more explicit.