具有超过100个属性的类的设计模式

时间:2009-10-27 19:42:09

标签: c# design-patterns oop class-design

您在设计具有超过100个属性的类时会提供哪些建议/建议/指导?

背景

  • 该课程描述了发票。发票可以有超过100个描述它的属性,即日期,金额,代码等......
  • 我们提交发票的系统使用100个属性中的每一个,并作为单个实体提交(而不是在不同时间提交的各个部分)。
  • 描述发票的属性是业务流程的一部分。业务流程无法更改。

建议吗

  • 在面对设计具有100个属性的类时,其他人做了什么?即,用100个属性中的每一个创建类?
  • 以某种方式分解(如果是这样,如何)?
  • 或者这在您的经历中是否相当正常?

修改 在阅读了一些很好的回答并进一步思考之后,我认为这个问题确实没有任何单一答案。然而,由于我们最终按照LBrushkin's Answer的方式对我们的设计进行建模,所以我给了他信任。虽然不是最受欢迎的答案,但LBrushkin的回答帮助我们定义了几个界面,我们在整个应用程序中聚合和重用这些界面,并推动我们调查一些可能在未来发挥作用的模式。

17 个答案:

答案 0 :(得分:33)

您可以像数据库表一样尝试“规范化”它。例如,可能将所有与地址相关的属性放在Address类中 - 然后在BillingAddress类中具有MailingAddressAddress类型的Invoice属性。这些类也可以在以后重用。

答案 1 :(得分:22)

糟糕的设计显然在您提交的系统中 - 没有发票有100多个属性无法分组到子结构中。例如,发票将拥有客户,而客户将拥有ID和地址。地址反过来将有一条街道,一个邮政编码,还有其他什么。但所有这些属性不应直接属于发票 - 发票没有客户ID或邮政编码。

如果您必须构建一个发票类,所有这些属性都直接附加到发票上,我建议为客户,地址和所有其他所需的东西制作一个包含多个类的简洁设计,然后将其包好设计的对象图与胖发票类没有存储和逻辑本身只是将所有操作传递给后面的对象图。

答案 2 :(得分:11)

我认为其中一些属性可能彼此相关。我可以想象,可能有一组属性定义了Invoice的独立方面,这些方面作为一个组合有意义。

您可能需要考虑创建模拟发票不同方面的单个界面。这可能有助于以更连贯,易于理解的方式定义在这些方面运作的方法和属性。

您还可以选择将具有特定含义的属性(地址,位置,范围等)组合到您聚合的对象中,而不是单个大型类的单个属性。

请记住,您选择对问题进行建模的抽象以及与其他系统(或业务流程)进行通信所需的抽象不必相同。实际上,应用bridge pattern以允许单独的抽象独立发展通常很有效。

答案 3 :(得分:6)

嗯......所有这些都与发票真正相关特别?通常我所看到的是:

class Customer:
.ID
.Name

class Address
.ID 
.Street1
.Street2
.City
.State
.Zip

class CustomerAddress
.CustomerID
.AddressID
.AddressDescription ("ship","bill",etc)

class Order
.ID
.CustomerID
.DatePlaced
.DateShipped
.SubTotal

class OrderDetails
.OrderID
.ItemID
.ItemName
.ItemDescription
.Quantity
.UnitPrice

将它们捆绑在一起:

class Invoice
.OrderID
.CustomerID
.DateInvoiced

打印发票时,请同时加入所有这些记录。

如果你真的必须拥有100个属性的单个类,那么使用字典可能更好

Dictionary<string,object> d = new Dictionary<string,object>();
d.Add("CustomerName","Bob");
d.Add("ShipAddress","1600 Pennsylvania Ave, Suite 0, Washington, DC 00001");
d.Add("ShipDate",DateTime.Now);
....

这里的想法是将你分成逻辑单位。在上面的示例中,每个类对应于数据库中的表。您可以将其中的每一个加载到数据访问层中的专用类中,或者在生成报表(发票)时从表中选择加入它们。

答案 4 :(得分:3)

除非您的代码在很多地方实际使用了许多属性,否则我会去找字典。

拥有真正的属性有其优点(类型安全性,可发现性/智能感知,可重构性)但如果所有代码都是从其他地方获取这些内容,在UI上显示,在Web服务中发送,保存到文件等。

答案 5 :(得分:2)

当你存储它的类/表开始违反规范化规则时,列数太多了。

根据我的经验,在正确规范化时,很难获得那么多列。将规范化规则应用于宽表/类,我认为每个实体最终会有更少的列。

答案 6 :(得分:2)

它被认为是糟糕的OO风格,但是如果你所做的只是填充一个带有属性的对象来传递它们以进行处理,并且处理只读取属性(可能是为了创建一些其他对象或数据库更新),一个简单的POD对象就是你需要的,包含所有公共成员,默认构造函数,没有其他成员方法。因此,您可以将其视为属性的容器而不是完整的对象。

答案 7 :(得分:1)

如果您要为此其他服务预先存在的100列表创建table gateway,那么列表或字典可能是非常快速的入门方式。但是,如果您从大型表单或UI向导中获取输入,则可能需要在提交到远程服务之前验证内容。

简单的DTO可能如下所示:

class Form
{
    public $stuff = array();
    function add( $key, $value ) {}
}

表网关可能更像:

class Form
{
    function findBySubmitId( $id ) {} // look up my form
    function saveRecord() {}       // save it for my session
    function toBillingInvoice() {} // export it when done
}

你可以很容易地扩展它,具体取决于你是否有发票的变化。 (为每个子类添加validate()方法可能是合适的。)

class TPSReport extends Form { 
    function validate() {}
}

如果您想将DTO与交付机制分开,因为交付机制对您的所有发票都是通用的,这可能很容易。但是,您可能处于发票成功或失败的业务逻辑的情况。这就是我大肆进入杂草的地方。但它的位置和OO模型可能是有用的...我将支付一分钱,不同的发票将有不同的发票和不同的程序,如果发票提交barfs,你将需要额外的例程: - )

class Form { 
    function submitToBilling() {}
    function reportFailedSubmit() {}
    function reportSuccessfulSubmit() {}
}
class TPSReport extends Form { 
    function validate() {}
    function reportFailedSubmit() { /* oh this goes to AR */ }
}

注意David Livelys回答:这是一个很好的见解。通常,表单上的字段都是它们自己的数据结构,并且有自己的验证规则。所以你可以很快地建模复合对象。这会将每个字段类型与其自己的验证规则相关联,并强制执行更严格的类型。

如果您需要进一步验证,通常业务规则与表单或提供它们的DTO完全不同。您还可能面临部门导向的逻辑,与表单几乎没有关系。重要的是要将其保留在表单本身和模型提交过程的单独验证之外。

如果您要在这些表单后面组织架构,而不是包含100列的表,您可能会按字段标识符和值将条目细分为几列。

table FormSubmissions (
    id         int
    formVer    int -- fk of FormVersions
    formNum    int -- group by form submission
    fieldName  int -- fk of FormFields
    fieldValue text
)
table FormFields (
    id         int
    fieldName  char
)
table FormVersions (
    id
    name
)
select s.* f.fieldName from FormSubmissions s
left join FormFields f on s.fieldName = f.id
where formNum = 12345 ;

我想说这绝对是一个你需要重新考虑周围因素才能找到舒适感的情况。希望您可以控制架构和对象模型之类的东西。 (顺便说一句......那个表已经被称为'规范化'?我看过该模式的变体,通常按数据类型组织......好吗?)

答案 8 :(得分:1)

听起来你需要生成一个包含大约100个属性的发票对象。在每种情况下你有100个这样的属性吗?也许你会想要一个工厂,一个可以在给定较小参数的情况下生成发票的类。可以为发票的相关字段相关的每个方案添加不同的工厂方法。

答案 9 :(得分:1)

你不应该纯粹出于审美考虑而受到激励。

根据您的评论,该对象基本上是遗留系统所使用的数据传输对象,该对象期望存在所有字段。

除非从部件组成这个对象有实际价值,否则通过模糊其功能和目的可以获得什么?

这些是有正当理由的:

1 - 您正在从各种系统收集此对象的信息,并且这些部分相对独立。在这种情况下根据流程考虑因素组成最终对象是有意义的。

2 - 您有其他系统可以使用此对象的各个字段的子集。这里重用是激励因素。

3 - 基于更合理的设计,非常有可能实现下一代发票系统。在这里,系统的可扩展性和发展是激励因素。

如果这些考虑因素都不适用于您的情况,那么重点是什么?

答案 10 :(得分:1)

你可能会在本文中找到一些关于Steve Yegge称为Universal Design Pattern的属性的有用想法。

答案 11 :(得分:1)

我使用了词典&lt; string,string&gt;对于这样的事情。

它附带了一大堆可以处理它的函数,很容易将字符串转换为其他结构,易于存储等。

答案 12 :(得分:0)

字典?为什么不,但不一定。我看到一个C#标签,你的语言有反射,对你有好处。我的Python代码中有一些像这样的太大的类,反射有很多帮助:

for attName in 'attr1', 'attr2', ..... (10 other attributes):
    setattr( self, attName, process_attribute( getattr( self, attName ))

当你想将10个字符串成员从某些编码转换为UNICODE时,不应该触及其他一些字符串成员,你想对其他成员应用一些数字处理...转换类型... a for loop beats copy - 随时提供大量代码以保持清洁。

答案 13 :(得分:0)

您可以尝试使用LINQ,它会为您自动生成属性。如果所有字段分布在多个表中,您可以构建视图并将视图拖到设计器上。

答案 14 :(得分:0)

您是否始终需要返回的所有属性?你可以使用任何类消费数据的投影,并且只生成你当时需要的属性。

答案 15 :(得分:0)

如果一个实体有一百个唯一属性而不是一个具有一百个属性的类是正确的事情。

有可能将地址之类的东西拆分成子类,但这是因为地址本身就是一个实体,很容易被识别出来。

教科书(即过度简化在现实世界中不可用)的发票如下: -

 class invoice:
    int id;
    address shipto_address;
    address billing_address;
    order_date date;
    ship_date date;
    .
    .
    . 
    line_item invoice_line[999];

  class line_item;
    int item_no.
    int product_id;
    amt unit_price;
    int qty;
    amt item_cost;
    .
    .
    .

所以我很惊讶你至少没有一个line_items数组。

习惯它!在商业世界中,实体可以轻松拥有数百甚至数千个独特属性。

答案 16 :(得分:0)

如果所有其他方法都失败了,至少将该类拆分为几个部分类以提高可读性。它还使团队更容易在本课程的不同部分并行工作。

祝你好运:)