Given this abstract class:
public abstract class File { public abstract string Name { get; set; } public abstract void Add(File newFile); }
A composite can be generated:
public class LogFile : File { private string _name; public override string Name { get { return _name; } set { _name = value; } } public override void Add(File newFile) { throw new NotImplementedException(); } } public class LogFiles : File { private IList<File> _files = new List<File>(); public override string Name { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override void Add(File newFile) { _files.Add(newFile); } }
Out of the box, exception code is generated. Most of the time when writing the initial code, this is what you want. When switching between the collection class and the object class you can easily forget which sort of object you’re dealing with. Especially as variables are typically named using the abstract class name:
File thisFile = new LogFile {Name = "Monday.log"}; File thatFile = new LogFiles(); thatFile.Add(new LogFile() { Name = "Tuesday.log" }); thatFile.Add(new LogFile() { Name = "Wednesday.log" }); thatFile.Name = "My files"; // Runtime error
However, when consuming these objects, there are often issues when the parametersless constructor is used and an implemented property throws an exception. A common example being various forms of serialisation:
static void SerializeThis(File fileSet) { var json = new JavaScriptSerializer().Serialize(fileSet); }
In this case, the code can simply be refactored to remove the exceptions:
public class LogFile : File { private string _name; public override string Name { get { return _name; } set { _name = value; } } public override void Add(File newFile) { } } public class LogFiles : File { private IList<File> _files = new List<File>(); public override string Name { get { return string.Empty; } set { } } public override void Add(File newFile) { _files.Add(newFile); } }
But this removes the safeguards for derived classes. There seems to me to be a dichotomy between the raison d’être of a composite: “I shouldn’t care whether I am an object or a collection of objects” and making sure each derived class is used correctly.
I can’t help but think I am missing something fundamental here, and to be honest, it becomes a difficult sell to continue using this pattern when the wheels come off so often.
Any thoughts?