Design patterns 层次结构与设计模式问题

Design patterns 层次结构与设计模式问题,design-patterns,hierarchy,Design Patterns,Hierarchy,我正在为一个系统建模一个文档类。文档可以是两种类型之一:输入或输出 如果类型为中的,则文档具有发件人。发件人可以是两种类型之一:个人或公司 如果类型为“输出”,则文档有一个接收者。接收者可以是三种类型之一:个人、公司、部门 我不确定是使用带有枚举的属性作为文档类型更好,还是使用带有文档基类和两个类的层次结构作为每种文档类型更好 对于发送者和接收者,我不确定层次结构是否是一个好的选择,因为这三种类型没有任何共同点(个人、公司、部门)以及如何避免无效发送者 如果您能给我一些关于如何建模文档类的建议,

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

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

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

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

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

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

提前谢谢


除了发送方和接收方之外,in和out字段之间只有一些区别。而且,行为也一样,只是有一点变化

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


主要的问题是,当我收到文档时,如何读取发送者和接收者,而我必须读取这些数据。例如,如果我必须读取发送者,并且我对发送者类型使用枚举,我必须执行如下代码:如果发送者==个人读取个人并将其分配给个人,否则读取公司并分配给公司。如果我使用继承,我将如何避免使用cast,或者如何知道发送者是否是没有那么多代码或cast的个人或公司。再次感谢。

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

ISender可由个人和公司实施。IReceiver可以由个人、公司和部门实施

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


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

就个人而言,我不认为将文档类建模为层次结构有什么特别的好处,因为in和out文档之间的差别很小。 特别是考虑到如果

如果类型在文档中,则具有发件人

即使它是隐式的,它也有一个接收者(你)。我们的文档也是如此。 因此,根据您提供的信息,我会使用enum来区分文档(输入和输出)和类型(个人、公司、部门)

尽可能地跟随

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

编辑:

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

public interface Entity{...}
public class Document<T implements Entity>{}
公共接口实体{…}
公共类文档{}

我非常反对这里的等级制度。发送者和接受者显然是具有某种行为和携带某些数据的概念。我将专注于这些职责,并创建发送者和接收者实体。以后,如果需要,可以创建类似CompanySender的子类。基本上,如果你的整个系统不是关于发送者和接收者的,就不要用发送者逻辑来处理公司实体(我有点认为不是)。

基本上可以归结为这一点。如果有像这样的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{}

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

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

如果您通常在编译时知道文档是否是in/out类型,而不是发送方和接收方,那么有很多共享属性和行为,那么层次结构就很好了(因为您有接受它们的方法)
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)