如何重构嵌套的 if else 语句

use*_*101 3 c# design-patterns if-statement nested-if data-structures

假设您有一个怪物对象的下表:

性别 年龄 类型 结果
男性 年轻的 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...
Run Code Online (Sandbox Code Playgroud)

我似乎无法全神贯注于使其可扩展,例如:说我想在将来添加一列,或者想在另一件事之前验证某件事。

是否有任何简单的设计模式或类似的东西我可以实现来简化它并使其可扩展?

Oli*_*bes 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; }
}
Run Code Online (Sandbox Code Playgroud)

使用元组模式:

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
};
Run Code Online (Sandbox Code Playgroud)

使用属性模式:

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
};
Run Code Online (Sandbox Code Playgroud)

通过将此Deconstruct方法添加到Monster类中...

public void Deconstruct(out Gender gender, out Age age, out MonsterType type)
{
    gender = Gender;
    age = Age;
    type = Type;
}
Run Code Online (Sandbox Code Playgroud)

...我们可以使用位置模式(看起来像元组模式;但是,我们可以直接打开,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
};
Run Code Online (Sandbox Code Playgroud)

如果我们将怪物声明为记录,我们就可以免费获得这个 Deconstruct 方法:

record Monster (Gender Gender, Age Age,  MonsterType Type);
Run Code Online (Sandbox Code Playgroud)

一种更加面向对象的方法是从不同怪物类型的抽象基类派生不同的怪物类,而不是拥有属性Type。怪物类别将对它们自己的值负责Result(无论这代表什么。我将保留名称Result)。

然后您只需打开GenderAge

抽象基类(enum 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; }
}
Run Code Online (Sandbox Code Playgroud)

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
    };
}
Run Code Online (Sandbox Code Playgroud)

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