2

I'm writing a simple game in Unity, and learning C# on my own. Currently I'm doing first pass on the scoring system. I decided to do this with native c# events. So my first idea was to have the Score class responsible for counting/keeping player score. This class would receive events from objects implementing the IScoreable interface.

It would look something like this (example code):

public interface IScoreable {
  event Action<IScoreable> Scored;
  int Value { get; set; }
}

public class Score { 
  public void addScore(IScoreable scoreable) { 
     //do something with scoreable.Value 
  }
}

In theory, it's decent enough, but I had a problem with coupling:

With just the above code, Score needs to know about all possible objects that implement IScoreable, so it can subscribe to the Scored event. Considering there will be a lot of various objects implementing this interface - it may get instantiated from different parts of code, I don't see any "clean" way of doing this.

Another option would be, to have every IScoreable object register itself with Score object, but this again would create strong coupling. For example adding another player, and subsequently another Score instance, would require rewriting all classes implementing IScoreable)

Which leaves me with two alternatives (that I see). Create some kind of event manager/ aggregator. This option is now for me (subjectively speaking, I like how in c# events are strongly connected to class that defines them).

Another option, the one I'm leaning towards, is to use a static event for this. This would require a switch from interface to abstract class for IScoreable. Which could be a big deal with c# single inheritance, but in this case it isn't (Unity component based approach, discourages deep inheritance trees) I think it would fit this use case quite well.

public abstract class Scorable {
  public static event Action<Scorable> Scored;
  protected virtual void onScored()  {  if (Scored != null) Scored(this);  }
  public int Value { get; set; }
}

Score object would simply subscribe to Scored, and be done. Every class inheriting from Scoreable would call base.onScore when needed, and be done. No additional classes would be needed. Other than possibility of memory leak - if not careful I don't see downsides to this.

But since the use of static events seems to be discouraged, I have my doubts, maybe because of my inexperience I don't see a simple solution.

So the question... Is there a better (cleaner, simpler) solution to my problem? Would you advise against the use of static event in this case?

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • "`Score` needs to know about all possible objects that implement `IScoreable`" why? The point of using an interface is so you _don't_ have to know what the implementation is. – D Stanley Jan 30 '14 at 21:04
  • 1
    I don't think an event is the way to go. A singleton for the Score class would probably be the best route. Each IScorable object would handle their respective event(s), ask for the singleton instance of Score and call the applicable method.. – AWinkle Jan 30 '14 at 21:14
  • WeakEvents may be something you want to take a look at, here is an excellent article on them: http://www.codeproject.com/Articles/29922/Weak-Events-in-C – David Venegoni Jan 30 '14 at 21:31
  • @DStanley what i meant was, in my first example 'Score' would need some way of accessing all instantiated objects implementing `IScoreable`, so it could subscribe to the event. –  Jan 30 '14 at 22:32

2 Answers2

1

I think you have this backwards. Rather than have a Score object that knows about everything that can change the score, you should have all of your objects that might update the Score know about a single instance (a singleton) Score object.

Really simple case might be something like:

 public class Score 
 {
     public int TheScore { get; private set; }

     public static _instance;
     public static Instance     // I'm using a static property, but you could also create a static "getInstance" method if you prefer
     {
         get 
         {
             if (_instance == null) 
             {
                 // Note: if you are multithreading, you might need some locking here to avoid ending up with more than one instance
                 _instance = new Score();
             }
             return _instance;
         }
     }

     private Score() 
     {
         // Note: it's important to have a private constructor so there is no way to make another instance of this object
     }

     public int AddToScore(int score)
     {
         // again - if you have multiple threads here you might need to be careful
         TheScore += score;
     }
 }

Now in your object that might update the score, you just:

 Score.Instance.AddToScore(100);     // adds 100 to the score

Now if you want to get really fancy here, you could abstract the Score class into an interface IScore and then use an inversion of control (IoC) container to manage it for you. That way you can decouple the implementation of the class that holds the score from the classes that will consume it.

And of course to get the current score:

 int currentScore = Score.Instance.TheScore;

If you want to use events, then you could look at the publisher/subscriber pattern where you'd basically have a singleton that acts as the manager for your events and everybody that need to publish an event (i.e. your Player class) will publish to it and your Score class will subscribe to it.

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
  • Yah, I should have mentioned that I'm trying to minimize use of singletons, sorry for that. And while singleton makes sense, it could cause some problems later when i add more players, or different game mode that uses separate scoring system. –  Jan 30 '14 at 22:40
  • Why and how so? What do you have against singletons? – Matt Burland Jan 31 '14 at 04:00
  • You could easily extend this into a singleton `ScoreManager` which will basically manage separate `Score` instances (no longer singletons, but created by the `ScoreManager`) for each player / game mode / whatever. Then your scorable items would call a method in the `ScoreManager` with the updated score plus some key which identifies which instance of `Score` it should target. At that point, you are pretty close to a publisher/subscriber model. – Matt Burland Jan 31 '14 at 18:10
  • I'm going to accept your answer, I was overthinking the problem. Singleton is in fact simpler/cleaner alternative for events in this case. –  Jan 31 '14 at 21:34
  • Singleton and static methods are not good for Unit testing and will tightly couple your code, which in turn will make your code stale and ripe for refactoring at a later point. – Bjørn May 03 '18 at 08:49
  • @Bjorn: Not if you do it right. Most DI frameworks even have functions for creating singletons. – Matt Burland May 03 '18 at 12:50
  • What a DI Framework supports or not does not make the use of a feature correct in all scenarios. That being said, Singletons are not all bad, in fact sometimes it's the right tool for the job. Altho this time, as a reference to keep score for a player, it seems like the wrong path to take. What if a multiplayer feature were to be added? Would you then have multiple singeltons for P1, P2 etc? I strongly agree with the idea of the score not knowing about everyone who would potentially change it tho :) – Bjørn May 04 '18 at 10:22
0

Here are a few changes that will give you much smoother coupling

 public interface IScoreable {
      event EventHandler<IScoreable> Scored;
      int Value { get; set; }
    }

Create a new ScoreEventArgs class to handle your event args in a more flexible way

public class ScoreEventArgs: EventArgs
{
  public int Score {get;set;}

  // Can put more properties here to include when the event is raised

  public ScoreEventArgs(int value)
  {
    this.Score=value;
  }
}

Changes to your abstract class to ensure that event is handled according to your needs

  public abstract class Scorable {

  event EventHandler<ScoreEventArgs> scored;
  public event EventHandler<ScoreEventArgs> Scored;
  {
      add { this.scored += value; }
      remove { this.Scored -= value; }
  }
  // onScored method now handles the object (could be player in your case) where its called from and the score

  protected virtual void onScored(IScorable sender,int score)  
  {  
      if (Scored != null)
      {
         var e = new ScoreEventArgs(score)
         Scored(sender,e);  
         // Could set optional this.Value += score; // to get the current score or something like that 
      }
  }
  public int Value { get; set; }
}

Your calling class which will implement the event and will have a score

public class Player : Scorable
{
     public Player()
     {
        // Also can register a scored event callback 
         Scored+= new EventHandler<ScoreEventArgs>(PlayerScored);
     }
     private void PlayerScored(object sender, ScoreEventArgs args)
     {
         // Other logic can go here as well, this method will be called after the onScored(this, e) is called from the PlayerScored method.. You can update your UI or do other stuff here
     }
     public event EventHandler<ScoreEventArgs> Scored
     {
          add {this.Scored+=value;}
          remove {this.Scored += value;}
     }
     private void PlayerScored()
     {
         // Raise the on scored event
         onScored(this, new ScoreEventArgs(10));
     }
}

Hope this clears some ambiguities..

Faaiz Khan
  • 332
  • 1
  • 4
  • This is quite good! The only thing i would reconsider is the inheritance here at the end. A player might want to inherit from other classes as well, which does not work in C#. If you tried a Composition flavor instead, i think this example would shine for all to see :) – Bjørn May 03 '18 at 08:51
  • I don't really understand how this is any improvement, or how it answers the question, can you clarify it? The most important thing is that it doesn't help with the issue of having to keep track of all instances of `IScoreable`, which is the main point of the question. Then other things: I'll assume `Scorable` implements `IScoreable` (otherwise, why have the interface at all?), which is something missing in the code; but then, is the interface just used by `Scorable`? If so, why have the interface at all, and not just use the abstract class? Or even better, why not use only the interface? – Trisibo Jul 13 '19 at 17:16
  • Then we have `Player` adding an event handler for its own event? This is very confusing, as is having 2 versions of the method `PlayerScored` that do very different things. And several other things that just add stuff for no gain and add to confusion, like having a property for the event on `Scorable` (why?) and another property for the event on `Player` that just uses the property on `Scorable`, or the `ScoreEventArgs` which, although considered good practice, doesn't really add anything to the question. I have to -1 this answer unless somebody clarifies what I am missing. – Trisibo Jul 13 '19 at 17:16