Composite and Memento Design Patterns – A Real World Example

So you don’t need to know software design patterns. But, as I hope got across in my post, knowing and understanding patterns can only benefit you. I wanted to put together a real-life example of some of the instances where I’ve used patterns, even if the use of the patterns was unintentional. Hopefully you will get some use out of them.

I had a requirement where I had to track a list of files and needed to be able to save this list. But the list needed to be able to not only have a list of files but needed to support a list of lists. The use case was that the user could define a list of files say, “My files from client A.” The user could then put together a list of lists defined as, “My clients from the east coast” which would be comprised of lists from any clients on the east coast.

Of course this defines the composite pattern. The composite pattern is just an object that contains zero or more of that object. The two most obvious classes in .NET that use this are TreeViewItem and MenuItem. A MenuItem contains it’s own content as a MenuItem but also contains children MenuItems. In my case my class, “FileGroupModel” has a list of files (think of that list as the content of the class) as well as a list of FileGroupModels, which is exactly what the composite design pattern is.

Now to facilitate this I obviously need to save out the FileGroupModel, which is the memento pattern. As you’ll see in the code I went with a DataContract to save out the data.

using CompositeMementoSample.Infrastructure;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace CompositeMementoSample.Models
{
    [DataContract(IsReference = true)]
    public class FileGroupModel : DomainObject
    {
        public virtual string OpenFileDialogFilter 
        {
            get { return "Files & Groups (*.*)|*.*|File Groups (*.fxml)|*.fxml"; }
        }
        public virtual string SerializedExtension 
        {
            get { return ".fxml"; }
        }

        [DataMember]
        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                if (this.name != value)
                {
                    this.ValidateProperty("Name", value);
                    this.name = value;
                    this.RaisePropertyChanged("Name");
                }
            }
        }

        [DataMember]
        private ObservableCollection<FileInfo> files;
        public ObservableCollection<FileInfo> Files
        {
            get
            {
                return files;
            }
        }

        [DataMember]
        private ObservableCollection<FileGroupModel> groups;
        public ObservableCollection<FileGroupModel> Groups
        {
            get
            {
                return groups;
            }
        }

        public ReadOnlyCollection<object> CompositeList
        {
            get
            {
                List<object> allItems = new List<object>();
                allItems.AddRange(files);
                allItems.AddRange(groups);
                return new ReadOnlyCollection<object>(allItems);
            }
        }

        public FileGroupModel()
        {
            files = new ObservableCollection<FileInfo>();
            files.CollectionChanged += child_CollectionChanged;
            groups = new ObservableCollection<FileGroupModel>();
            groups.CollectionChanged += child_CollectionChanged;
        }

        void child_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            this.RaisePropertyChanged("CompositeList");
        }

        public void WriteToFile(string Path)
        {
            using (MemoryStream memStm = new MemoryStream())
            using (StreamWriter outfile = new StreamWriter(Path))
            {
                DataContractSerializer ser = new DataContractSerializer(typeof(FileGroupModel));
                ser.WriteObject(memStm, this);
                memStm.Seek(0, SeekOrigin.Begin);
                string result = new StreamReader(memStm).ReadToEnd();
                outfile.Write(result);
            }
        }

        public static FileGroupModel ReadFromFile(string Path)
        {
            string contents = System.IO.File.ReadAllText(Path);
            using (Stream stream = new MemoryStream())
            {
                byte[] data = System.Text.Encoding.UTF8.GetBytes(contents);
                stream.Write(data, 0, data.Length);
                stream.Position = 0;
                DataContractSerializer deserializer = new DataContractSerializer(typeof(FileGroupModel));
                object o = deserializer.ReadObject(stream);
                return o as FileGroupModel;
            }
        }

        public override string ToString()
        {
            return Name;
        }
    }
}

To start with, FileGroupModel extends DomainObject which in turn implements INotifyPropertyChanged and INotifyDataErrorInfo. This is done so that the model can integrate into MVVM. DomainObject can be found in my series on MVVM. So the MVVM stuff out of the way, we can get on to the composite design pattern.

Implementing Composite

Looking at line 42 you can see the observable collection that contains our FileInfos. At line 52 you can see the observable collection that contains our FileGroupModels, which makes this the composite pattern.  That’s it.  The composite pattern is just about implementing tree structure data.

CompositeMementoBut we need to use this in MVVM and we can only to bind an ItemsSource to one list. That is the purpose of the CompositeList. By hooking into the CollectionChanged event of the two observable collections, anytime files or file group models are added to either list we can raise a property changed event so the view way down the link that gets hooked up to the model via the view model, can get notified of the change to the collection.  My use of naming the combined list “CompositeList” is a bit unfortunate as I obviously mean that to indicate a combined list and not anything that really has to do with the composite pattern.

Implementing Memento

The last requirement is to be able to save out the state of the object (i.e. the memento design pattern). In .NET, arguably, the two easiest ways to write out an object is BinaryFormatter and DataContract. I tend to only use the binary formatter if there is proprietary data that needs to be stored. When that’s the case I use not only the BinaryFormater but I use it with a CryptoStream to ensure the security of the data. Most of the time, however, I try to use a DataContractSerializer. It’s a bit more in the set-up, having to define the DataContract and DataMember attributes but on the whole everything seems a bit cleaner. That way, if I need to, I can read the XML directly and see what’s going on with the data.

Now the tough part is that if you are going to use multiple classes that extend from a base class then you have to violate the Open-Closed principle. In my use of the above class I actually extend FileGroupModel (which is an abstract in my production code) to limit the types of files that are embedded. The problem with this approach is that you have to define a KnownType attribute in the base class so when you deserialize the object the DataContractSerializer knows what to do with it. This means that every time you add a class that extends the base class you have to add a KnownType for that class in the base. See? An obvious violation of the OCP but definitely a situation where we can ignore the rules on the paint can.

Next week I’ll follow up this post with an MVVM sample that is a bit closer to how I actually use it. I’ll show a sample extending FileGroupModel so you can get a better idea of using DataContractSerializer, but this also leads into using the command design pattern.

Thanks,
Brian

Leave a Reply