【问题标题】:Parameter string to class type + id类类型 + id 的参数字符串
【发布时间】:2018-04-17 22:14:26
【问题描述】:

此处为简化报告示例:

假设我有三个不同的值对象,它们都有一个 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 方法将利用特定于对象的存储库,并在 ViewModel 中返​​回(或存储)一个 DTO,以便 View 可以访问它。因此,该方法的输入不常见(它将是三个值对象之一),并且该方法的输出也不常见(它将是包含相应表中数据的三个不同 DTO 之一) .

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

编辑#2:在我试图简化问题时,我可能走得太远了。为移动目标道歉,并且第一次没有正确表达我的问题,这不是我的意图。

在最初的问题中,我试图推断 ObjectA、B 和 C 是实际值对象(每个 DDD),但我从未明确表示过。所以我现在说,我更新了示例代码,希望能更好地加强这一点。

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

希望这可以更好地阐明问题/问题(但我觉得我当然可以做得更好,如果我有任何想法,我会继续思考并更新)。

【问题讨论】:

  • 你熟悉继承吗?您的 A、B 和 C 对象可以从单个抽象基类继承吗?级联 if 是一个经典问题,继承可以很好地解决。
  • 在原始问题中添加了一些措辞......如果有共同的属性/行为,我没有看到它(或在错误的地方寻找它)。

标签: c# asp.net-mvc oop


【解决方案1】:

为了扩展 Jay 的答案,以下是实现 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();

【讨论】:

  • 查看更新的问题...这是我试图走下去的道路,但它不符合我目前的结构方式。我的ObjectA 是一个实际值对象,仅用于表示我的域中的特定类型的 ID。我不相信它应该有行为。即使是这样,我也不认为 GetData 行为属于域,它属于应用程序服务层。
  • 我看到了扩展方法如何允许我将行为置于更合乎逻辑的位置,但我看不出这如何免除我使用凌乱的 if 语句和显式类型转换的责任。跨度>
【解决方案2】:

以下方法有用吗?

我不清楚 GetData() 方法是应该由您的对象完成还是在实例化它们的类上完成。如果您想避免 if else 阻塞,您将需要预先知道它们有哪些共同属性,以便您可以声明一个适当的接口,或者您要将获取数据的责任委托给对象本身(如我通过实现 IDataProvider 接口完成了以下工作。

我使用字典在您的各种字符串和类型本身之间进行映射(仅完成 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 实例。

【讨论】:

  • 我喜欢你对工厂模式的使用。最后,我可能会使用类似的东西,而不是我在简化示例中展示的内容。请参阅更新的问题... ObjectA、B 和 C 实际上是域中的值对象,我不相信 GetData 行为属于那里。我认为它属于应用程序服务层(我的问题所在)。
  • 我在我的答案中添加了一个编辑。看看这是否会导致任何地方。
  • 让我的大脑走上正确道路的关键是“他们可以共享一个共同的祖先类”。我让我的值对象(A、B 和 C)来自一个公共父类,然后能够消除一堆 if 语句并获得完成工作所需的代码。谢谢!
【解决方案3】:

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

【讨论】:

  • 虽然我同意使用界面,但您的其余答案可能不一定适用。毕竟这是一个简化的例子。
  • 我不知道你为什么对我使用 SubstringTryParse 感到厌烦。这个例子被简化了。类型标识符和 id 都可以长于一个字符和一个数字。我希望有一个具有单个功能的接口,可以删除所有分支。愿意启发我如何做到这一点(也许有一个适用于我的示例的示例抽象类)?
  • 你不是顾问吗?
  • @Jay - 我?我是一个正在恢复的程序程序员,正在尝试学习和应用 OOP。而且我也在我自己的项目中独自飞行(我不与任何其他程序员一起工作)。如果你是在暗示我应该“知道怎么做”,那么为什么这个很棒的网站还会存在呢?
  • 我不认为这是功能性 vs OO,同样适用于那里......这个网站的存在是为了回答问题而不是编写你的代码,如果你的问题是如何组成界面,它会被回答否则尝试代码审查堆栈交换
猜你喜欢
  • 2021-10-21
  • 2021-10-13
  • 1970-01-01
  • 2021-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多