user1047771
user1047771

Reputation: 707

Composite pattern for file and folder structure with parent reference in C#

I'm currently struggling with the implementation of a set of file system classes. I guess this calls for the composite pattern if I'm not mistaken. So I set up the following classes:

An abstract class Node which has a reference to its parent folder and two classes Folder and File that implement Node. A folder contains a collection of all its children and methods to add and remove children.

The thing is, I can't figure out how to implement all the methods properly. In all the examples I have seen there is no reference to the parent in the children. How can the AddChild method ensure that the child's parent reference is set correctly? I solved that by checking whether child.Parent has already been set to the folder or it throws an ArgumentException. The matter is further complicated by the fact that AddChild might also throw an exception like DuplicateNameException or something. So my methods look like this now:

File.AddTo(Folder folder) {
    this.Parent = folder;
    try {
        folder.AddChild(this);
    } catch {
        this.Parent = null;
        throw;
    }
}

Folder.AddChild(Node child)
{
    if(child.Parent != this)
        throw new ArgumentException(...);
    ...
}

Now I have this ugly AddTo method and cannot do something like someFolder.AddChild(new File(...)). I wonder how it was implemented with ListViewItem for instance. There I can just do someListView.Items.Add(new ListViewItem(...)).

My solution works, but I'm not convinced that it's the right way to do this. Maybe someone has a better solution or can point me to a good example. Thanks in advance.

EDIT: Minimal full class definitions below.

abstract class Node
{
    public Folder Parent { get; protected set; }
    public string Name { get; private set; }

    public Node(string name) {
        Parent = null;
        Name = name;
    }
}

class Folder : Node {
    private Dictionary<string, Node> _children;

    public Folder(string name) : base(name) {
        // Other initializations here...
    }

    public void AddChild(Node child) {
        if(child is Folder)
            ((Folder)child).Parent = this; // Damn, doesn't work for files!!!
        else if(child.Parent != this)
            throw new ArgumentException();

        if(_children.ContainsKey(child.Name))
            throw new DuplicateNameException();

        _children[child.Name] = child;
    }
}

class File : Node {
    public File(string name) : base(name) {
        // Other initializations here...
    }

    public void AddTo(Folder folder) {
        Parent = folder;
        try {
            folder.AddChild(this);
        } catch {
            Parent = null;
        }
    }
}

Upvotes: 4

Views: 7829

Answers (4)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236208

When I implement bidirectional associations, I usually move all association maintenance to one of the sides. In this case, I'd chose Folder.

public abstract class Node
{
    public Folder Parent { get; set; }
    public string Name { get; set; }
    public abstract long Size { get; }
}

public class File : Node
{        
    private long _size;

    public override long Size
    {
        get { return _size; }
    }

    public void AddTo(Folder folder)
    {
        folder.Add(this);
    }

    public void RemoveFrom(Folder folder)
    {
        folder.Remove(this);
    }
}

public class Folder : Node
{
    private List<Node> _children = new List<Node>();

    public void Add(Node node)
    {
        if (node.Parent == this)
            return; // already a child of this folder

        _children.Add(node);
        node.Parent = this;
    }

    public void Remove(Node node)
    {
        if (node.Parent != this)
            return; // not a child of this folder

        _children.Remove(node);
        node.Parent = null;
    }

    public override long Size
    {
        get { return _children.Sum(node => node.Size); }
    }
}

PS try to eliminate bidirectional association, it adds lot of headache.

UPDATE With unidirectional association you have simple code, without ugly Folder field in Node class (I hate when base class depends on its child). Also no headache with adding/removing files.

public abstract class Node
{
    public string Name { get; set; }
    public abstract long Size { get; }
}

public class File : Node
{        
    private long _size;

    public override long Size
    {
        get { return _size; }
    }
}

public class Folder : Node
{
    private List<Node> _children = new List<Node>();

    public void Add(Node node)
    {
        if (_children.Contains(node))
            return;

        _children.Add(node);
    }

    public void Remove(Node node)
    {
        if (!_children.Contains(node))
            return;

        _children.Remove(node);
    }        

    public override long Size
    {
        get { return _children.Sum(node => node.Size); }
    }
}

Upvotes: 1

Cheeso
Cheeso

Reputation: 192467

AddChild() is a method on the parent.

Thinking about the purpose of the method, and your desire to maintain a reference to the parent in the child, you need to expose a property on the child that can be set by the parent, presumably in the AddChild method.

public abstract class Node 
{
    private Node parent;

    internal void SetParent(Node parent)
    {
        this.parent = parent;
    }
}

public class Folder : Node
{
    void AddChild(Node child) 
    {
        this.children.Add(child);
        child.SetParent(this); // or, you could use a C# Property
    }
}


public class File : Node
{
}

The child knows how to establish its parent; the parent knows how to adopt a child.

Upvotes: 0

Erik Dietrich
Erik Dietrich

Reputation: 6090

If you're adding a child to the parent, that should be done through a method on parent. Parent can then confirm/validate its own state and that its preconditions are satisfied. It's not up to a node to figure out whether its parent is valid -- let the parent do that.

So, by way of code, you have something like:

public class Node
{
    public string Name { get; set; }
    public abstract void Add(Node child);
    protected abstract void CreateOnDisk();
}

public class File
{
    public override void Add(Node child)
    {
         //No op, since you can't add a child to a file
    }

    protected override void CreateOnDisk()
    {
        File.Create(this.Name);
    }
}

public class Directory
{
    public override void Add(Node child)
    {
        child.Name = Path.Combine(this.Name, child.Name);
        child.CreateOnDisk();
    }

    protected override CreateOnDisk()
    {
        Directory.Create(this.Name);
    }
}

I just freelanced a bit off the top of my head, but that's to give an idea. I really think there's no need to keep track of your parent, and I think that's going to turn out in the end to be a fairly cumbersome solution.

Upvotes: 1

Jaime
Jaime

Reputation: 6814

How about doing it the other way around:

Folder.AddChild(Node child) 
{
   child.Parent = this;
   this._children.Add(child); // or what ever your doing to store the children
   ...
}

Upvotes: 1

Related Questions