【问题标题】:How to check conditions without multiple if statements?如何在没有多个 if 语句的情况下检查条件?
【发布时间】:2017-06-05 20:52:24
【问题描述】:

我正在 Xamarin 中为 android 开发游戏,但我有两个非常干燥的特定部分(不要重复自己)。

第一部分是当我想要更改配乐(音频播放)或背景时,我根据级别为同一配乐设置了三个不同的音高,而背景画布交替的背景也是如此。

对于这些方法,条件基于等于玩家所在级别的整数级别。

示例代码

private void SetBackgrounds()
{
    if (level == 5)
    {
        gameAreaCanvas.SetBackgroundResource(Resource.Drawable.LevelUpOneBackground);

    }
    else if (level == 10)
    {
        gameAreaCanvas.SetBackgroundResource(Resource.Drawable.LevelUpTwoBackground);

    }
    else if (level == 15)
    {
        gameAreaCanvas.SetBackgroundResource(Resource.Drawable.LevelUpThreeBackground);

    }
}

代码的不同部分也是如此,其中 alot 基于一个整数值,即级别。每次玩家将级别整数递增 1,然后 Activity 具有检查整数级别是什么的方法。代码可以运行,但显然效率很低,因为有很多重复的代码,只需稍加调整即可。

例如,一个关卡看起来像这样。

        if(level == 1) {

            levelDisplay.Text = "LEVEL 1";
            timer = new Timer();
            timer.Interval = 2000; /// DIFFERENT
            timer.Enabled = true;
            timer.Elapsed += Level1; /// DIFFERENT
            timer.Start();
        }
        ///LEVEL 2
        if (level == 2)
        {
            levelDisplay.Text = "LEVEL 2";
            timer = new Timer();
            timer.Interval = 2000; /// DIFFERENT
            timer.Enabled = true;
            timer.Elapsed += Level2; /// DIFFERENT
            timer.Start();
        }

有没有办法让这段代码不那么干?感谢您的意见。

【问题讨论】:

标签: c# android if-statement dry


【解决方案1】:

第一部分可以精简如下:

canvas.SetBackgroundResource( level == 5? Resource.Drawable.LevelUpOneBackground : 
                              level = 10? Resource.Drawable.LevelUpTwoBackground :
                                          Resource.Drawable.LevelUpThreeBackground );

或者,更好的是,创建一个字典,将级别编号映射到背景,这样你就可以拥有这个:

gameAreaCanvas.SetBackgroundResource( resourcesFromLevels[level] );

简化第二部分涉及更多内容。

在这种情况下,一种可能的解决方案是继承。

您创建了一个新的抽象class Level 来表示您的游戏关卡,并为每个特定关卡创建该类的子类。因此,您将拥有class Level1: Levelclass Level2: Level 等等。基类有一个Setup() 方法,该方法通过调用自身的覆盖来工作,每个覆盖都有一个默认实现,但Level 的后代可以提供自己的实现。

此外,并非所有事情都必须由可覆盖对象处理。 Level 类可以接受一些构造函数参数,例如级别名称,然后每个后代都可以为基类提供正确的级别名称。所以,它看起来像这样:

class Level
{
    readonly string levelName;

    Level( String levelName )
    {
        this.levelName = levelName;
    }

    void Setup()
    {
        levelDisplay.Text = levelName;
        SetupTimer();
    }

    virtual void SetupTimer()
    {
         //Default implementation
    }
}

class Level1: Level
{
    Level1() : Level( "LEVEL 1" ) 
    {
    }

    override void SetupTimer()
    {
        //Level1 implementation
    }
}

【讨论】:

  • 有趣的解决方案。试图找出将计时器与此解决方案集成的解决方案。
【解决方案2】:

您在寻找字典吗?

//TODO: please, check dictionary's value type
private static Dictionary<int, Resource.Drawable> s_Backgrounds = 
  new Dictionary<int, Resource.Drawable>() {
    {5, Resource.Drawable.LevelUpOneBackground},
    {10, Resource.Drawable.LevelUpTwoBackground},
    {15, Resource.Drawable.LevelUpThreeBackground},
};

...

private void SetBackgrounds() {
  gameAreaCanvas.SetBackgroundResource(s_Backgrounds[level]); 
}

编辑:level 相同的想法;唯一的区别是您有 三个 值对应于每个键。最简单的解决方案是将这些值组织成一个Tuple(不过,自定义类会是更好的选择):

// I've used Tuple<string, int, int> to store three values
// you may want to change it to a custom class  
private static Dictionary<int, Tuple<string, int, int>> s_Levels = 
  new Dictionary<int, Tuple<string, int, int>>() {
    {1, new Tuple<string, int, int>("LEVEL 1", 2000, Level1)},
    {2, new Tuple<string, int, int>("LEVEL 2", 2000, Level2)},
};

...

levelDisplay.Text = s_Levels[level].Item1;
timer = new Timer();
timer.Interval = s_Levels[level].Item2; /// DIFFERENT
timer.Enabled = true;
timer.Elapsed += s_Levels[level].Item3; /// DIFFERENT
imer.Start();

【讨论】:

  • 这是一个有趣的解决方案!编辑:你对关卡有什么建议吗,或者 switch case 是唯一的其他选择吗?
  • @Eli:在Tuple&lt;&gt; 的帮助下(自定义类将是更好的解决方案,但是)您可以为多个(在您的情况下为三个)值创建字典。查看我的编辑。
  • 我喜欢第一个解决方案,但你对 Tuple 有点迷失了......也许我需要更多地研究它。
  • @Eli: Tuple 是组合多个值的最简单(机械)方法;但是它通常看起来很繁琐,这就是我建议自定义类的原因。
【解决方案3】:

它们被称为 switch 语句,它们看起来像这样:

switch(level)
{
  case 1:
    doLevel1();
    break;
  case 2:
    doLevel2();
    break;
}

更多信息可以在这里找到:Switch Statement Tutorial

【讨论】:

  • 这是我一直在研究的解决方案,但代码量似乎并没有减少。我有时最多有 25 个关卡,只有写 25 个 switch case 的解决方案吗?
  • 在您的情况下,最好的解决方案是 switch 语句,即使您必须编写 25 个 switch 案例。
  • @fn5341 我认为在这种情况下,最好的解决方案是使用 Map(C# 中的字典)。没有 switch 语句,干净而强大。
【解决方案4】:

我会让你的“关卡”更加面向对象:

public LevelObject
{
    public BackgroundResource BackgroundResource { get; set; }
    public string Text { get; set; }
    public double TimeInterval { get; set; }
    public double ElapsedInterval { get; set; }

    // constructors, methods, etc...
}

这样,您可以初始化“级别”集合,例如List&lt;LevelObject&gt;,然后根据您当前的level,根据需要设置任何属性:

int level = // current level
List<LevelObject> levelObjectList =
    new List<LevelObject>
    {
        new LevelObject("LEVEL 1", 2000, Level1),
        new LevelObject("LEVEL 2", 2000, Level2),
        // etc...
    }
LevelObject levelObject = levelObjectList[level];

例子:

private void SetBackgrounds(LevelObject levelObject)
{
    if (levelObject.BackgroundResource != null)
    {
        gameAreaCanvas.SetBackgroundResource(levelObject.BackgroundResource);
    }
}

levelDisplay.Text = levelObject.Text;
timer = new Timer();
timer.Interval = levelObject.TimeInterval;
timer.Enabled = true;
timer.Elapsed += levelObject.ElapsedInterval;
timer.Start();

【讨论】:

    【解决方案5】:

    我会走这条路:

    首先,你需要一种枚举

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    
    namespace ConsoleApp1
    {
       public class MyEnumeration
       {
           #region Private Fields
    
           private readonly string _displayName;
           private readonly int _value;
           private readonly int _interval;
           private readonly Action _action;
    
           #endregion Private Fields
    
           #region Protected Constructors
    
           protected MyEnumeration()
           {
           }
    
           protected MyEnumeration(int value, string displayName, int interval, Action action)
           {
               _value = value;
               _displayName = displayName;
               _interval = interval;
               _action = action;
           }
    
           #endregion Protected Constructors
    
           #region Public Properties
    
           public string DisplayName
           {
               get { return _displayName; }
           }
    
           public int Value
           {
               get { return _value; }
           }
    
           public int Interval
           {
               get { return _interval; }
           }
    
           public Action Action
           {
               get { return _action; }
           }
    
           #endregion Public Properties
    
           #region Public Methods
    
           public static int AbsoluteDifference(MyEnumeration firstValue, MyEnumeration secondValue)
           {
               var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value);
               return absoluteDifference;
           }
    
           public static T FromDisplayName<T>(string displayName) where T : MyEnumeration, new()
           {
               var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName);
               return matchingItem;
           }
    
           public static T FromValue<T>(int value) where T : MyEnumeration, new()
           {
               var matchingItem = parse<T, int>(value, "value", item => item.Value == value);
               return matchingItem;
           }
    
           public static IEnumerable<T> GetAll<T>() where T : MyEnumeration, new()
           {
               var type = typeof(T);
               var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
    
               foreach (var info in fields)
               {
                   var instance = new T();
                   var locatedValue = info.GetValue(instance) as T;
    
                   if (locatedValue != null)
                   {
                       yield return locatedValue;
                   }
               }
           }
    
           public int CompareTo(object other)
           {
               return Value.CompareTo(((MyEnumeration)other).Value);
           }
    
           public override bool Equals(object obj)
           {
               var otherValue = obj as MyEnumeration;
    
               if (otherValue == null)
               {
                   return false;
               }
    
               var typeMatches = GetType().Equals(obj.GetType());
               var valueMatches = _value.Equals(otherValue.Value);
    
               return typeMatches && valueMatches;
           }
    
           public override int GetHashCode()
           {
               return _value.GetHashCode();
           }
    
           public override string ToString()
           {
               return DisplayName;
           }
    
           #endregion Public Methods
    
           #region Private Methods
    
           private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : MyEnumeration, new()
           {
               var matchingItem = GetAll<T>().FirstOrDefault(predicate);
    
               if (matchingItem == null)
               {
                   var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
                   throw new ApplicationException(message);
               }
    
               return matchingItem;
           }
    
           #endregion Private Methods
       }
    

    }

    然后您可以创建自己的枚举,例如:

    using System;
    
    namespace ConsoleApp1
    {
        internal class LevelEnum : MyEnumeration
        {
            public static readonly LevelEnum Level1 = new LevelEnum(1, "Level 1", 2000, Program.Level1);
            public static readonly LevelEnum Level2 = new LevelEnum(2, "Level 2", 3000, Program.Level2);
            public static readonly LevelEnum Level3 = new LevelEnum(3, "Level 3", 4000, Program.Level3);
            public static readonly LevelEnum Level4 = new LevelEnum(4, "Level 4", 5000, Program.Level4);
            public static readonly LevelEnum Level5 = new LevelEnum(5, "Level 5", 6000, Program.Level5);
            public static readonly LevelEnum Level6 = new LevelEnum(6, "Level 6", 7000, Program.Level6);
            public static readonly LevelEnum Level7 = new LevelEnum(7, "Level 7", 8000, Program.Level7);
            public static readonly LevelEnum Level8 = new LevelEnum(8, "Level 8", 9000, Program.Level8);
    
            public LevelEnum()
            {
            }
    
            protected LevelEnum(int value, string displayName, int interval, Action action) : base(value, displayName, interval, action)
            {
            }
        }
    }
    

    你可以像这样使用它:

    private static void Main(string[] args)
    {
        int level = 5;
        LevelEnum levelEnum = MyEnumeration.FromValue<LevelEnum>(level);
    
        levelDisplay.Text = levelEnum.DisplayName;
        timer = new Timer();
        timer.Interval = levelEnum.Interval;
        timer.Enabled = true;
        timer.Elapsed += levelEnum.Action;
        timer.Start();
    }
    
    internal static void Level1()
    {
        // Action for Level 1
    }
    
    internal static void Level2()
    {
        // Action for Level 2
    }
    
    internal static void Level3()
    {
        // Action for Level 3
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-28
      • 2018-10-25
      • 2013-03-27
      • 2012-12-13
      • 2023-02-06
      • 2017-09-03
      • 1970-01-01
      相关资源
      最近更新 更多