重构大开关案例以设置属性

时间:2016-09-27 03:15:22

标签: c# switch-statement refactoring

你好,大家好,这是我的第一个问题,我希望它是好的\ o /

所以,首先是c#应用程序通过套接字继续接收数据,第一次连接到服务器并且我发出请求我收到这个大字符串(超过100个字段),为了我将把它缩小的例子:

0:7:1:Augusto:2:Question:3:Stackoverflow:4:Question:5:201609262219:6:stuff:7:stuff:..:100:!

之后我收到这个大字符串,我只会收到更新,例如:

0:7:3:Changed Topic:4:Doubt:5:2016092710:100:!

字段100总是会出现,因为它定义了字符串终止符 当我收到这个大字符串时,我必须构建一个对象问题,这个大字符串总是会产生一个Question对象,所以我们有:

public class Question 
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Action { get; set; }
    public string Topic { get; set; }
    public string Body { get; set; }
    public string Time { get; set; }
    ...
    public string 99 { get; set; }
}

要构建问题对象,我这样做了:

public void Process(string data) 
{
    Question question = new Question();
    String[] fields = data.split(“:”);

    for(int i = 0; i < fields.Length; i +=2) {
        Switch (fields[i]) {
            case "0":
                question.Id = fields[i+1]
                    break;

            case "1":
                question.Name = fields[i+1]
                    break;

            case "2":
                question.Action = fields[i+1]
                    break;
            case "3":
                question.Topic = fields[i+1]
                    break;

            case "4":
                question.Body = fields[i+1]
                    break;
            case "5":
                question.Time = fields[i+1]
                    break;
                ...
            case "99":
                    Question.99 = fields[i+1]
                        break;

        }   
    }
}

我研究了stackoverflow的答案,但它们与我想要实现的不同 我不认为使用策略模式或命令模式是值得的,因为逻辑很简单,使用这种模式会增加太多的复杂性。
我不确定Dictionary<string,Func<string>>是否可以解决我的问题,我认为这与女巫案件是一回事。
我也考虑过使用反射(setValue),但我认为它会很慢,我真的需要这个东西才能飞行。

这里的问题是我只是将一个值归因于该对象,我不想做这个大的切换案例。我认为我创建对象的方式是错误的,但我不能找到不同的解决方案。

还有一个问题,你们认为string.Split()会成为我代码的瓶颈吗?因为我把方法Process(数据)称为一堆时间,比如每秒100次。

谢谢!

---更新
修复了样本和案例“5”。

2 个答案:

答案 0 :(得分:2)

我认为你担心这种情况下的过早优化。一般的经验法则是 - 除非性能问题实际上是可测量的,否则不要进行优化。

考虑到这一点,这个问题可以通过一个相当优雅的庄园来解决。我将使用一些缓存反射,这样我只需提取PropertyInfo及其自定义属性一次。因此,您将看到初始连接的性能开销很小。在这一点上,这非常快。

  

因为我把方法Process(数据)称为一堆时间,比如每秒100次。

我在拥有4个内核和8GB内存的Windows 10虚拟机中运行了这个。我在一个包含100个值的字符串上运行了10,000次迭代测试(没有使用缩短的字符串),我看到每串516.32 ticks 的平均解析时间(每1ms 10,000个滴答)。考虑到您的要求是1秒内100个字符串,这应该足够快,以满足您的吞吐量。我能够在1秒的时间内解析超过20,000点。每秒解析的次数最终将高于20,000 I进程,因为我每次都使用整个100个元素字符串而不是更小的更新字符串。

长期运行可能会有一些GC压力,我必须运行测试才能看到。这可以通过引入几个对象池来减少分配来优化。无论采用何种方法,您都会遇到此问题,因为您必须将1个字符串解析为多个值,因此此解决方案不会尝试解决此问题。

让我们从我的字符串开始,其中包含100个元素。

0:FizBam Foo Bar:1:FizBam Foo Bar:2:FizBam Foo Bar:3:FizBam Foo Bar:4:FizBam Foo Bar:5:FizBam Foo Bar:6:FizBam Foo Bar:7:FizBam Foo Bar:8:FizBam Foo Bar:9:FizBam Foo Bar:10:FizBam Foo Bar:11:FizBam Foo Bar:12:FizBam Foo Bar:13:FizBam Foo Bar:14:FizBam Foo Bar:15:FizBam Foo Bar:16:FizBam Foo Bar:17:FizBam Foo Bar:18:FizBam Foo Bar:19:FizBam Foo Bar:20:FizBam Foo Bar:21:FizBam Foo Bar:22:FizBam Foo Bar:23:FizBam Foo Bar:24:FizBam Foo Bar:25:FizBam Foo Bar:26:FizBam Foo Bar:27:FizBam Foo Bar:28:FizBam Foo Bar:29:FizBam Foo Bar:30:FizBam Foo Bar:31:FizBam Foo Bar:32:FizBam Foo Bar:33:FizBam Foo Bar:34:FizBam Foo Bar:35:FizBam Foo Bar:36:FizBam Foo Bar:37:FizBam Foo Bar:38:FizBam Foo Bar:39:FizBam Foo Bar:40:FizBam Foo Bar:41:FizBam Foo Bar:42:FizBam Foo Bar:43:FizBam Foo Bar:44:FizBam Foo Bar:45:FizBam Foo Bar:46:FizBam Foo Bar:47:FizBam Foo Bar:48:FizBam Foo Bar:49:FizBam Foo Bar:50:FizBam Foo Bar:51:FizBam Foo Bar:52:FizBam Foo Bar:53:FizBam Foo Bar:54:FizBam Foo Bar:55:FizBam Foo Bar:56:FizBam Foo Bar:57:FizBam Foo Bar:58:FizBam Foo Bar:59:FizBam Foo Bar:60:FizBam Foo Bar:61:FizBam Foo Bar:62:FizBam Foo Bar:63:FizBam Foo Bar:64:FizBam Foo Bar:65:FizBam Foo Bar:66:FizBam Foo Bar:67:FizBam Foo Bar:68:FizBam Foo Bar:69:FizBam Foo Bar:70:FizBam Foo Bar:71:FizBam Foo Bar:72:FizBam Foo Bar:73:FizBam Foo Bar:74:FizBam Foo Bar:75:FizBam Foo Bar:76:FizBam Foo Bar:77:FizBam Foo Bar:78:FizBam Foo Bar:79:FizBam Foo Bar:80:FizBam Foo Bar:81:FizBam Foo Bar:82:FizBam Foo Bar:83:FizBam Foo Bar:84:FizBam Foo Bar:85:FizBam Foo Bar:86:FizBam Foo Bar:87:FizBam Foo Bar:88:FizBam Foo Bar:89:FizBam Foo Bar:90:FizBam Foo Bar:91:FizBam Foo Bar:92:FizBam Foo Bar:93:FizBam Foo Bar:94:FizBam Foo Bar:95:FizBam Foo Bar:96:FizBam Foo Bar:97:FizBam Foo Bar:98:FizBam Foo Bar:99:FizBam Foo Bar:100:!

然后我创建了一个Attribute,我可以用它将元素映射到模型属性。

public class MapAttribute : Attribute
{
    public MapAttribute(string fieldKey)
    {
        this.Field = fieldKey;
    }

    public string Field { get; private set; }

    public PropertyInfo Property { get; set; }

    public void SetValue(Question question, string value)
    {
        this.Property.SetValue(question, value);
    }
}

现在,在您的模型上,您可以将属性映射到传入字段。当一个字段进入时,你将它与一个问题的实例一起传递给属性,并让它分配值。您也可以在某种查找表中处理此问题,但属性似乎是允许您向模型添加新属性并在单个位置更新映射的最简单方法。

我在这里展示整个模型,其中100个属性映射到服务器响应。

public class Question
{
    [Map("0")]
    public string FooBar { get; set; }

    [Map("1")]
    public string Id { get; set; }

    [Map("2")]
    public string Action { get; set; }

    [Map("3")]
    public string Topic { get; set; }

    [Map("4")]
    public string Body { get; set; }

    [Map("5")]
    public string Time { get; set; }

    [Map("6")]
    public string Query { get; set; }

    [Map("7")]
    public string Answer { get; set; }

    [Map("8")]
    public string __8 { get; set; }

    [Map("9")]
    public string __9 { get; set; }

    [Map("10")]
    public string __10 { get; set; }

    [Map("11")]
    public string __11 { get; set; }

    [Map("12")]
    public string __12 { get; set; }

    [Map("13")]
    public string __13 { get; set; }

    [Map("14")]
    public string __14 { get; set; }

    [Map("15")]
    public string __15 { get; set; }

    [Map("16")]
    public string __16 { get; set; }

    [Map("17")]
    public string __17 { get; set; }

    [Map("18")]
    public string __18 { get; set; }

    [Map("19")]
    public string __19 { get; set; }

    [Map("20")]
    public string __20 { get; set; }

    [Map("21")]
    public string __21 { get; set; }

    [Map("22")]
    public string __22 { get; set; }

    [Map("23")]
    public string __23 { get; set; }

    [Map("24")]
    public string __24 { get; set; }

    [Map("25")]
    public string __25 { get; set; }

    [Map("26")]
    public string __26 { get; set; }

    [Map("27")]
    public string __27 { get; set; }

    [Map("28")]
    public string __28 { get; set; }

    [Map("29")]
    public string __29 { get; set; }

    [Map("30")]
    public string __30 { get; set; }

    [Map("31")]
    public string __31 { get; set; }

    [Map("32")]
    public string __32 { get; set; }

    [Map("33")]
    public string __33 { get; set; }

    [Map("34")]
    public string __34 { get; set; }

    [Map("35")]
    public string __35 { get; set; }

    [Map("36")]
    public string __36 { get; set; }

    [Map("37")]
    public string __37 { get; set; }

    [Map("38")]
    public string __38 { get; set; }

    [Map("39")]
    public string __39 { get; set; }

    [Map("40")]
    public string __40 { get; set; }

    [Map("41")]
    public string __41 { get; set; }

    [Map("42")]
    public string __42 { get; set; }

    [Map("43")]
    public string __43 { get; set; }

    [Map("44")]
    public string __44 { get; set; }

    [Map("45")]
    public string __45 { get; set; }

    [Map("46")]
    public string __46 { get; set; }

    [Map("47")]
    public string __47 { get; set; }

    [Map("48")]
    public string __48 { get; set; }

    [Map("49")]
    public string __49 { get; set; }

    [Map("50")]
    public string __50 { get; set; }

    [Map("51")]
    public string __51 { get; set; }

    [Map("52")]
    public string __52 { get; set; }

    [Map("53")]
    public string __53 { get; set; }

    [Map("54")]
    public string __54 { get; set; }

    [Map("55")]
    public string __55 { get; set; }

    [Map("56")]
    public string __56 { get; set; }

    [Map("57")]
    public string __57 { get; set; }

    [Map("58")]
    public string __58 { get; set; }

    [Map("59")]
    public string __59 { get; set; }

    [Map("60")]
    public string __60 { get; set; }

    [Map("61")]
    public string __61 { get; set; }

    [Map("62")]
    public string __62 { get; set; }

    [Map("63")]
    public string __63 { get; set; }

    [Map("64")]
    public string __64 { get; set; }

    [Map("65")]
    public string __65 { get; set; }

    [Map("66")]
    public string __66 { get; set; }

    [Map("67")]
    public string __67 { get; set; }

    [Map("68")]
    public string __68 { get; set; }

    [Map("69")]
    public string __69 { get; set; }

    [Map("70")]
    public string __70 { get; set; }

    [Map("71")]
    public string __71 { get; set; }

    [Map("72")]
    public string __72 { get; set; }

    [Map("73")]
    public string __73 { get; set; }

    [Map("74")]
    public string __74 { get; set; }

    [Map("75")]
    public string __75 { get; set; }

    [Map("76")]
    public string __76 { get; set; }

    [Map("77")]
    public string __77 { get; set; }

    [Map("78")]
    public string __78 { get; set; }

    [Map("79")]
    public string __79 { get; set; }

    [Map("80")]
    public string __80 { get; set; }

    [Map("81")]
    public string __81 { get; set; }

    [Map("82")]
    public string __82 { get; set; }

    [Map("83")]
    public string __83 { get; set; }

    [Map("84")]
    public string __84 { get; set; }

    [Map("85")]
    public string __85 { get; set; }

    [Map("86")]
    public string __86 { get; set; }

    [Map("87")]
    public string __87 { get; set; }

    [Map("88")]
    public string __88 { get; set; }

    [Map("89")]
    public string __89 { get; set; }

    [Map("90")]
    public string __90 { get; set; }

    [Map("91")]
    public string __91 { get; set; }

    [Map("92")]
    public string __92 { get; set; }

    [Map("93")]
    public string __93 { get; set; }

    [Map("94")]
    public string __94 { get; set; }

    [Map("95")]
    public string __95 { get; set; }

    [Map("96")]
    public string __96 { get; set; }

    [Map("97")]
    public string __97 { get; set; }

    [Map("98")]
    public string __98 { get; set; }

    [Map("99")]
    public string __99 { get; set; }

    [Map("100")]
    public string __100 { get; set; }
}

现在,当建立第一个连接时,抓取所有属性及其属性并将它们缓存在字典中。

Dictionary<string, MapAttribute> properties = typeof(Question).GetProperties().ToDictionary(
    property => property.GetCustomAttribute<MapAttribute>().Field,
    property =>
    {
        var attribute = property.GetCustomAttribute<MapAttribute>();
        attribute.Property = property;
        return attribute;
    });

在应用程序的生命周期中只执行一次。您将从服务器获得的每个响应中重复使用该字典。现在,当您从服务器收到更新时,只需使用属性解析它。

void Parse(string message, Dictionary<string, MapAttribute> fieldMapping)
{
    string[] messageContent = message.Split(':');
    var question = new Question();
    for (int index = 0; index < messageContent.Length; index++)
    {
        string field = messageContent[index];
        MapAttribute mapping = fieldMapping[field];
        index++;
        mapping.SetValue(question, messageContent[index]);
    }
}

这里没有任何真正的幻想。我们刚刚用你的switch语句替换了一个字典查找和一个属性来处理映射响应键,你的模型属性。大大减少了您必须编写的最终代码,并允许您在将来使用新属性更新模型。简单易维护。你可以通过不担心性能来实现这一点,直到性能实际上成为一个可衡量的问题。

答案 1 :(得分:0)

您可以像这样重构代码:

public class Question
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Action { get; set; }
    public string Topic { get; set; }
    public string Body { get; set; }
    public string Time { get; set; }
    public string _99 { get; set; }

    private static Dictionary<string, Action<Question, string>> __assignments = new Dictionary<string, Action<Question, string>>()
    {
        { "0", (q, t) => q.Id = t },
        { "1", (q, t) => q.Name = t },
        // ...
        { "99",  (q, t) => q._99 = t },
    };

    public void SetProperty(string key, string value)
    {
        __assignments[key](this, value);
    }
}

public void Process(string data)
{
    Question question = new Question();
    string[] fields = data.Split(':');

    for (int i = 0; i < fields.Length; i += 2)
    {
        question.SetProperty(fields[i], fields[i + 1]);
    }
}

这消除了开关,允许您在运行时重构字典。

我建议也许这是一种更好的方法:

__assignments

而且,作为一种轻微的替代方案,您可以通过这种方式定义 private static Dictionary<string, Action<Question, string>> __assignments = new Action<UserQuery.Question, string>[] { (q, t) => q.Id = t, (q, t) => q.Name = t, // ... (q, t) => q._99 = t, } .Select((x, n) => new { x, n }) .ToDictionary(xn => xn.n.ToString(), xn => xn.x); 以使其更清洁:

   #import the "random" library
import random
#Welcome the user and explain the program
print("Welcome to the Rock Paper Scissor game! You will play until you or the computer gets 5 wins!")


#Set constants for rock paper and scissors for the computer's choice
ROCK = 1
PAPER = 2
SCISSOR = 3


#Define the scores for the user and computer and start them as 0
user_score = 0
cpu_score = 0

#Start the loop that runs until the user or computer reaches the score of 5
while user_score != 5  or cpu_score != 5:
    #Gets the choice of the user and stores it in a variable
    choice = input("Please enter your choice- 'r'ock, 'p'aper, or 's'cissors? \n")
    #Prevents the loop from progressing until the user picks a valid command
    while choice != "r" and choice != "p" and choice != "s":
        choice = input("Invalid command: Please enter your choice- 'r'ock, 'p'aper, or 's'cissors? \n")
    #get's a random pick between 1 and 3 for the cpu's choice
    cpu_pick = random.randint(ROCK,SCISSOR)

    #Prints the pick of the user prior to determining the winner so this does not have to be included in every if statement
    if choice == "r":
        print("You pick rock!")
    elif choice == "s":
        print("You pick scissors!")
    elif choice == "p":
        print("You pick paper!")

    #Prints the pick of the cpu prior to determining the winner so this does not have to be included in every if statement
    if cpu_pick == ROCK:
        print("The cpu picks a rock!\n")
    elif cpu_pick == SCISSOR:
        print("The cpu picks scissors!\n")
    elif cpu_pick == PAPER:
        print("The cpu picks paper!\n")

    #Accounts for all cases when the cpu pick is rock and adds to the scores when somebody wins
    if cpu_pick == ROCK and choice == "r":
        print("Tie! New round!")
    elif cpu_pick == ROCK and choice == "s":
        print("CPU wins this round!")
        cpu_score += 1
    elif cpu_pick == ROCK and choice == "p":
        print("You win this round!")
        user_score += 1

    #Accounts for all cases when the cpu pick is Scissors and adds to the scores when somebody wins
    if cpu_pick == SCISSOR and choice == "s":
        print("Tie! New round!")
    elif cpu_pick == SCISSOR and choice == "p":
        print("CPU wins this round!")
        cpu_score += 1
    elif cpu_pick == SCISSOR and choice == "r":
        print("You win this round!")
        user_score += 1

    # Accounts for all cases when the cpu pick is Paper and adds to the scores when somebody wins
    if cpu_pick == PAPER and choice == "p":
        print("Tie! New round!")
    elif cpu_pick == PAPER and choice == "r":
        print("CPU wins this round!")
        cpu_score += 1
    elif cpu_pick == PAPER and choice == "s":
        print("You win this round!")
        user_score += 1

    #Prints the score after each round
    print("Score: \nComputer: %d\nYou: %d" % (cpu_score, user_score))

#when the loop is broken check who won then print the final score and winner
if user_score == 5:
    print("You win by a score of %d to %d!" % (user_score, cpu_score))
elif user_score == 5:
    print("The CPU won bu a score of %d to %d!" % (cpu_score, user_score))