7

If class A has a unique interaction with each of classes B, C and D, should the code for the interaction be in A or in B, C and D?

I am writing a little game whereby lots of objects can have unique interactions with other objects. For example, an EMP hits a sentry gun and disables it. It could also hit a grenade and detonate it, it could also hit a player and applying a slowing effect.

My question is, should this code go in the EMP class, or be spread amongst all the other classes? My gut tells me to do this polymorphically so that I simply tell each class to deal with the EMP strike however it likes, which would allow me to add more items and change how they deal with the EMP without changing the EMP code.

However, the EMP only currently interacts with about 4 out of 50 of my objects, so filling 46 objects with empty RespondToEMP() calls seems wrong. It also seems somewhat unintuitive, if I decide to remove the EMP, I need to change every other class, and the EMP class itself ends up fairly tiny. It also means if I want to change how the EMP behaves I need to go looking through all the different classes to find all the possible usages. Additionally, if the EMP has some generic effects such as an explosion, this effect would definitely be inside the EMP code, away from all the other effects which would be distributed.

Michael Parker
  • 7,180
  • 7
  • 30
  • 39
  • You didn't mention what programming language that you are using, but this sounds like a perfect use case for a protocol (in objective-c) or a interface (in Java). Then, when your EMP detonates it can check to see if the objects that are in range conform to the protocol and call a protocol method if it does. – lnafziger Mar 07 '13 at 02:20
  • It would also be good to have a central class to handle these interactions. I.e. a "Interaction" class with a method that is called every time that an action which can result in interactions happens. Then, you can loop through all objects "in-range" and say `if action is EMP and target conforms to protocol EMP then send EMP_DETONATED message to target`. This way when you add new interactions, all of the code is in one place and you only implement the protocol in the classes where it is appropriate. – lnafziger Mar 07 '13 at 02:23

5 Answers5

1

My thoughts would be that sentry gun, grenade and player should all be implementing a common interface that enforces the method RespondToEMP. This way, if you later decide that one of your other 46 objects can be hit by an EMP, you'll know to implement this interface.

juco
  • 6,331
  • 3
  • 25
  • 42
  • Ok so then I have 3 objects which implement the interface, and 47 objects which don't. When my EMP explodes I know that it has hit some objects, how do I know if the object it has hit has implemented this interface or not? – Michael Parker Mar 06 '13 at 19:25
  • I'm not sure I understand the question, but you should be able to use [reflection](http://en.wikipedia.org/wiki/Reflection_(computer_programming)) for that. All logic regarding the "hit" would be handled in the class implementing the interface. – juco Mar 06 '13 at 19:33
  • Let me clarify - My emp explodes, and the engine gives me a list of entities which the EMP has hit. Only a minority of these entities need to interact with the EMP. So you're saying to check each object in turn whether that object implements an interface using reflection, then calling that classes function if it does? Aren't you effectively checking types to switch logic, basically the opposite of what polymorphism is supposed to accomplish? – Michael Parker Mar 06 '13 at 19:45
  • Why would you need to know which objects implement the interface? The objects that do implement it would do whatever they are supposed to do when EMP explodes (because they subscribed to EMP exloded event) and if some other code needs to know about what these objects did when EMP exploded you can either implement a callback or publish an event other code can subscribe to... – Dean Kuga Mar 06 '13 at 21:53
  • This is what I was suggesting in my comment above. It is not the opposite of polymorphism as you are checking whether or not a generic object implements an interface (or conforms to a protocol, depending on your language). You don't care what the object is, just whether or not it wants to talk to you. – lnafziger Mar 07 '13 at 03:42
1

you may want to consider http://en.wikipedia.org/wiki/Visitor_pattern. if you have more than 2 things interacting together take a look at my answer to Managing inter-object relationships

Community
  • 1
  • 1
Ray Tayek
  • 9,841
  • 8
  • 50
  • 90
1

You are right, having a dummy method is a code smell. There are a couple ways to get around this:

Switch statements to handle events works well if you expect to add more objects because you will avoid having to modify events when you add more objects:

enum Event { EMP, Bomb }

class SentryGun {
    void handleEvent(Event e) { switch(e) {
        case EMP: disable();
    }}
    void disable() {}
}
class Grenade {
    void handleEvent(Event e) { switch(e) {
            case EMP: detonate();
    }}
    void detonate() {}
}

Events that visit objects works well if you expect to add more events because you avoid having to modify every object that can now react to that event:

class EMP {
    void hit(SentryGun object) {
        object.disable();
    }

    void hit(Grenade object) {
        object.detonate();
    }
}
Garrett Hall
  • 29,524
  • 10
  • 61
  • 76
  • It's a little confusing mentioning events when you're not refering to what most people consider events (observer pattern and/or .NET events). What you're doing here is effectively creating an enum to represent class types, then switching on it, which seems just as bad as casting the object and checking for null. Every time you create a new class you'll have to update your enum list which both sides depend on. At least with a cast you can't forget this extra step. In your second example you're assuming you've already cast the object, and all the problems I outlined in my question still apply. – Michael Parker Mar 07 '13 at 18:16
0

This isnt the most elegant solution, but one thing that you could do is to have a base class that your objects extend that have a default behavior for an event, and objects could override it if there was something to do.

John Kane
  • 4,383
  • 1
  • 24
  • 42
0

In your case at some point you need to distinguish objects that can interact or not. There are three options:

  • Common interface with possibly empty implementations
  • interface and checking whether object implements it
  • define some map/table with interactions and reactions

As first two were presented in other answers I will describe third option. We define mapping: Thing x Object -> Command. In this case EMP_hit would be Thing (maybe names could be better ..), potentially contain information about its source. Objects would be all your stuff. Command would be similar to Command Pattern (see http://en.wikipedia.org/wiki/Command_pattern). I would store this in maps instead of table to keep only non-empty Commands and use some String IDs that would be shared across Things/Objects of the same class to find proper entries in this mapping.

This solution has this advantage that you can define it in some config file or/and change dynamically in the run-time. No recompiatlion would be needed to change configuration

kkonrad
  • 1,262
  • 13
  • 32
  • Interesting idea, but isn't this going to eventually have the same code for typechecking objects and switching logic based on the result? i.e. `if (source is EMP && target is SentryGun) Disable()`? Or if I call an EMP specific function: `if (target is SentryGun) Disable()`. In that case I may as well put the code in the EMP: `if (target is SentryGun) Disable()`. – Michael Parker Mar 07 '13 at 18:30
  • Yes and no ;) In this case your command could have function do_command(thing, object) -> void or status. I'm not sure whether there is point to check type as if you created this map/table properly you should have guarantee of correct type - so you just downcast objects to expected types. No `if` or `switch`. Advantage is that you can easily change behavior - for example when setting of the game changes into different plant than EMP might not work on anything - you just use different table or modify existing one. You separate this action definition from objects that are involved. – kkonrad Mar 07 '13 at 19:39
  • Correction: you separate objects definition from actions. also I meant planet - not plant ... – kkonrad Mar 07 '13 at 20:22