代码之家  ›  专栏  ›  技术社区  ›  Metro Smurf

具有100个以上属性的类的设计模式

  •  22
  • Metro Smurf  · 技术社区  · 16 年前

    对于设计具有超过100个属性的类,您会提供什么建议/建议/指导?

    背景

    • 类描述发票。一张发票可以有100多个描述它的属性,即日期、金额、代码等。
    • 我们提交发票的系统将使用100个属性中的每一个,并作为单个实体提交(而不是在不同的时间提交不同的部分)。
    • 描述发票的属性是业务流程的一部分。不能更改业务流程。

    建议?

    • 当设计一个具有100个属性的类时,其他人做了什么?也就是说,用100个属性中的每一个创建类?
    • 以某种方式把它分开(如果是,怎么做)?
    • 或者这在你的经历中是一个很正常的事情?

    编辑 在阅读了一些很好的回答并进一步思考之后,我认为这个问题没有任何单一的答案。但是,由于我们最终按照 LBrushkin's Answer 我把他的功劳归功于他。虽然不是最流行的答案,但Lbrushkin的答案帮助我们定义了几个接口,我们在整个应用程序中聚合和重用这些接口,并促使我们研究一些模式,这些模式可能会在今后有所帮助。

    17 回复  |  直到 15 年前
        1
  •  11
  •   LBushkin    16 年前

    我可以想象这些属性中的一些可能是相互关联的。我可以想象,可能有一些属性组定义了发票的独立方面,它们作为一个组是有意义的。

    您可能需要考虑创建为发票的不同方面建模的单个接口。这可能有助于以更连贯、更容易理解的方式定义在这些方面上操作的方法和属性。

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

    请记住,您选择为问题建模的抽象和与其他系统(或业务流程)通信所需的抽象不一定是相同的。事实上,应用 bridge pattern 允许独立的抽象独立发展。

        2
  •  33
  •   Philip Wallace    16 年前

    您可以像处理数据库表那样尝试“规范化”。可能将所有与地址相关的属性 Address 例如类-然后 BillingAddress MailingAddress 类型的属性 地址 在你 Invoice 班级。这些类稍后也可以重用。

        3
  •  22
  •   Daniel Brückner    16 年前

    糟糕的设计显然是在您提交给的系统中-没有发票具有100+个不能分组到子结构中的属性。例如,发票将有一个客户,客户将有一个ID和一个地址。地址依次是街道、邮政编码和其他内容。但所有这些属性不应该直接属于发票-发票没有客户ID或邮政编码。

    如果您必须构建一个发票类,所有这些属性都直接附加到发票上,我建议为一个客户、一个地址和所有其他所需的东西创建一个具有多个类的干净设计,然后用一个没有存储和逻辑本身的胖发票类包装这个设计良好的对象图,通过所有操作。到后面的对象图。

        4
  •  6
  •   3Dave    15 年前

    六羟甲基三聚氰胺六甲醚。。。所有这些真的相关吗 明确地 只有 到发票上?通常我所看到的是:

    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);
    ....
    

    这里的想法是将您划分为逻辑单元。在上面的示例中,每个类对应于数据库中的一个表。您可以将这些内容中的每一个加载到数据访问层中的一个专用类中,或者从生成报告(发票)时存储它们的表中选择一个联接。

        5
  •  3
  •   Andriy Volkov    16 年前

    除非你 代码 实际上,在许多地方使用了许多属性,我会改为使用字典。

    拥有真实属性有其优点(类型安全性、可发现性/可理解性、可重构性),但如果所有代码都是从其他地方获取这些属性、在UI上显示、在Web服务中发送、保存到文件等,则这些属性并不重要。

        6
  •  2
  •   Raj More    16 年前

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

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

        7
  •  2
  •   David R Tribble    16 年前

    它被认为是不好的O-O样式,但是如果您所做的只是用属性填充一个对象以将其传递给处理,而处理只读取属性(大概是为了创建一些其他对象或数据库更新),那么可能您需要一个简单的pod对象,拥有所有公共成员、一个默认的构造函数,而不需要其他对象。ER成员方法。因此,您可以将它视为一个属性容器,而不是一个完整的对象。

        8
  •  1
  •   Oren Mazor    16 年前

    我用字典来做这样的事。

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

        9
  •  1
  •   kb.    16 年前

    在本文中,您可能会发现一些关于Steve Yegge的关于财产的有用想法,这些想法称为 Universal Design Pattern .

        10
  •  1
  •   alphazero    16 年前

    你不应该纯粹出于审美考虑。

    根据您的注释,该对象基本上是一个数据传输对象,它由期望所有字段都存在的遗留系统使用。

    除非从零件组成这个物体有真正的价值,否则掩盖它的功能和目的究竟能得到什么呢?

    这是合理的理由:

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

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

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

    如果这些考虑都不适用于您的案例,那么有什么意义呢?

        11
  •  1
  •   Frank Schwieterman    16 年前

    听起来,对于最终结果,您需要生成一个具有大约100个属性的发票对象。每种情况下你都有100个这样的财产吗?也许你会想要一个工厂,一个类,在给定一组较小的参数的情况下,可以生成一个发票。对于发票相关字段相关的每个场景,可以添加不同的工厂方法。

        12
  •  1
  •   memnoch_proxy    16 年前

    如果你要创造的是 table gateway 对于这个其他服务的预先存在的100列表,一个列表或字典可能是快速入门的方法。但是,如果您从大型表单或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模型是有用的……我会花一分钱,因为不同的发票会有不同的发票和不同的程序,如果提交发票出错,您需要额外的程序:—)

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

    注意大卫·利维利的回答:这是一个很好的洞察力。通常,表单上的字段都是各自的数据结构,并有各自的验证规则。所以您可以很快地对复合对象建模。这将使每个字段类型与其自己的验证规则相关联,并强制执行更严格的类型。

    如果您确实需要进一步进行验证,那么业务规则通常是与提供它们的表单或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 ;
    

    我想说的是,在这种情况下,你肯定会想重新考虑你的方式,直到你找到舒适的东西。希望您对模式和对象模型之类的东西有一些控制权。(顺便问一下……那张表是不是称为“规范化的”?我已经看到了该模式的变化,通常是按数据类型组织的……好吗?)

        13
  •  0
  •   Gus Paul    16 年前

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

        14
  •  0
  •   tom d    16 年前

    你可以试试linq,它会自动生成你的属性。如果所有字段都分布在多个表中,您可以构建一个视图并将该视图拖到设计器中。

        15
  •  0
  •   bobflux    16 年前

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

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

    当您要将10个字符串成员从某种编码转换为Unicode时,不应触摸其他一些字符串成员,您需要对其他成员应用一些数字处理…转换类型…for循环比复制粘贴大量的代码更干净。

        16
  •  0
  •   James Anderson    16 年前

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

    可能可以将地址这样的东西分成一个子类,但这是因为地址本身确实是一个实体,而且很容易被识别出来。

    教科书(即过于简单化,在现实世界中无法使用)发票如下:

     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;
        .
        .
        .
    

    所以我很惊讶你在那里至少没有一系列的行项目。

    习惯吧!在商业世界中,一个实体可以很容易地拥有成百上千的独特属性。

        17
  •  0
  •   Ami    16 年前

    如果所有其他的都失败了,那么至少将类拆分为几个部分类,以便具有更好的可读性。这也将使团队更容易在本课程的不同部分并行工作。

    祝你好运: