1

After many years, I still unable to determine the correct way to define a class/function to play sound. Suppose my app may need to play some sounds, for example: button click, error popup,... How should I define the function?

** Method 1: one single function for all sounds**

//simulate languages features that enums are numbers
const Sound={
    BUTTON_CLICK:0,
    ERROR_POPUP:1,
};

class MyClass{
  static playSound(sound){
    switch(sound){
      case Sound.BUTTON_CLICK:
        SoundLibrary.playSound('button_click.mp3');
      break;
      case Sound.ERROR_POPUP:
        SoundLibrary.playSound('error_popup.mp3');
      break;
    }
  }
}

** Method 2: one function for each sounds**

class MyClass{
  static playButtonClickSound(){
    SoundLibrary.playSound('button_click.mp3');
  }

  static playErrorPopupSound(){
    SoundLibrary.playSound('error_popup.mp3');
  }
}

** Method 3: one class for each sounds**

class ButtonClickSound{
  static play(){
    SoundLibrary.playSound('button_click.mp3');
  }
}

class ErrorPopupSound{
  static play(){
    SoundLibrary.playSound('error_popup.mp3');
  }
}

** Method 4: (other methods)**

Which way should I use?

3

Each method has its strengths and weaknesses. However if your program doesn't stand to benefit from the strengths, the added complexity is still a weakness.

Method 2

Method 2 for instance will simplify your code. Rather than pass the proper parameter, you need only call the proper function. However this also means that if you wind up having 100 sounds, you'll have 100 methods. It isn't scalable, and honestly, they all ultimately do the same thing conceptually. What changes is the value being passed to SoundLibrary.playSound.

Method 3

Method 3 won't have a single class with 100 methods, but you will have 100 classes! This may be a strength if you're dynamically loading the class, say you have a configuration that selects a list of classes pointing to the class containing the sound to be played, effectively allowing you to change the sounds without creating another program version. However, this is going to create serious clutter with a lot of sounds.

Method 1

Method 1 is the best of the methods mentioned and more sensible for the caller, having to pass only the proper enum value pertaining to the sound to play, however you again risk to have a gigantic switch statement containing 100 cases.

Method 4

Consider method 4:

const Sound={
    BUTTON_CLICK:"button_click.mp3",
    ERROR_POPUP:"error_popup.mp3",
};

The enum value now is now 1-to-1 associated with the path to the file of the sound. Your program continues to use the enum values, without having to know anything about the path or how to play it. You need only create a single function which will play the associated sound file associated with the enum value.

I can't be sure which language this is, but if your language doesn't support associating values with it, you can still load a configuration file with = lines defining the exact path associated with each enum. The function tasked with the job of playing the sound will look up the enum's path and then call the sound library with the associated path.

This solution is probably ideal in any situation where you may potentially be dealing with a large number of sounds in the future as it scales well. If you're dealing with a pet project with a small number of sounds, method 1 would likely suit you just fine. Otherwise, I highly recommend method 4.

  • 2
    Method 5(=Method 4 + Method 1): Initialize a map (if available in your language) with the mapping between enums and sound file names (in the constructor of the class), and use that map instead of a switch-case in the play-sound functions (should scale better than switch-case, and you neatly separate the "play" part from the "determine the sound file" part) – CharonX Mar 20 '18 at 7:58
  • @CharonX True, this could work well too. At that point ideally the map would be a private static reference in the same class tasked with playing the sounds. – Neil Mar 20 '18 at 8:22
  • I definitely recommend the map over the switch. Depending on the language, it can remove an entire class of errors. – RubberDuck Mar 20 '18 at 9:23
  • @RubberDuck, also, unless the language optimised that switch to a map anyway when compiled, using a map can increase performance too. – David Arno Mar 20 '18 at 9:57
3

All three solutions suffer from a number of problems:

  1. You are using a static method to create side-effects,
  2. You are tightly coupling these methods to SoundLibrary,
  3. You are using "magic strings" rather than constants.

I'd suggest you should focus on fixing those issues, before worrying about how many methods and classes to use.

When you have addressed them, then you should consider the idea of using events to trigger the sounds, rather than calling methods directly.

And finally, I agree with Neil: use a map data structure to map your enum values or event names to sounds, to avoid gigantic switch statements or large numbers of methods/classes.

2

I suggest to try looking at it first more from the perspective of the consuming client rather than the implementation.

What do you want the consuming client to be able to do? For example,

  • Are you going to support sound schemes that can be changed on the fly?
  • Add new things that can make sounds on the fly?

Without knowing what you're trying to provide it will be hard answer to which of your shown implementations is better than the others.

That being said, still, beyond the first approach, the classes you're suggesting and the static methods you're suggesting don't buy us much in terms of an abstraction — they amount to packaging, which is useful, but there is a lot more to good & usable abstractions than packaging. Those classes hide mp3 file names from the consuming client, but then trade-off hard coded class and/or method names.

The enum is a reasonable way to identify the things that produce sounds if the list is static, meaning it is only edited/added-to during development and not at runtime.

If you need a dynamic capability, then you'll need to be able to hand out different keys. You'll also want to consider mapping the keys to human readable texts for when you want to help the user change their sound scheme.

Focus first on the overall abstraction that you want your clients (probably you) to consume, then implement that abstraction using the tools of the language (like interfaces, classes, etc..).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.