简体   繁体   中英

Design pattern suggestion for immutable class

I would like suggestions on how to solve the following.

First I have these classes that are mutable. The important class is the ImageData that contains an image as a byte array.

public class RootData
{
    public string Name { get; set; }
    public string Description { get; set; }
    public ImageData Image { get; set; }
    public List<ChildData> Children { get; set; }
}

public class ChildData
{
    public string Name { get; set; }
    public ImageData Image { get; set; }
}

public class ImageData
{
    public string Extension { get; set; }
    public byte[] Data { get; set; }
}

The second setup of classes are these and they are immutable. The important class is the ImageInfo that contains a file path to a image on disk.

public class RootInfo
{
    private RootInfo()
    {
    }

    public string Name
    {
        get { return this.name; }
    }

    public string Description
    {
        get { return this.description; }
    }

    public ImageInfo Image
    {
        get { return this.image; }
    }

    public IEnumerable<ChildInfo> Children
    {
        get { return this.children; }
    }

    public static RootInfo ToInfo(RootData rootData)
    {
    }

    private readonly string name;
    private readonly string description;
    private readonly ImageInfo image;
    private IEnumerable<ChildInfo> children;
}

public class ChildInfo
{
    public string Name
    {
        get { return this.name; }
    }

    public ImageInfo Image
    {
        get { return this.image; }
    }

    private readonly string name;
    private readonly ImageInfo image;
}

public class ImageInfo
{
    public string Path
    {
        get { return this.path; }
    }

    private readonly string path;
}

Now to the issue, I want to convert a RootData object to a RootInfo object. I was thinking first that I create a static method (ToInfo) on the RootInfo class that takes a RootData object, which creates a RootInfo object. But I don't want to make the ImageInfo class responsible for saving the image on disk.

Do you have any suggestions on how to solve this in a clean way?

Note: This is just an example, the real classes are more complex but the principle is the same.

Seems like you have two different implementations of same set of abstractions. So how about to define shared interfaces (IRootInfo, IChildInfo and IImageInfo) and make each of *Data and *Info classes implement corresponding interface? And whenever you need to work with, for example, root name, you will get it via interface, like:

public string GetNameOfRoot(IRootInfo root)
{
    return root.Name;
}

But don't forget to use interfaces instead of concret types in references:

public List<IChildInfo> Children { get; set; }

Better think of interfaces instead of a classes. A class may implement one or more interfaces, and interfaces may inherit from each other. Let's start with a read-only interface:

public interface IReadOnlyRootInfo
{
    string Name { get; }
    string Description { get; }
    ImageData Image { get; }
    List<ChildData> Children { get; }
}

Next, let's add a writable version of it:

public interface IWritableRootInfo : IReadOnlyRootInfo
{
    new string Name { get; set; }
    new string Description { get; set; }
    new ImageData Image { get; set; }
    List<ChildData> Children { get; set; }
}

And now your concrete class implements the IWritableRootInfo and thus also IReadOnlyRootInfo :

public class RootData : IWritableRootInfo, IReadOnlyRootInfo
{
    public string Name { get; set; }
    public string Description { get; set; }
    public ImageData Image { get; set; }
    public List<ChildData> Children { get; set; }
}

Now just pass the object which actually is a RootData as an IWritableRootInfo or as an IReadOnlyRootInfo to other methods.

Of course, you should continue this differentiation further down with ImageData , `ChildData etc.

You are correct that an information-bearing class like ImageData should not automatically save itself to disk when you convert it to an ImageInfo . There are lots of details involved with that operation that are outside the scope of any class whose job is basically to represent data.

It's perfectly reasonable for such a class to provide methods that convert it into other forms, though, even if they require some kind of generic support. To get the details of disk storage out of your data classes, I would do something like this...

All of your Data classes get methods that can save them to a given (abstract) storage location. You create an interface to define the services that a storage location must provide:

interface IMyStorage
{
    ImageInfo SaveImage(ImageData);
}

class RootData
{
    ...
    RootInfo SaveTo(IMyStorage storage);
}

...

class ImageData
{
    ...
    ImageInfo SaveTo(IMyStorage storage)
    {
        return storage.SaveImage(this);
    }
}

And then you can make a disk-specific implementation of IMyStorage that saves to disk, encapsulating all the details of that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM