【问题标题】:Should a constructor parse input?构造函数应该解析输入吗?
【发布时间】:2014-01-07 19:48:52
【问题描述】:

通常,我发现我必须实例化一堆对象,但我发现将此实例化的参数作为人类可读的文本文件提供更容易,我手动编写该文件并将其作为输入输入到程序中。

例如,如果对象是Car,那么文件可能是一堆行,每行包含名称、速度和颜色(三个必需的构造函数参数),并用制表符分隔:

My car          65       Red
Arthur's car    132      Pink
Old junk car    23       Rust brown

这对我来说很容易目视检查、修改或由另一个程序生成。然后程序可以加载文件,获取每一行,解析出相关参数,将它们输入Car(string name, int speed, uint color) 构造函数并创建对象。

注意在输入与构造函数兼容之前必须对输入进行一些工作:必须通过调用int.Parse 将速度从string 转换为int。颜色必须通过查找英文颜色名称来匹配 RGB 值(也许程序会访问 Wikipedia 以找出每种颜色的值,或者在某处查阅名称的预定义映射 -> RGB)。

我的问题是,从 OOP 的角度来看,谁应该做这个解析?构造函数,还是调用构造函数的方法?

第一个选项的优点是简单。调用函数必须只做:

foreach(var row in input_file)
     list_of_objects_that_i_am_populating.Add(new Car(row));

并且所有难看的解析都可以很好地包含在构造函数中,反正没有太多其他代码,所以解析代码可以很容易地阅读和修改,而不会被非解析代码分心。

缺点是代码重用已经过时了,因为现在我的对象在臀部连接到输入格式(更糟糕的是,因为输入格式是临时和手动组合的,它是短暂的并且可能无法保证保留相同)。如果我在另一个程序中重用这个对象,我决定稍微改变输入文件的格式很方便,那么两个版本的对象定义现在是不同的。我经常发现自己在构造函数的注释部分定义输入格式,这似乎有点代码异味。

另一个缺点是我失去了进行批处理操作的能力。回想一下前面将颜色名称映射到值的示例问题:如果我使用的 Web 服务需要 1 分钟来处理每个单独的请求,而不管该请求是要求转换一个颜色名称还是一百万个颜色名称。对于一个非常大的输入文件,我会通过为每一行访问一次服务来大大减慢我的应用程序,而不是为所有行提交一个大请求,然后根据回复实例化对象。

处理这种情况的“正确”方法是什么?我是否应该解析输入构造函数并将上述问题视为必须逐案处理的异常问题?我是否应该让我的调用方法进行解析(即使它可能已经被很多复杂的程序逻辑所淹没)?

【问题讨论】:

  • 我会使用一个单独的类来解析数据并返回一个Car 对象。通常这是通过工厂模式完成的(有 3 个,选择一个具有足够抽象来满足您的需求)。构造函数用于初始化字段,而不是执行逻辑。

标签: c# oop factory


【解决方案1】:

我的问题是,从 OOP 的角度来看,谁应该做这个解析?构造函数,还是调用构造函数的方法?

一般来说,您应该避免在构造函数中这样做。这将违反Single Responsibility Principle。每种类型都应该只负责该类型中所需的操作,而不是其他任何事情。

理想情况下,一个单独的类将负责将数据解析为正确的形式(仅此而已)。创建您的实例的方法将获取(解析的)数据并创建您的类型。

【讨论】:

    【解决方案2】:

    我将创建并使用工厂方法通过设置/文件、csv 加载,而不是将此类代码放入构造函数本身。

    工厂版本 1:

    public class Car
    {
         ... your existing methods and data ...
    
    
         public static Car CreateFromCsv(string csv ) { .... }
         public static Car CreateFromFile(string fileName) { ...}
     }
    

    或使用专用工厂:

    public static class CarFactory
    {
       public static Car CreateFromCsv(string csv ) { .... }
       public static Car CreateFromFile(string fileName) { ...}
    }
    

    或者专门的业务逻辑类:

    namespace BusinessLogic;
    
    public class LoadCars
    {
    
        public Car ExecuteForCsv(string csv) { ...}
        public Car ExecuteForFile(string fileName) { ... }
    }
    

    【讨论】:

    • 谢谢,我选择了这个答案,因为会问这个问题的人可能(像我一样)不是很有知识,并且可能(像我一样)会从简单的,to-the-中受益匪浅说明要点的例子。就我而言,我并没有完全“理解”工厂应该如何运作——我现在感觉是这样。
    • 我经常将 #1 FromString 命名为 ToString 的补充。
    【解决方案3】:

    我认为将 FileParser 与 Car 类分开通常是更好的做法。我会亲自解析文件并返回 List<string[]> 或类似的东西,然后重载 Car 构造函数,如下所示:

    Car(string[] values)
    {
        // do error handling here like
        if (values.Length != 2)
            // error
        if (int.TryParse(values[1], out tempVar))
            // set int param, if not then throw error      
    }
    

    所以我会有一个类将文件解析为其标记(作为字符串)并进行基本的错误处理(例如检查文件是否存在以及记录数是否符合您的预期等)。然后在 car 构造函数中进行更具体的输入验证,因为这也适用于其他输入源(比如用户在 cmd 行输入他们的输入,您仍然可以有效地使用该构造函数)。

    【讨论】:

      【解决方案4】:

      一般来说,避免将代码放入可能引发异常或无法构造正确格式对象的构造函数中。正如您在问题中指出的那样,您当前的实现已将您的对象与文件格式紧密耦合,该文件格式通常更好地委托给类或工厂方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-08
        • 1970-01-01
        • 2020-09-25
        • 2015-05-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多