在这里寻找一些设计建议。我已经碰到过几次这个问题了,直到现在我的方法还是不够完善。
例如,我要创建一个名为 GetConversation 的方法。现在,无论您为其赋予 id 还是其他 address 参数, GetConversation 都将返回相同的数据。只是一个偏好问题,或者您拥有的任何可用数据都会使它更加灵活和易于使用。很好,但是我遇到的问题是这两个都是字符串参数。理想情况下,看起来像这样,
public object GetConversation(string id)
{
// Get Conversation by id
}
public object GetConversation(string address)
{
// Get Conversation by address
}
现在,由于模棱两可,这当然会失败。当用户深入到 GetConversation 时,编译器不知道要访问哪个功能。
这将我带到了第二种方法,即灵活的解决方案,
public object GetConversation(string id, string address)
{
// Get Conversation by id or address
if (id != null)
{
// Get by id
}
else if (address != null)
{
// Get by address
}
return null;
}
但是现在我开始倾向于总是先按ID搜索。不一定是一件坏事,但我觉得这不是最有凝聚力的解决方案,我必须在我的支票之外明确设置两个支票都失败的收益。
对更好的方法有何想法?
答案 0 :(得分:4)
在这种特定情况下,我将方法重命名为GetConversationById
和GetConversationByAddress
,以避免产生歧义。
如果情况稍有不同,并且这些方法是具有不同实现的真正的重载,那么我将使用strategy pattern和abstract factory来创建策略。
答案 1 :(得分:2)
您可能希望考虑的另一种方法是引入新类型 1 。因此,您将拥有Address
类型和Id
类型。
至少,您希望这些新类型支持(显式)到string
的转换,并实现所有期望的相等和比较函数 2 。然后,您可以将这些类型用于此功能,并且1)允许使用,因为现在这些类型有所不同,并且2)现在可以清楚地看到调用方如何弄清楚他们正在调用哪个类型:
var conv = thing.GetConversation((Address)"here");
现在,如果这是您唯一使用这些类型的地方,那么似乎不值得付出努力-但是这些类型还有更多用途吗?除了Id
以外,Address
或string
还具有更多的结构吗? (当然,如果事实证明Address
必须始终是有效的Uri
,那么事实证明我们并不需要做所有这些工作,因为有人已经写过该类给我们)。如果是这样,我们可以考虑使用静态TryParse
方法来允许用户安全地测试字符串。
您可以使用这些类型的场所越多,当期望id
时偶然通过address
的次数就越少,反之亦然。
您还可以考虑希望通过包装器公开的基础类型的哪些操作。当然,串联和分割 strings 是有意义的,但是将两个id
或两个address
串联在一起是否有意义? (或更奇怪的是,将id
和address
连接在一起?)。因此,您只公开了对这些新类型有意义的操作,并且正在引导消费者减少可能的滥用。通过将显式运算符提供回string
,如果消费者确实确实需要做一些您认为没有用的操作,那么总会有一个“释放阀”。
1 引入此类包装程序会产生一些开销。因此,我不建议在热路径上对性能敏感的内容执行此操作,但是在大多数情况下,这将不适用。
2 是的,编写所有常用样板来实现IEquatable<T>
和IComparable<T>
确实很繁琐,但是您可以在VS中对此进行摘要。
答案 2 :(得分:1)
很明显,您不能重载相同签名的方法。
如果您要编译时类型安全,我建议使用这两种方法之一。
(1)
public object GetConversationById(string id)
{
// Get by id
}
public object GetConversationByAddress(string address)
{
// Get by address
}
(2)
public object GetConversation(ConversationSource source, string parameter)
{
switch (source)
{
case ConversationSource.Id :
// Get by id
break;
case ConversationSource.Address :
// Get by address
break;
}
return null;
}
public enum ConversationSource
{
Id,
Address,
}
您将像这样使用它们:
(1)
var conversation1 = GetConversationById("A123");
var conversation2 = GetConversationByAddress("1 Somewhere St");
(2)
var conversation1 = GetConversation(ConversationSource.Id, "A123");
var conversation2 = GetConversation(ConversationSource.Address, "1 Somewhere St");
对我来说,选项(1)更干净。
答案 3 :(得分:0)
这取决于您的设计以及如何存储对话(列表,数据库,其他服务等)以及所使用的内容,但是您可以定义一个“通用”方法来通过使用谓词({{ 1}})作为参数。
Func<Conversation>
因此该方法的调用者可以按以下方式使用它
private List<Conversation> _conversations;
public Conversation FindConversation(Func<Conversation> predicate) {
return _conversations.FirstOrDefault(predicate);
}
然后,您只有一种方法可以进行对话,并且如果需要通过其他参数查找对话,则不需要定义其他方法。
如果将转换存储在数据库中,则应使用FindConversation(p=>p.Id == 123);
//or
FindConversation(p=>p.Address == "street");
//or
FindConversation(p=>p.Id = 123 && p.Address == "street");
作为谓词,以便数据库提供程序(IQueryable)可以将谓词转换为相应的查询语句(sql等)
答案 4 :(得分:0)
您可以选择Switch Case
。它将同时适用于两种以上类型。
public object GetConversation(string param, string value)
{
switch(param)
{
case "id":
// ...
break;
case "address":
// ...
break;
default:
break;
}
return null;
}
GetConversation("id", id);
GetConversation("address", address);