不可变类的设计模式建议

时间:2017-07-24 12:29:28

标签: c#

我想就如何解决以下问题提出建议。

首先,我有这些可变的类。重要的类是ImageData,它包含一个图像作为字节数组。

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; }
}

类的第二个设置是这些,它们是不可变的。重要的类是ImageInfo,它包含磁盘上图像的文件路径。

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;
}

现在问题,我想将RootData对象转换为RootInfo对象。我首先考虑在RootInfo类上创建一个静态方法(ToInfo),该方法接受一个RootData对象,该对象创建一个RootInfo对象。但我不想让ImageInfo类负责将图像保存在磁盘上。

您对如何以干净的方式解决这个问题有什么建议吗?

注意:这只是一个例子,真正的类更复杂但原理是相同的。

3 个答案:

答案 0 :(得分:0)

好像你有两组相同的抽象实现。那么如何定义共享接口(IRootInfo,IChildInfo和IImageInfo)并使每个* Data和* Info类实现相应的接口?无论何时您需要使用根名称,您都可以通过界面获取它,例如:

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

但是不要忘记在引用中使用接口而不是concret类型:

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

答案 1 :(得分:0)

更好地考虑接口而不是类。类可以实现一个或多个接口,并且接口可以彼此继承。让我们从只读界面开始:

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

接下来,让我们添加一个可写版本:

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

现在你的具体类实现了IWritableRootInfo,因此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; }
}

现在只需将实际为RootData的对象作为IWritableRootInfoIReadOnlyRootInfo传递给其他方法。

当然,你应该继续使用ImageData,'ChildData等。

来进一步区分

答案 2 :(得分:0)

正确的是,ImageData这样的信息承载类在将其转换为ImageInfo时不应自动将其自身保存到磁盘。该操作涉及许多细节,这些细节超出了其作业基本上代表数据的任何类的范围。

这样的类提供将其转换为其他形式的方法是完全合理的,即使它们需要某种通用支持。要从数据类中获取磁盘存储的详细信息,我会做这样的事情......

所有Data类都可以获取可以将它们保存到给定(抽象)存储位置的方法。您可以创建一个接口来定义存储位置必须提供的服务:

interface IMyStorage
{
    ImageInfo SaveImage(ImageData);
}

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

...

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

然后你可以制作一个特定于磁盘的IMyStorage实现,它可以保存到磁盘,并封装了它的所有细节。