这可能是一个非常初学者的问题,但我已经搜索了很多主题,但实际上并没有找到相同的情况,尽管我确信这种情况一直在发生。
我的项目/程序将跟踪建筑项目中图纸的更改,并在更改图纸时向人们发送通知。
将有许多建筑项目(工作现场),而每个项目中都有许多图纸。每个工程图都会有几个修订版本(更改时会创建一个新修订版本)。
这是我的项目班级
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;
}
}
我的问题是与图形类中的项目参考和修订结构中的图形参考有关。 似乎有点代码气味? 似乎将来也可能导致序列化问题。 有更好的方法吗?
一个图形对象似乎必须知道它属于哪个项目,这样,如果我使用单个图形对象,我就能知道它们属于哪个项目。
类似地,每个修订版基本上都由图纸“拥有”或作为图纸的一部分。没有图形,修订就没有意义,因此需要引用它所属的图形吗?
任何建议将不胜感激。
答案 0 :(得分:5)
您所拥有的不是循环引用,而是
的两个示例父母与子女的关系,在两端可导航。
是的,这是正常且可以接受的,不是,这不是代码的味道。是的,某些序列化工具需要您提示。例如Newtonsoft.Json需要设置ReferenceLoopHandling.Ignore
。
可导航性作为一个概念在OO设计中并不总是被谈论,这是不幸的,因为它只是您想要的概念。 (这是UML中的一个明确术语)。
您通常不需要两端都具有导航性。 亲子关系通常仅在父母之间编码。这真的很常见。例如,invoiceline
类很少需要为其父级invoice
使用显式字段,因为大多数应用程序只在获取父级发票后才查看行。
因此,设计决定不是
“没有图纸的复审有道理吗?”
但是
“我是否只需要修订就可以找到工程图?”
我的猜测是,您的修订就像发票行一样,不需要导航到其父级。图纸<---->项目关系的答案对我来说并不明显。 (这是有关您的域的分析问题,而不是有关编码样式的问题。)
OO代码和例如SQL之间存在显着差异。在SQL数据库中,它必须是revision
表,该表包含对其父级drawing
id
的引用。在OO代码中,父类几乎总是持有对子代的引用。孩子通常不需要引用父母,因为访问孩子的唯一方法是已经有了父母。
答案 1 :(得分:2)
一般来说,循环引用在C#程序和数据模型中是很正常的,所以不必担心它们。不过,在序列化期间确实必须对它们进行特殊处理。
答案 2 :(得分:1)
是的,它是一个循环引用,是的,它是代码气味。此外,我确实认为这种情况下的气味是正确的,这不是一个好的OO设计。
免责声明
对于C#程序,就像@Rugbrød所说的那样,这很正常,我无法对此发表评论,我不是C#编码器。
对于非oo范例,例如“基于组件”或过程编程,这种设计可能是可以的。
所以您可以忽略这种气味,我想这是否是您代码的上下文。
详细信息
主要问题是您正在建模数据,而不是行为。您首先要拥有“数据”,然后再考虑要在其上实现的实际功能。例如显示图纸,存档等。您还没有这些,但是您认为正确吗?
面向对象的方法(诚然,并非所有人都同意)是对行为 进行建模。如果要存档图形,请实施Drawing.Archive()
。我的意思不是设置一个标志,而是将其真正复制到冷藏或其他任何东西上。您的应用程序应该具有的真正的业务功能。
如果您这样做,您会发现,没有相互需要的行为,因为那显然是一种行为。可能发生的情况是,两种行为可能需要第三个抽象行为(有时称为“依赖倒置”)。
答案 3 :(得分:0)
我认为这里唯一的问题是Drawing.CurrentRevision
否则,Revision
属于Drawing
,后者属于Project
。
CurrentRevision
并不是图形的真正属性,它是“修订”列表中一项的快捷方式。
如何将其更改为方法GetCurrentRevision()
和CurrentRevisionID
属性?这样一来,很明显,尽管ID是ID,但不应序列化GetCurrentRevision。