循环参考-体系结构问题

时间:2018-11-02 11:43:19

标签: c# oop circular-reference

这可能是一个非常初学者的问题,但我已经搜索了很多主题,但实际上并没有找到相同的情况,尽管我确信这种情况一直在发生。

我的项目/程序将跟踪建筑项目中图纸的更改,并在更改图纸时向人们发送通知。

将有许多建筑项目(工作现场),而每个项目中都有许多图纸。每个工程图都会有几个修订版本(更改时会创建一个新修订版本)。

这是我的项目班级

public class Project
{
    private readonly List<Drawing> _drawings = new List<Drawing>(30);
    private readonly List<Person> _autoRecepients = new List<Person>(30);

    public int ID { get; private set; }
    public string ProjectNumber { get; private set; }
    public string Name { get; private set; }
    public bool Archived { get; private set; }
    public List<Person> AutoRecepients { get { return _autoRecepients; } }


    public Project(int id, string projectNumber, string name)
    {
        if (id < 1) { id = -1; }

        ID = id;
        ProjectNumber = projectNumber;
        Name = name;
    }


    public bool AddDrawing(Drawing drawing)
    {
        if (drawing == null) return false;
        if (_drawings.Contains(drawing)) { return true; }

        _drawings.Add(drawing);

        return _drawings.Contains(drawing);
    }


    public void Archive()
    {
        Archived = true;
    }

    public bool DeleteDrawing(Drawing drawing)
    {
        return _drawings.Remove(drawing);
    }

    public IEnumerable<Drawing> ListDrawings()
    {
        return _drawings.AsReadOnly();
    }

    public override string ToString()
    {
        return string.Format("{0} {1}", ProjectNumber, Name);
    }
}

这是我的绘画课

public class Drawing : IDrawing
{
    private List<IRevision> _revisions = new List<IRevision>(5);
    private List<IssueRecord> _issueRecords = new List<IssueRecord>(30);
    private IRevision _currentRevision;

    public int ID { get; private set; }
    public string Name { get; private set; }
    public string Description { get; set; }
    public Project Project { get; private set; }
    public IRevision CurrentRevision { get { return _currentRevision; } }


    public Drawing(int id, string name, string description, Project project)
    {
        // To be implemented
    }


    /// <summary>
    /// Automatically issue the current revision to all Auto Recepients
    /// </summary>
    public void AutoIssue(DateTime date)
    {
        AutoIssue(date, _currentRevision);
    }

    /// <summary>
    /// Automatically issue a particular revision to all Auto Recepients
    /// </summary>
    public void AutoIssue(DateTime date, IRevision revision)
    {

    }

    public void IssueTo(Person person, DateTime date, IRevision revision)
    {
        _issueRecords.Add(new IssueRecord(date, this, revision, person));

        throw new NotImplementedException();
    }


    public void IssueTo(Person person, DateTime date)
    {
        IssueTo(person, date, _currentRevision);
    }        

    public void IssueTo(IEnumerable<Person> people, DateTime date)
    {
        IssueTo(people, date, _currentRevision);
    }

    public void IssueTo(IEnumerable<Person> people, DateTime date, IRevision revision)
    {
        foreach (var person in people)
        {
            IssueTo(person, date, revision);
        }

    }

    public void Rename(string name)
    {
        if (string.IsNullOrWhiteSpace(name)) { return; }

        Name = name;
    }

    public void Revise(IRevision revision)
    {
        if (revision.Name == null ) return;

        _revisions.Add(revision);
        _currentRevision = revision;
    }

    public struct IssueRecord
    {
        public int ID { get; private set; }
        public DateTime Date { get; private set; }
        public IDrawing Drawing { get; private set; }
        public IRevision Revision { get; private set; }
        public Person Person { get; private set; }

        public IssueRecord(int id, DateTime date, IDrawing drawing, IRevision revision, Person person)
        {
            if (id < 1) { id = -1; }

            ID = id;
            Date = date;
            Drawing = drawing;
            Revision = revision;
            Person = person;
        }

    }
}

这是修订版结构

public struct Revision : IRevision
{        
    public int ID { get; private set; }
    public string Name { get; }
    public DateTime Date { get; set; }
    public IDrawing Drawing { get; }
    public IDrawingFile DrawingFile { get; private set; }

    public Revision(int id, string name, IDrawing drawing, DateTime date, IDrawingFile drawingFile)
    {
        if (name == null) { throw new ArgumentNullException("name", "Cannot create a revision with a null name"); }
        if (drawing == null) { throw new ArgumentNullException("drawing", "Cannot create a revision with a null drawing"); }
        if (id < 1) { id = -1; }

        ID = id;
        Name = name;
        Drawing = drawing;
        Date = date;
        DrawingFile = drawingFile;
    }

    public Revision(string name, IDrawing drawing, DateTime date, IDrawingFile drawingFile)
        : this(-1, name, drawing, date, drawingFile)
    {

    }

    public Revision(string name, IDrawing drawing)
        : this(-1, name, drawing, DateTime.Today, null)
    {

    }

    public void ChangeID(int id)
    {
        if (id < 1) { id = -1; }

        ID = id;
    }

    public void SetDrawingFile(IDrawingFile drawingFile)
    {
        DrawingFile = drawingFile;
    }
}

我的问题是与图形类中的项目参考和修订结构中的图形参考有关。 似乎有点代码气味? 似乎将来也可能导致序列化问题。 有更好的方法吗?

一个图形对象似乎必须知道它属于哪个项目,这样,如果我使用单个图形对象,我就能知道它们属于哪个项目。

类似地,每个修订版基本上都由图纸“拥有”或作为图纸的一部分。没有图形,修订就没有意义,因此需要引用它所属的图形吗?

任何建议将不胜感激。

4 个答案:

答案 0 :(得分:5)

您所拥有的不是循环引用,而是

的两个示例

父母与子女的关系,在两端可导航

是的,这是正常且可以接受的,不是,这不是代码的味道。是的,某些序列化工具需要您提示。例如Newtonsoft.Json需要设置ReferenceLoopHandling.Ignore

可导航性作为一个概念在OO设计中并不总是被谈论,这是不幸的,因为它只是您想要的概念。 (这是UML中的一个明确术语)。

您通常不需要两端都具有导航性。 亲子关系通常仅在父母之间编码。这真的很常见。例如,invoiceline类很少需要为其父级invoice使用显式字段,因为大多数应用程序只在获取父级发票后才查看行。

因此,设计决定不是

“没有图纸的复审有道理吗?”

但是

“我是否只需要修订就可以找到工程图?”

我的猜测是,您的修订就像发票行一样,不需要导航到其父级。图纸<---->项目关系的答案对我来说并不明显。 (这是有关您的域的分析问题,而不是有关编码样式的问题。)

OO代码和例如SQL之间存在显着差异。在SQL数据库中,它必须是revision表,该表包含对其父级drawing id的引用。在OO代码中,父类几乎总是持有对子代的引用。孩子通常不需要引用父母,因为访问孩子的唯一方法是已经有了父母。

答案 1 :(得分:2)

一般来说,循环引用在C#程序和数据模型中是很正常的,所以不必担心它们。不过,在序列化期间确实必须对它们进行特殊处理。

答案 2 :(得分:1)

是的,它是一个循环引用,是的,它是代码气味。此外,我确实认为这种情况下的气味是正确的,这不是一个好的OO设计。

免责声明

  1. 对于C#程序,就像@Rugbrød所说的那样,这很正常,我无法对此发表评论,我不是C#编码器。

  2. 对于非oo范例,例如“基于组件”或过程编程,这种设计可能是可以的。

所以您可以忽略这种气味,我想这是否是您代码的上下文。

详细信息

主要问题是您正在建模数据,而不是行为。您首先要拥有“数据”,然后再考虑要在其上实现的实际功能。例如显示图纸,存档等。您还没有这些,但是您认为正确吗?

面向对象的方法(诚然,并非所有人都同意)是对行为 进行建模。如果要存档图形,请实施Drawing.Archive()。我的意思不是设置一个标志,而是将其真正复制到冷藏或其他任何东西上。您的应用程序应该具有的真正的业务功能。

如果您这样做,您会发现,没有相互需要的行为,因为那显然是一种行为。可能发生的情况是,两种行为可能需要第三个抽象行为(有时称为“依赖倒置”)。

答案 3 :(得分:0)

我认为这里唯一的问题是Drawing.CurrentRevision

否则,Revision属于Drawing,后者属于Project

CurrentRevision并不是图形的真正属性,它是“修订”列表中一项的快捷方式。

如何将其更改为方法GetCurrentRevision()CurrentRevisionID属性?这样一来,很明显,尽管ID是ID,但不应序列化GetCurrentRevision。