【问题标题】:How to avoid downcasting on nested properties (not methods)如何避免对嵌套属性(不是方法)进行向下转换
【发布时间】:2021-11-17 08:16:08
【问题描述】:

我正在编写一个 API 来将数据导入我们的软件。

正如您在下面看到的,我的模型层次结构分为不同类型的模型(A 和 B)。

每个模型:

  • 嵌入另一个模型:“ImportModel”嵌入“Production”,其中嵌入“Parts”

     Ex : ImportModelA => ProductionModelA => List of PartModelA
    
  • 具有与自身相关的特定属性和一些共同的属性

public class ImportModelA : ImportModelBase
{
    public string ImportModelAProp { get; set; }
}

public class ImportModelB : ImportModelBase
{
    public string ImportModelBProp { get; set; }
}

public abstract class ImportModelBase
{
    public ProductionModelBase Production { get; set; }
}

public class ProductionModelA : ProductionModelBase
{
    public int ProductionModelAProp { get; set; }
}

public class ProductionModelB : ProductionModelBase
{
    public int ProductionModelBProp { get; set; }
}

public abstract class ProductionModelBase
{
    public List<PartModelBase> Parts { get; set; }
}

public class PartModelA : PartModelBase
{
    public int PartModelAProp { get; set; }
}

public class PartModelB : PartModelBase
{
    public int PartModelBProp { get; set; }
}

public abstract class PartModelBase
{
    public short CommonProp { get; set; }
}

这是我的问题:
在我的模型特定的服务方法中,我别无选择,只能向下转换“生产”和“零件”模型以访问它们的特定属性:

public class ImportModelAManagement
{
    public void DoModelASpecificStuff(ImportModelA importModel)
    {
        // First downcast
        var production = (ProductionModelA)importModel.Production;

        // Uses productionModelA specific property
        Console.WriteLine(production.ProductionModelAProp);

        // Second downcast
        var parts = production.Parts.ConvertAll(p => (PartModelA)p)
            .ToList();

        // Uses partModelA specific property        
        var firstPartModelAProp = parts[0].PartModelAProp;

        Console.WriteLine(firstPartModelAProp);
    }
}

此方法为“external”操作,不能直接添加到模型类中。

作为替代方案,我已经尝试使用泛型,如下所示:

public class ImportModelA : ImportModelBase<ProductionModelA, PartModelA>
{
    public string ImportModelAProp { get; set; }
}

public class ImportModelB : ImportModelBase<ProductionModelB, PartModelB>
{
    public string ImportModelBProp { get; set; }
}

public abstract class ImportModelBase<TProductionModel, TPartModel>
    where TProductionModel : ProductionModelBase<TPartModel>
    where TPartModel : PartModelBase
{
    public TProductionModel Production { get; set; }
}

public class ProductionModelA : ProductionModelBase<PartModelA>
{
    public int ProductionModelAProp { get; set; }
}

public class ProductionModelB : ProductionModelBase<PartModelB>
{
    public int ProductionModelBProp { get; set; }
}

public abstract class ProductionModelBase<TPartModel>
    where TPartModel : PartModelBase
{
    public List<TPartModel> Parts { get; set; }
}

public class PartModelA : PartModelBase
{
    public int PartModelAProp { get; set; }
}

public class PartModelB : PartModelBase
{
    public int PartModelBProp { get; set; }
}

public abstract class PartModelBase
{
    public short CommonProp { get; set; }
}

向下转换问题现已解决。
不幸的是,随着代码库的增长,这种设计将很快变得无法维护。

我知道我的设计不正确,所以:

  1. 如何避免在这些嵌套模型上向下转换或使用“convertAll”?
  2. “访问者”模式是否适合解决此问题?

编辑

不确定,但可以通过简化初始层次结构来解决此问题:

public class ImportModelA : ImportModelBase
{
    public string ImportModelAProp { get; set; }
    public ProductionModelA Production { get; set; }
}

public class ImportModelB : ImportModelBase
{
    public string ImportModelBProp { get; set; }
    public ProductionModelB Production { get; set; }
}

public abstract class ImportModelBase
{
    public string ImportModelBaseProp { get; set; }
}

public class ProductionModelA : ProductionModelBase
{
    public int ProductionModelAProp { get; set; }
    public PartModelA Parts { get; set; }
}

public class ProductionModelB : ProductionModelBase
{
    public int ProductionModelBProp { get; set; }
    public PartModelB Parts { get; set; }
}

public abstract class ProductionModelBase
{
    public string ProductionModelBaseProp { get; set; }
}

public class PartModelA : PartModelBase
{
    public int PartModelAProp { get; set; }
}

public class PartModelB : PartModelBase
{
    public int PartModelBProp { get; set; }
}

public abstract class PartModelBase
{
    public short CommonProp { get; set; }
}

谢谢

【问题讨论】:

  • 你展示的代码当然是例子,但是为什么你的对象需要层次结构呢?看起来您的一棵大类层次结构树与您的程序结构的结构不匹配。向下转型可能是过度设计的数据结构的标志。保持更简单,是我的猜测。
  • 这个代码确实是例如。在我看来,这种层次结构对我来说非常明显,因为每个模型在概念上都是父模型的一部分(例如:HeadModel 或 LegModel 确实是 BodyModel 的一部分)。这就是我以这种方式使用继承和嵌套模型的原因。你说的更简单具体是什么意思?简化继承,如我的“编辑”部分所示,但它仍然有意义吗?

标签: c# properties downcast


【解决方案1】:

您创建了抽象,但没有使用它们(和多态性)的力量。您可能不需要DoModelASpecificStuffDoModelBSpecificStuff 和显式向下转换,但只需要DoAnyModelStuff 并与ImportModels 一起使用相同的对象,并且仅当您需要一些具体的模型特定属性时 - 您可以使用模式匹配和is关键字:

public abstract class ImportBase
{
    public ProductionBase Production { get; set; }
    public string ImportCommonProp { get; set; } = "Common Import prop value";
}
public class ImportA : ImportBase
{
    public string ImportASpecificProp { get; set; } = "Specific Import A prop value";
}
public class ImportB : ImportBase
{
    public string ImportBSpecificProp { get; set; } = "Specific Import B prop value";
}

public abstract class ProductionBase
{
    public List<PartBase> Parts { get; set; } = new List<PartBase>();
    public string ProductionCommonProp { get; set; } = "Common Production prop value";
}
public class ProductionA : ProductionBase
{
    public string ProductionASpecificProp { get; set; } = "Specific Production A prop value";
}
public class ProductionB : ProductionBase
{
    public string ProductionBSpecificProp { get; set; } = "Specific Production B prop value";
}

public abstract class PartBase
{
    public string PartCommonProp { get; set; } = "Common Part prop value";
}
public class PartA : PartBase
{
    public string PartASpecificProp { get; set; } = "Specific Part A prop value";
}
public class PartB : PartBase
{
    public string PartBSpecificProp { get; set; } = "Specific Part B prop value";
}

所以你创建了一些 A 和 B 具体类型的 ImportModels:

void Main(string[] args)
{
    ImportBase importModelA = new ImportA
    {
        Production = new ProductionA()
        {
            Parts = new List<PartBase>()
            {
                new PartA(),
                new PartA()
            }
        }
    };
    ImportBase importModelB = new ImportB
    {
        Production = new ProductionB()
        {
            Parts = new List<PartBase>()
            {
                new PartB(),
                new PartB()
            }
        }
    };

    DoAnyModelStuff(importModelA, importModelB);

    _ = Console.ReadKey();
}

DoAnyModelStuff 中使用它们就像使用一组相同(抽象,而不是具体)对象,如果需要访问特定类型属性 - 使用模式匹配:

public void DoAnyModelStuff(params ImportBase[] importBases)
{
    foreach (var importBase in importBases)
    {
        Console.WriteLine(importBase.ImportCommonProp);

        if (importBase is ImportA importA)
            Console.WriteLine(importA.ImportASpecificProp);
        if (importBase is ImportB importB)
            Console.WriteLine(importB.ImportBSpecificProp);

        var production = importBase.Production;
        Console.WriteLine(production.ProductionCommonProp);

        if (production is ProductionA productionA)
            Console.WriteLine(productionA.ProductionASpecificProp);
        if (production is ProductionB productionB)
            Console.WriteLine(productionB.ProductionBSpecificProp);

        var parts = production.Parts;
        foreach (var part in parts)
        {
            Console.WriteLine(part.PartCommonProp);

            if (part is PartA partA)
                Console.WriteLine(partA.PartASpecificProp);
            if (part is PartB partB)
                Console.WriteLine(partB.PartBSpecificProp);
        }

        Console.WriteLine(new string('-', 25));
    }
}

样本输出:

【讨论】:

  • 你说得对,我目前没有充分利用多态性。但是,我可能是错的,但您的解决方案是否违反了开闭原则?添加新的模型类型需要每次都添加新的类类型测试,因此,修改 DoAnyModelStuff 的类而不是添加新的类型相关类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-12
  • 2021-04-07
  • 2014-07-09
  • 1970-01-01
  • 2022-06-22
  • 1970-01-01
相关资源
最近更新 更多