Sign in to follow this  
Telastyn

Interface Tagging?

Recommended Posts

I recently had a dispute over the use of empty (or otherwise duplicated) interfaces/abstract classes to 'tag' a class.

What do you guys think? Useful trick or bad practice?

I gave my argument in the context of mainstream languages that can even do this sort of thing (C++/C#/Java), but a wider discussion is fine by me.

Share this post


Link to post
Share on other sites
I find that to be a useful trick. If it helps you or the others in the discussion, you could research the <a href="http://en.wikipedia.org/wiki/Marker_interface_pattern">marker interface pattern</a>. I imagine there are quite a few positive and negative viewpoints out there on the topic.

Share this post


Link to post
Share on other sites
I sometimes use empty interfaces to indicate a 'grouping' of classes without actually sharing any implementation between those classes. This is kind of a code maintenance aid for me when I want to keep track of how things are intended to be related, but without having a common RUNTIME interface (i.e. "find all references" is better than find-text-in-comments).

I only use empty interfaces when it *doesn't* make sense to use an ABC or non-empty interface.

Since you can add and remove them without really affecting any code, I don't think there are any real downsides. Edited by Nypyren

Share this post


Link to post
Share on other sites
I'm curious what the "counter" argument was, obviously made by your unnamed assailant?

As for marking, I find it quite useful but the use cases tend to be rather rare. There is, in my opinion, a difference between a true marker interface and an interface that is, for whatever reason, simply empty.

Share this post


Link to post
Share on other sites
I would find it pointless. If two classes don't have anything actually in common, why should they share a class? If its a placeholder I might see the benefits; that you may add shared attributes between those classes in the future.

Its mostly a matter of taste though, as far as I can tell. There wouldn't be any performance issues of an empty class.

A prolific use of classes that are badly conceptualized is however a bad thing. Without knowing the context of your particular case, I'd say the extra empty class and the two inheriting from it would be evidence of this.

I like OO structures, but they are usually abused and overused. Don't use inheritance where it's not called for.

Share this post


Link to post
Share on other sites
[quote name='Kyan' timestamp='1339654633' post='4949058']
I'm curious what the "counter" argument was, obviously made by your unnamed assailant?
[/quote]

No, I was actually on the bad practice side.

To me, they can only end in badness. If you have an empty interface, and a method takes that interface you can't actually [i]do[/i] anything without: casting, "if X is T" stuff, reflection, or type trait shennanigans.
[list]
[*]Casting out of an interface is bad. If you knew you were getting a meaningful type, then say that. If you might get multiple types... well that leads to #2:
[*]"If X is T" is bad because it violates the open closed principle. You [i]can[/i] put your marker interface on things, but you can't actually do things with it until you modify the giant "If X is T" block.
[*]Reflection is reflection. To be avoided. Plus, languages with reflection have attributes for this sort of thing.
[*]Type traits work in C++, but why not just use the concrete type with the trait at that point?
[/list]
If you have a non-empty interface that is identical to another except for the name, then you have different issues. Either they:
[list]
[*]Do different things with the same name; in which case you need better names. If one ToString is human readable and another is a serialization string... maybe you should be clearer.
[*]Behave differently due to the type name. This is the worst case, and (as is my understanding) where this pattern is most used. The issue here is that you're using the type system to do everything except enforce behavior. [b]That's what it is there for[/b]!The behavior differences don't exist in the marker, they exist in the implementors. At that point, nothing but social contract enforces that a certain marker is used as expected; worse yet, those expectations are often based on the real world behavior of the type name. These behaviors are assumed by developers, and will be different depending on their personal expectations/experiences.
[/list]
Again, I made my argument against C++/Java/C#. Something like Scala with actual traits behave differently. Hell, I use this sort of thing in Tangent coding, but that's because Tangent allows me to compose types on the fly so that tags can be applied partially (and easily).

Share this post


Link to post
Share on other sites
[quote name='Waterlimon' timestamp='1339680926' post='4949155']
Can someone give examples of in what kind of situation you could use interface tagging?
[/quote]

This was provided in the original discussion:

http://orchard.codeplex.com/SourceControl/changeset/view/9eeaf6cf48d2#src/Orchard/IDependency.cs

Share this post


Link to post
Share on other sites
[quote name='Telastyn' timestamp='1339680643' post='4949153']
[quote name='Kyan' timestamp='1339654633' post='4949058']
I'm curious what the "counter" argument was, obviously made by your unnamed assailant?
[/quote]

No, I was actually on the bad practice side.
[/quote]

And I'm with you. I won't repeat your bullet points though.

Share this post


Link to post
Share on other sites
"Tag" interfaces are a code smell [i]in most cases[/i]. I'm sure someone could come up with some template trait hackery where they are used and I wouldn't really be able to fault it, but that's rare in my experience. Most of the time tag interfaces are incorrect attempts at misusing upwards casts in an inheritance hierarchy in a misguided effort to be type safe, at least from what I've seen. If your types have something in common, it probably isn't an empty interface.

Tag [i]classes[/i] - by which I mean classes which are empty but not derived from - are fine IMHO. I use them for things like compile-time type dispatch instead of runtime (potentially virtual) dispatch. For instance:

[code]struct FooTag { };
struct BarTag { };

struct Operations
{
void Frobnicate(const FooTag& tag)
{
std::cout << "Do some foo!\n";
}
void Frobnicate(const BarTag& tag)
{
std::cout << "Do some bar!\n";
}
};[/code]

Saves a switch on an enum or something similar, and in certain code organizational patterns can be handy. N.B. that nothing derives from the tag objects, they're just dummy types used to tell the compiler that a Foo isn't a Bar. Edited by ApochPiQ

Share this post


Link to post
Share on other sites
Still smells to me. If the code knows enough to tag with foo and bar, shouldn't it know enough to provide an implementation of a common Frobnicate interface?

Share this post


Link to post
Share on other sites
Only if there's some kind of member data or other important context involved external to the Operations class.

Consider traversing an AST (which is where I use this pattern most heavily in my personal code, for instance). If we use a traditional Visitor pattern, I traverse a Token. But suppose the Token's meaning is dependent on the context in which it is found. Instead of having an IdentifierToken and a KeywordToken and maybe a LiteralToken, all I need is a Token which represents all of them, and a "context switch" set of tags which the AST traverser is fed to know how to operate on subsequently traversed nodes.

Share this post


Link to post
Share on other sites
[quote]Plus, languages with reflection have attributes for this sort of thing.[/quote]
Ah, I see. I apologize, then, as - upon reading the Wikipedia article j-locke linked - I realize I don't use markers like that at all, for the reasons you mentioned (and that I've discovered in practice. Anyone who has worked with an object/primitive boundary has tried it at some point ...). In my defense, I like to use markers as a sort of programmer-level metadata and documentation for a specific attribute or feature that I know won't change - a basic example would be [i]java.util.RandomAccess[/i]. For everything else (including serializabiliy) annotations are much better (as are the tools for using them).

I'm in complete agreement with the inherent evil then, heh.

Share this post


Link to post
Share on other sites
Yeah, Nypren's use as a 'Find all references' placeholder doesn't seem totally evil. Though in an ideal world one wouldn't need to abuse the type system to compensate for inadequate tools. Edited by Telastyn

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this