层次结构和设计模式问题

时间:2011-04-29 20:24:03

标签: design-patterns hierarchy

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

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

如果类型为 out ,则文档具有接收器。接收器可以是三种类型之一:人,公司,部门。

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

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

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

提前致谢。


in和out之间只有一些差异,相同的字段除了发送方和接收方之外。此外,行为是相同的,稍有变化。

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

主要问题是当我拿到文件时如何读取发送者或接收者,我必须阅读该数据。例如,如果我必须阅读发件人,并且我使用具有发件人类型的枚举,我必须执行此类代码,如果发件人==人阅读人员并将其分配给其他人阅读公司并分配给公司。如果我使用继承,我如何避免使用强制转换或我如何知道发件人是没有太多代码或演员的人或公司。再次感谢。

5 个答案:

答案 0 :(得分:1)

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

ISender可由人和公司实施。 IReceiver可以由人员,公司和部门实施。

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

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

答案 1 :(得分:1)

就个人而言,我没有看到建模作为文档类的层次结构的特别优势,因为文档内外的差别很小。 特别考虑到如果

  

如果文档中的类型有发件人。

即使隐含它也有接收器(你)。同样是出于文件。 所以,据我所知,我可以使用枚举来区分文件(进出)和类型(人,公司,部门)。

尽可能尝试关注kiss principle

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

编辑:

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

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

答案 2 :(得分:0)

我非常反对这里的等级制度。发件人和收件人显然是具有某些行为并携带一些数据的概念。我将专注于这些职责并创建实体Sender和Reciever。之后,如果必要的话,可以像CompanySender那样制作这些子类。如果您的整个系统不是关于发件人和收件人(我认为不是),那么基本上不要使用发送者逻辑来对公司实体进行轮询。

答案 3 :(得分:0)

基本上归结为此。如果有像

这样的陈述
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())语句本地化到一个地方。不在代码中的不同位置重复决策的好处很多。

答案 4 :(得分:0)

对于对文档进行建模,最佳设计取决于您通常如何处理文档。

如果您通常在编译时知道文档是输入/输出类型而不是发送方与接收方之间存在大量共享属性和行为,那么层次结构会很好(因为您有方法可以接受InDocument或OutDocument,它们可以在编译时解析,甚至可以在运行时解析,具体取决于您的语言)。

另一方面,如果您的文档全部混淆,并且您在运行时主要处理它们,那么另一个解决方案(我将在稍后介绍)可能更清晰。我怀疑这对于发送方和接收方几乎肯定是正确的,这几乎肯定不保证层次结构(但是,根据您的语言,您可能需要按顺序使用接口标记模仿我即将提出的功能性解决方案)。在这些情况下你想要的是一种“带数据的枚举”。在像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)