"However, since not every card is going to have the correspondingabilities, I realised I would have to throw something along the linesof UnsupportedOperationException"
You're thinking about it in the wrong way. As you outlined:
- A card must have a primary ability
- A card may have an "ally" ability. If it does, it can be triggered, given a certain condition is satisfied. Actual condition is notrelevant to the discussion.
- A card may have a "sacrifice" ability. If it does, it can be sacrificed, removing it from play, to trigger this ability.
These statements, in their essence, do not describe an interface; these are really the rules of the game.
When a card doesn't have the "ally" ability or the "sacrifice" ability, it's not an exceptional circumstance - so you shouldn't treat it as such (i.e., you shouldn't throw an exception, except maybe at construction time).
Given this perspective, you have different options here - you could retain the same interface, and implement these optional abilities as no-ops (Null Object Pattern, or something along those lines). Or, you could introduce the Ability object, and just have your interface be something that returns a list of abilities, or maybe a list of abilities by category (where you'd return an empty list when there are no abilities in that category). You'd probably have to modify your surrounding logic to work with this new interface, but doing one of these things avoids conditionals and null checks, and makes your code cleaner and more readable (as in, if you do it right, you'll find the code easy to make sense of).
That said, I'm not really trying to suggest any particular solution here, I just want to steer your thinking in a different direction. You know what the gameplay should be, so you'll probably come up with better ideas than I can produce. It's worth going down that path just to sort of "explore the design space", even if you decide to backtrack.
Finally, it's not necessarily the best idea to model different kinds of cards as different types (subclasses). If you have the Ability class, then Card could just be one class, and different kinds of cards would be represented by different combinations of Abilities. In such a scenario, the abilities might form a type hierarchy instead. That's quite a bit more flexible (you yourself have noticed that it would be impractical to have a separate class for each kind of card), and it's likely to work better with an editor or game UI. You could do other cool things, e.g., you could have prototypical card instances that you load from a file (perhaps created in an editor), and clone them when you need new instances of that type to form decks and whatnot.