On 21-Mar-17 9:11 PM, Christopher Pisz wrote:
>
> I talked to a friend of mine during lunch, whom happens to be a .NET
> developer. He claims that any time he sees an enum that it raises a
> red flag for him. He'd rather create class for each state.
>
> If we use an example where we make a class Socket with enum
> {CONNECTED, DISCONNNECTED} and then derive a class
> MySpecificClientSocket that wants to add a state LOGGED_IN and
> perhaps other derived classes that have yet more states that are only
> used in their specific use. He'd create a MySpecificSocketConnected,
> MySpecificSocketDisconnected, MySpecificSocketLoggedIn, classes that
> implement the specific functionality that is permitted in their
> states. That would make for a lot of classes! I guess that comes from
> a DDD mindset.
>
> Thoughts on that?
It sounds as if you were discussing using the enum to discriminate
between different sets of functionality, different logical sub-types.
In that case (but not in the case of just using the enum to e.g. report
the current connection status) I'd agree with your friend.
Essentially, with NF different functions, each of which might or might
not apply in a given state, and each of which might be implemented
differently in some states, and with NS different states, the main
choice is between
A) NF function implementations, each of which internally discriminates
NS different states and do possibly different things in each state, or
B) MS×NF function implementations, where each set of NF implementations
is appropriate for and need consider only a given state.
The total code size can be about the same, but the latter is more
maintainable because (1) it /separates concerns/, and (2) it avoids the
/type discrimination/, which is a common anti-pattern because the
discrimination has to be updated in each of umpteen places in the code.
In order to present (B) to client code as a simple set of only NF
functions, one can use the envelope/letter idiom. As far as I know it
was first named as such by Coplien. See <url:
http://tinf2.vub.ac.be/~dvermeir/c++/EuroPLoP98.html#EnvelopeLetter>
(the images are now lacking, they may have been archived by the Wayback
Engine, possibly).
For example, the Eiffel language lacks enum support because Bertrand
Meyer wanted to discourage the common anti-pattern of discriminating on
a type indicator value (having a value that specifies how to interpret
the data) rather than using language-supported polymorphism such as the
the virtual functions of the envelope/letter-idiom.
The problem with the indicator of logical type is that, in general,
proper maintenance becomes impossible as the system grows. For when a
logical subtype is removed, or a new one is added, all places that do
such discrimination must be updated, regardless of how well hidden and
convoluted the discrimination is in each case (e.g. relying on an
assumption about how state has been set earlier in code in a completely
different source). In one very limited sense it's a case of redundancy
gone amok, having that discrimination repeated all over the place, but
it's worse because there can be e.g. logical subtype relationships that
are leveraged in different ways in different places, yielding no simple
pattern to recognize the various instances of discriminating code.
On the third hand, each case is unique, just as each human is unique,
and as each model of car is unique. “Oh it's a Ford, then it's prone to
rusting” is invalid reasoning when one knows which particular Ford model
this car is, or when one knows even more specifically that it's a 12
year old car with no rust so far. It's no good to go all overboard with
patterns and classes every which way if /a much simpler/ and smaller
solution is obtained with an enum (one case that springs to mind is a
discriminated union for a compiler's token handling). ;-)
Cheers!,
- Alf