此处简化报告示例:
假设我有三个不同的值对象都具有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
方法属于域层中的值对象。
希望这能更好地澄清问题/问题(但我觉得我当然可以做得更好,如果我提出任何建议,我会继续思考并更新。)
答案 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]