【问题标题】:Hierarchy and design patterns question层次结构和设计模式问题
【发布时间】:2011-04-29 20:24:02
【问题描述】:

我正在为系统建模一个文档类。文档可以是以下两种类型之一:inout

如果类型是in,则文档有一个发件人。发件人可以是以下两种类型之一:个人或公司。

如果类型是out,则文档有接收者。接收者可以是以下三种类型之一:个人、公司、部门。

我不确定是使用带有文档类型枚举的属性还是使用带有文档基类和每个文档类型两个类的层次结构更好。

对于发送者和接收者,我不确定层次结构是否是一个好的选择,因为这三种类型没有任何共同点(个人、公司、部门)以及如何避免无效的发送者。

如果你能给我一些关于如何建模文档类的建议,或者如果 你可以告诉我一些我应该使用的设计模式。

提前致谢。


in 和 out 之间只有很少的区别,除了发送者和接收者之外,相同的字段。此外,行为是相同的,只是略有变化。

发送者和接收者没有行为,他们唯一需要做的就是包含正确的对象,例如发送者可以包含一个人或一个公司,但没有一个部门,因为部门不是有效的发送者。此外,如果发件人包含一个人,则它不能包含公司,因为只接受一个发件人。

主要问题是当我获取文档时如何读取发送方或接收方,而我必须读取该数据。例如,如果我必须读取发件人并且我使用枚举类型的发件人,我必须执行这样的代码 if sender==person 读取人员并将其分配给其他人读取公司并分配给公司。如果我使用继承,我如何避免使用演员表,或者我如何知道发件人是没有这么多代码或演员表的人或公司。再次感谢。

【问题讨论】:

  • 您打算对这些文档进行哪些操作?它们如何依赖于 IN/OUT 等。
  • @Fdo。 - 我第二个@Andrei 的问题。具体来说,当您有一个文档时,您通常会在编译时知道它是输入文档还是输出文档?或者您将主要在运行时处理文档类型(输入/输出)?
  • Andrei,我必须在根据类型、发件人或收件人进行一些验证后保存文档的信息。另外,我必须得到文件。
  • 斯蒂芬,我在运行时就知道了。
  • @Andrei,根据类型、发件人或收件人,我必须在经过一些验证后保存文档的信息。我也必须得到文件。谢谢。

标签: design-patterns hierarchy


【解决方案1】:

如果您使用的语言允许对象实现接口,那么这将是处理复杂类型关系的好方法。

ISender 可以由 Person 和 Company 实现。 IReceiver 可以由 Person、Company 和 Department 实现。

这样,文档可以保存对接收者的引用,即使这三种类型没有任何共同之处。

对于这两种类型的文档,很大程度上取决于它们之间将共享多少功能。如果没有,那么建立关系根本没有意义。如果它们共享很多功能,那么包含一个抽象基类来实现它可能是值得的。如果它们完全(或几乎)相同,那么带有输入/输出标志的单个类可能是个好主意。

【讨论】:

  • 感谢您的回答。根据您的建议,我将对文档类型使用枚举,因为 in 和 out 之间没有太大区别。
【解决方案2】:

我个人认为将文档类建模为层次结构并没有特别的优势,因为进出文档之间的差异很小。 特别是考虑到如果

如果该类型在文档中有一个发件人。

即使它是隐含的,它也有一个接收者(你)。出文件也是如此。 因此,据我所知,从您提供的信息中,我会使用 enum 来区分 Document(进出)和类型(个人、公司、部门)。

尽可能关注kiss principle

无论如何,您还必须考虑您将对文档执行的操作类型以及必须存储的附加数据。 如果您发现 in 和 out 文档可能会在您的应用程序增长时增加它们的差异,那么引入层次结构可能是一个好主意。 (即使在这种情况下,我也会将类型分开,使用一些类似枚举的结构来存储它们)

编辑:

关于您的评论:也许有。我不知道你打算使用哪种语言。但是,例如,对于 Java,每个实体(个人、公司..)都可以是一个实现类 一个接口(例如一个接口调用实体)。然后使用泛型,您可以实例化您的类,强制泛型类型成为接口实体的实现。 比如:

public interface Entity{...}
public class Document<T implements Entity>{}

【讨论】:

  • 感谢您的回答。根据您的建议,我将对文档类型使用枚举,因为 in 和 out 之间没有太大区别。对于发送者和接收者保存文档时可以,但是当我获取文档时,每次需要读取发送者或接收者时,我都必须编写一些代码。例如,如果 sender == person 则读取 person else 然后读取 company。不知道有没有什么办法可以避免这种代码。
【解决方案3】:

基本上可以归结为这一点。如果会有类似的 if 语句

if(document.IsIncoming()){
  do something
}elseif (document.SentToDepartment()) {
  do something else
}

使用某种多态解决方案(想想抽象类或接口)重复了不止几个地方。发送者和接收者类型也是如此。

但是,您不需要在顶部进行子类化。您可以通过多态性将具有不同行为的单个文档类附加到它。假设传入和传出文档的打印行为不同。然后创建 IPrinter 接口并在两个类中实现它,如下所示。

public class Document
{
  DocumentType type;
  IPrinter printer;
}
interface IPrinter{
  print();
}

class IncomingPrinter :IPrinter{}

class OutgoingPrinter :IPrinter{}

只要确定要传入文档(可能是在创建文档时),您就可以分配 IncomingPrinter。如果有多种类型的行为需要像这样分配,通常使用工厂模式。这样您就可以将 if(doc.IsIncoming()) 语句本地化到一个地方。在代码的不同位置不重复决策的好处很多。

【讨论】:

  • 为什么需要从 Document 中引用打印机?我的意思是为什么不打印(文档)?
  • @Boris:本例中假设传入和传出文档的打印功能不同。我们希望避免重复 if(doc.IsIncoming()) 类型的决策。 IPrinter 界面正是这样做的。
  • 啊好吧,我明白你的意思了。我实际上错过了您答案的“工厂”部分。顺便提一句。这也可以通过 Print(document) 方法实现为访问者。在像 c# 4 这样的语言中,它变得很容易 (blogs.msdn.com/b/shawnhar/archive/2011/04/05/…) 没有类爆炸,这对于使用强类型语言的访问者来说是典型的。
【解决方案4】:

我非常反对这里的等级制度。发送者和接收者显然是具有某些行为并带有某些数据的概念。我将专注于这些职责并创建实体 Sender 和 Reciever。稍后,如果需要,可以创建这些的子类,例如 CompanySender。如果您的整个系统与发送者和接收者无关(我有点假设它不是),则基本上不要用发送者逻辑污染公司实体。

【讨论】:

    【解决方案5】:

    对于输入与输出文档的建模,最佳设计取决于您通常如何处理文档。

    如果您通常在编译时知道文档是否是输入/输出类型,并且除了发送者与接收者之外,还有很多共享属性和行为,那么层次结构会很好(因为您有方法接受可能在编译时解析的 InDocument 或 OutDocument,甚至取决于您的语言在运行时解析)。

    另一方面,如果您的文档都混杂在一起并且您主要在运行时处理它们,那么另一种解决方案(我将很快介绍)可能会更简洁。我怀疑这对于发送者和接收者类型来说几乎可以肯定是正确的,这几乎可以肯定不保证层次结构(尽管,根据您的语言,您可能需要按顺序使用接口 marker模拟我将要介绍的功能解决方案)。在这些情况下,您想要的是一种“带有数据的枚举”。在像 F# 这样的对象函数式语言中,您可以结合记录类型和判别联合来很好地建模:

    type PersonInfo = {Name:string ; Age:int}
    
    type CompanyInfo = {Name:string ; Location:string} 
    
    type sender =
        | Person of PersonInfo
        | Company of CompanyInfo
    
    type DepartmentInfo = {Name:string ; Floor: int}
    
    type receiver =
        | Person of PersonInfo
        | Company of CompanyInfo
        | Department of DepartmentInfo
    
    type documentType =
        | In of sender
        | Out of receiver
    
    type Document = {Type:documentType ; Title:string ; Body:string ; Date:DateTime } with 
        //example of how to process a Document using pattern matching
        override this.ToString() =
            match this.Type with
            | In(s) -> 
                match s with
                | sender.Person(info) -> sprintf "In / Person, extra info: Name = %s ; Age= %i" info.Name info.Age
                | sender.Company(_) -> "In / Company"
            | Out(r) ->
                match r with
                | receiver.Person(_) -> "In / Person"
                | receiver.Company(_) -> "In / Company"
                | receiver.Department(_) -> "In / Department"
    
    
    //create a list of In and Out documents all mixed up
    let documents = 
       [{Type = In(sender.Person({Name="John"; Age=3})); Title="My In Doc"; Body="Hello World"; Date=DateTime.Now}
        {Type = Out(receiver.Department({Name="John"; Floor=20})); Title="My Out Doc"; Body="Testing"; Date=DateTime.MinValue}]
    
    //partition documents into a list of In and Out Types
    
    let inDocuments, outDocuments = 
        documents |> List.partition (function | {Type=In(_)} -> true | {Type=Out(_)} -> false)
    

    【讨论】:

      猜你喜欢
      • 2011-05-07
      • 1970-01-01
      • 2014-12-06
      • 1970-01-01
      • 1970-01-01
      • 2023-02-14
      • 2011-04-02
      • 2011-01-18
      • 1970-01-01
      相关资源
      最近更新 更多