如何重构嵌套的if else语句

3
假设你有如下一个怪物对象的表格:
性别 年龄 类型 结果
年轻 1
2
6
年轻 4
5
8
其他 年轻 7
其他 10
其他 20
如果我想要输入一个怪物并获得它的结果,那么如何不使用长串的嵌套if-else语句得到此怪物对象的结果?
if(monster.Gender == Male && monster.Age == Old)
    if(monster.Type == Ice)
        return 2
    if(monster.Type == Wood)
        return 6
if(monster.Gender == Male && monster.Age == Young)
etc...

我似乎无法理解如何使其可扩展等问题:比如说,如果我未来想要添加一列或者在某个操作之前验证某个事情,该怎么办。

有没有什么简单的设计模式或类似的东西可以实现简化和扩展性?

2个回答

8
你可以使用C# 8.0中引入的switch表达式模式匹配一起使用。
假设有以下声明:
public enum Gender
{
    Other,
    Female,
    Male
}

public enum Age
{
    Young,
    Old
}

public enum MonsterType
{
    Fire,
    Ice,
    Wood
}

class Monster
{
    public Gender Gender { get; set; }
    public Age Age { get; set; }
    public MonsterType Type { get; set; }
}

使用元组模式:
return (monster.Gender, monster.Age, monster.Type) switch {
    (Gender.Male, Age.Old, MonsterType.Ice) => 2,
    (Gender.Male, Age.Old, MonsterType.Wood) => 6,
    (Gender.Male, Age.Young, MonsterType.Ice) => 12,
    ... etc.
    _ => -1
};

使用属性模式:
return monster switch {
    { Gender: Gender.Male, Age: Age.Old, Type: MonsterType.Ice } => 2,
    { Gender: Gender.Male, Age: Age.Old, Type: MonsterType.Wood } => 6,
    { Gender: Gender.Male, Age: Age.Young, Type: MonsterType.Ice } => 12,
    ... etc.
    _ => -1
};

通过向Monster类添加Deconstruct方法,...
public void Deconstruct(out Gender gender, out Age age, out MonsterType type)
{
    gender = Gender;
    age = Age;
    type = Type;
}

...我们可以使用位置模式(看起来像元组模式;但是,我们可以直接在monster上进行切换,而不必先创建元组):

return monster switch {
    (Gender.Male, Age.Old, MonsterType.Ice) => 2,
    (Gender.Male, Age.Old, MonsterType.Wood) => 6,
    (Gender.Male, Age.Young, MonsterType.Ice) => 12,
    ... etc.
    _ => -1
};

如果我们将怪物声明为记录,那么我们就可以免费获得这个Deconstruct方法:
record Monster (Gender Gender, Age Age,  MonsterType Type);

一个更面向对象的方法是从抽象基类中派生出不同的怪物类,以代替拥有一个“Type”属性。这些怪物类将负责自己的“Result”值(无论这代表什么,我会保留名称“Result”)。
然后您只需要在“Gender”和“Age”上切换即可。
抽象基类(现在枚举“MonsterType”已过时):
abstract class Monster
{
    public Gender Gender { get; set; }
    public Age Age { get; set; }

    public void Deconstruct(out Gender gender, out Age age)
    {
        gender = Gender;
        age = Age;
    }

    public abstract int Result { get; }
}

一个针对Fire类型的实现
class FireMonster : Monster
{
    public override int Result => this switch {
        (Gender.Male, Age.Young) => 1,
        (Gender.Female, Age.Young) => 4,
        (Gender.Other, Age.Young) => 7,
        // etc.
        _ => -1
    };
}

注意:如果情况涵盖了性别和年龄的所有组合,即它们是穷尽的,则不再需要默认情况_ =>(如果这些属性属于枚举类型。如果它们是int常量,则C#编译器无法确定它们是否穷尽)。

这非常详细,并以易于理解的方式解释,这帮助我学到了很多!非常感谢! - user19911101

2
这似乎是需要使用配置文件的情况。您可以将“表格”存储在CSV文件或类似文件(如数据库表)中,并创建一个类来从该文件读取并与您正在搜索的怪物匹配。每当您添加新的怪物时,只需要将它们添加到配置文件中。
我正在使用CsvHelper来读取下面的CSV文件:
public class Monster
{
    public string Gender { get; set; }
    public string Age { get; set; }
    public string Type { get; set; }
    public int Result { get; set; }
}

public class MonsterLookup
{
    private readonly List<Monster> _lookupList;
    
    public MonsterLookup(string configFilePath)
    {
        using var reader = new StreamReader(configFilePath);
        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
        _lookupList = csv.GetRecords<Monster>().ToList();
    }
    
    public int GetResult(Monster monster)
    {
        var match = _lookupList.FirstOrDefault(
            m => m.Gender == monster.Gender
                && m.Age == monster.Age
                && m.Type == monster.Type);
        return match?.Result ?? -1;
    }
}

// Call it like:
var monsterLookup = new MonsterLookup(configFilePath);
var result = monsterLookup.GetResult(myMonster);

配置文件(您可以添加所有类型):
Gender,Age,Type,Result
Male,Young,Fire,1
Male,Old,Ice,2
Female,Young,Fire,4

以下是示例,点击此处查看。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接