通常,我发现我必须实例化一堆对象,但我发现更容易将此实例化的参数作为人类可读的文本文件提供,我手动编写并作为输入提供给程序。
例如,如果对象是Car
,那么该文件可能是一堆行,每行包含用制表符分隔的名称,速度和颜色(三个必需的构造函数参数):
My car 65 Red
Arthur's car 132 Pink
Old junk car 23 Rust brown
我很容易通过其他程序直观地检查,修改或生成。然后,程序可以加载文件,获取每一行,解析相关参数,将它们提供给Car(string name, int speed, uint color)
构造函数并创建对象。
注意在输入与构造函数兼容之前,必须对输入进行一些工作:必须通过调用string
将速度从int
转换为int.Parse
。通过查找英文颜色名称,颜色必须与RGB值匹配(可能程序会访问维基百科来计算每种颜色的值,或者参考预定义的地图名称 - > RGB某处)。
我的问题是,从OOP的角度来看,谁应该进行这种解析?构造函数或调用构造函数的方法?
第一种选择,优点是简单。调用函数必须只执行:
foreach(var row in input_file)
list_of_objects_that_i_am_populating.Add(new Car(row));
所有丑陋的解析都可以很好地包含在构造函数中,无论如何它都没有太多其他代码,因此解析代码可以很容易地被读取和修改,而不会被非解析代码分散注意力。
缺点是代码重用超出了窗口,因为现在我的对象在输入格式上加入(更糟糕的是,因为输入格式是临时的并且是手动编写的,所以它是短暂的并且可能无法保证保留相同)。如果我在另一个程序中重用该对象,我决定稍微改变输入文件的格式是方便的,那么对象定义的两个版本现在是不同的。我经常发现自己在构造函数的注释部分定义了输入格式,这看起来有点像代码。
另一个缺点是我失去了批处理操作的能力。回想一下将颜色名称映射到值的早期示例问题:如果我使用需要1分钟处理每个单独请求的Web服务,无论该请求是要求转换一个颜色名称还是一百万个,该怎么办?对于一个非常大的输入文件,我会通过每行访问一次服务来大幅减慢我的应用程序,而不是为所有行提交一个大请求,然后根据回复实例化对象。
处理这种情况的“正确”方法是什么?我应该解析输入构造函数并将上述问题视为必须根据具体情况处理的特殊问题吗?我是否应该让我的调用方法进行解析(尽管它可能已经因为复杂的程序逻辑而膨胀)?
答案 0 :(得分:11)
我的问题是,从OOP的角度来看,谁应该进行这种解析?构造函数或调用构造函数的方法?
通常,您应该避免在构造函数中执行此操作。这将违反Single Responsibility Principle。每种类型应该只负责该类型中所需的操作,而不是其他任何类型。
理想情况下,一个单独的类负责将数据解析为正确的形式(没有别的)。创建实例的方法将获取(解析的)数据并创建类型。
答案 1 :(得分:8)
我会创建并使用工厂方法通过设置/文件,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) { ... }
}
答案 2 :(得分: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
}
所以我会有一个类将文件解析为其标记(作为字符串)并进行基本的错误处理(比如检查文件是否存在以及记录计数是您期望的等等)。然后在汽车构造函数中进行更具体的输入验证,因为它也适用于其他输入源(比如用户在cmd行输入他们的输入,你仍然可以有效地使用该构造函数)。
答案 3 :(得分:1)
通常,避免将代码放在可能抛出异常的构造函数中,或者只是无法构造正确形成的对象。正如您在问题中所述,您当前的实现已将对象与文件格式紧密耦合,通常可以更好地委托给类或工厂方法。