类类型的参数字符串+ id

时间:2018-04-17 22:14:26

标签: c# asp.net-mvc oop

此处简化报告示例:

假设我有三个不同的值对象都具有ID属性:

public class ObjectA() { int ID { get; set; } }
public class ObjectB() { int ID { get; set; } }
public class ObjectC() { int ID { get; set; } }

并说我想使用URL中的“ClassTypePlusID”参数:

http://www.example.com/show/A1
http://www.example.com/show/B3
http://www.example.com/show/C2

现在,在我的视图模型中,很容易提取类型(A,B或C)和ID(1,3或2)。从那里开始,不难对值对象进行水合(简化,无错误处理):

public object GetValueObject(string ClassTypePlusID)
{
    var type = ClassTypePlusID.Substring(0, 1);
    int.TryParse(ClassTypePlusID.Substring(1, 1), out var id);

    if (type == "A")
    {
        return new ValueObjectA() { ID = id };
    }
    else if (type == "B")
    {
        return new ValueObjectB() { ID = id };
    }
    else if (type == "C")
    {
        return new ValueObjectC() { ID = id };
    }
}

然后,我有三个GetData()方法来获取与该对象相关的数据(这里再次大大简化):

    private void GetData(ValueObjectA A)
    {
        // SELECT * FROM TableA WHERE ID=A.ID
    }
    private void GetData(ValueObjectB B)
    {
        // SELECT * FROM TableB WHERE ID=B.ID
    }
    private void GetData(ValueObjectC C)
    {
        // SELECT * FROM TableC WHERE ID=C.ID
    }

最后,我们得到了痛苦的来源......现在我需要将该值对象提供给方法。但是,在将对象用作参数之前,我必须(重新)将对象强制转换为正确的类型。我做不到这样的事情:

var valueObject = GetValueObject(urlParameter);
GetData(valueObject);

编译器(我认为正确)无法从对象转换为ObjectA,ObjectB或ObjectC。所以我必须为此做一个荒谬的扭曲:

    var valueObject = GetValueObject("A1");
    if (valueObject is ObjectA)
    {
        GetData((ValueObjectA)valueObject);
    }
    else if (valueObject is ObjectB)
    {
        GetData((ValueObjectB)valueObject);
    }
    else if (valueObject is ObjectC)
    {
        GetData((ValueObjectC)valueObject);
    }

为了加重伤害,我还没有完成if语句,因为我必须在视图中再次使用它们才能显示数据对象(无论哪一个)。

所以我一直在追逐我的尾巴试图找出如何在没有所有丑陋(并且难以维护)if语句的情况下做到这一点。必须有一个OOP方法来做到这一点,但我很难弄清楚它是什么。

编辑:好的,正如我所知,第一个建议是使用继承。所以,让我尝试一下,也许你可以告诉我我错过了什么。

我确实理解了继承和多态的基础知识,但我仍在学习如何有效地使用它们(正如你所看到的)...

因此,如果我要使用继承,我需要一个抽象类,它在要继承的对象之间具有共同的属性/行为。我没有看到任何共同点。我在示例中展示的GetData方法将使用特定于对象的Repository,并在ViewModel中返回(或存储)DTO,以便View可以访问它。因此该方法的输入不常见(它将是三个值对象之一),并且该方法的输出不常见(它将是包含来自相应表的数据的三个不同DTO之一)

我并不是说继承或多态不是答案(我确定是这样),我只是不知道如何利用它。

编辑#2:在我试图简化问题时,我可能已经走得太远了。为移动目标道歉而不是第一次正确地回答我的问题,这不是我的意图。

在最初的问题中,我试图推断出ObjectA,B和C是实际值对象(每个DDD),但我从来没有明确地说出来。所以我现在就说,我更新了示例代码,希望能更好地加强它。

GetData方法移动到Value Object中有一些问题。首先,我不认为它属于那里。 ValueObject的目的是描述特定类型的ID。它在我的域中的几个实体中使用。另一个问题是GetData方法在我的示例中被简化(可能太多),它实际上还有其他ID参数(也是从URL中提取的)。从那里,GetData方法将利用各种存储库来填充视图模型及其所需的数据(来自多个数据集)。所以这里的底线是我在这里的应用服务层。我不相信GetData方法属于域层中的值对象。

希望这能更好地澄清问题/问题(但我觉得我当然可以做得更好,如果我提出任何建议,我会继续思考并更新。)

3 个答案:

答案 0 :(得分:2)

要扩展周杰伦的答案,请按照以下方式实现interface

public interface IObject
{
    int ID { get; set; }
}

该接口理想地定义了您不相关的类之间的所有共同方法和属性。您已经实现了此界面,因此您只需添加如下界面:

public class ObjectA() : IObject

为了真正使这个界面变得有用,我建议您在相关课程中移动GetData方法。这样,您可以修改您的界面以包含该方法:

public interface IObject
{
    int ID { get; set; }
    void GetData();
}

完成后,您可以更改GetGetValueObject方法的签名以返回界面:

public IObject GetValueObject(string ClassTypePlusID)

然后,您可以简单地调用其GetData方法,而不是检查对象的类型:

var valueObject = GetValueObject("A1"); //var is of type IObject
ObjectA.GetData();

答案 1 :(得分:1)

以下是否有效?

我不清楚GetData()方法是应该由您的对象完成的,还是在已实例化它们的类上完成的。如果你想避免if else块,你需要先知道它们共有哪些属性,这样你就可以声明一个合适的接口,或者你要委托将数据传递给对象本身(如我在下面通过实现IDataProvider接口完成了。

我使用字典在各种字符串和Type本身之间进行映射(只做A和B,但你可以扩展它)。我使用泛型来解决你的类型转换不适,但你可能甚至不需要知道类型,因为你的调用者类,重要的是它可以GetData(),而不是类。我已经在c的测试用例中展示了这个,它可以同样拥有ObjectB的实例。

public interface IDataProvider
{
    void GetData();
}
public class ObjectA : IDataProvider
{
    public ObjectA(int id)
    {
        this.Id = id;
    }

    public int Id
    {
        get;
    }

    public void GetData()
    {
        // Get A's data
    }
}
public class ObjectB : IDataProvider
{
    public ObjectB(int id)
    {
        this.Id = id;
    }

    public int Id
    {
        get;
    }

    public void GetData()
    {
        // Get B's data
    }
}

public static class ObjectFactory
{
    private static readonly Dictionary<string, Type> typeByNameDictionary = new Dictionary<string, Type>();

    static ObjectFactory()
    {
        typeByNameDictionary.Add("A", typeof(ObjectA));
        typeByNameDictionary.Add("B", typeof(ObjectB));            
    }

    public static bool TryGetObject<T>(string classTypePlusId, out T createdObject) where T : class 
    {
        string objectName = classTypePlusId.Substring(0, 1);
        if (!int.TryParse(classTypePlusId.Substring(1, 1), out int id))
        {
            throw new ArgumentOutOfRangeException(classTypePlusId, "something meaningful");
        }

        if (!typeByNameDictionary.TryGetValue(objectName, out Type objectType))
        {
            createdObject = default(T);
            return false;
        }
        createdObject = Activator.CreateInstance(objectType, id) as T;
        return createdObject != null;
    }
}

[TestFixture]
public class Test
{
    [Test]
    public void CanCreateObjects()
    {
        ObjectA a;
        ObjectB b;
        IDataProvider c;

        Assert.That(ObjectFactory.TryGetObject("A1", out a), Is.True);
        Assert.That(a.Id, Is.EqualTo(1));
        a.GetData();

        Assert.That(ObjectFactory.TryGetObject("A1", out b), Is.False);
        Assert.That(ObjectFactory.TryGetObject("B4", out b), Is.True);
        Assert.That(b.Id, Is.EqualTo(4));
        b.GetData();            
        Assert.That(ObjectFactory.TryGetObject("A2", out c), Is.True);
        c.GetData();
    }
}

编辑1:根据以下评论:

如果您不希望服务层代码知道如何加载这些对象,那么您有两种方法。删除我建议的IDataProvider接口

1。隐藏接口背后的对象实现

如果对象都是从服务层的角度来看相同的,那么你可以声明一个类似于(例如)的界面:

public interface IObjectWithCommonProperties
{
    int Id
    {
        get;
    }

    string StoredProcName
    {
        get;
    }

    bool IsSomeOtherFact
    {
        get;
    }

            // etc
}

然后,您可以声明ObjectA,ObjectB和ObjectC以支持这些。这样,您的服务的GetData方法只接受IObjectWithCommonProperties参数。 (请选择一个比我更好的名字)并对待它们。

2。将对象GetData委托给另一个类

如果对象没有足够的共同点来处理(1)中的接口,那么这将是一种方法。拥有主服务层类所做的一切无疑是一个严重的违反单一责任原则,但你仍然可以将对象加载委托给另一个不必自己做dto的类。

public interface IObjectBuilder
{
    object GetCreateObjectWithData(string classPlusId);
}

public class ObjectABuilder : IObjectBuilder
{
    public object GetCreateObjectWithData(string classPlusId)
    {
        ObjectA loadedObject;
        ObjectFactory.TryGetObject(classPlusId, out loadedObject);

        loadedObject.SomeProperty = "someValue"; // etc
        return loadedObject;
    }
}

为每种类型的对象构造一个合适的IObjectBuilder(如果95%相同,它们可以共享一个共同的祖先类)。然后在对象工厂中注册它,以便对于给定的classPlusId,返回适当的IObjectBuilder实现(而不是对象本身);您的服务层只能在接口变量上使用GetCreateObjectWithData,而无需关心它将做什么。这也极大地简化了服务层的测试,因为您可以用模拟替换这些IObjectBuilder实例。

答案 2 :(得分:0)

为什么你没有一个你可以坚持的接口,然后只有一个函数并删除所有的分支... int.parse与子字符串一起是肤浅的,并且是分配这么小的可怕做法短期堆对象...您只需要字符串[0]的字符串和Id ...

的字符串[1]