我有一个方法接受一个简单的类对象并构建一个API调用中使用的URL。我希望这个方法能够处理/接受相似但具有不同属性的不同类类型。
public class ClientData
{
public string Name {get; set;}
public string Email {get; set;}
...
}
public class PaymentData
{
public decimal PaymentAmount {get; set;}
public string Description {get; set;}
...
}
以下是两个示例方法。如你所见,它们非常相似。将这些实现为接受不同参数的不同方法还是可以编写一个可以处理参数对象差异的方法更好?
public string BuildApiCall(ClientData clientDataObject)
{
StringBuilder sb = new StringBuilder();
sb.Append("http://mytestapi.com/");
sb.append("name=" + clientDataObject.Name);
sb.append("email=" + clientDataObject.Email);
return sb.ToString();
}
public string BuildApiCall(PaymentData paymentDataObject)
{
StringBuilder sb = new StringBuilder();
sb.Append("http://mytestapi.com/");
sb.append("payment=" + paymentDataObject.PaymentAmount );
sb.append("description=" + paymentDataObject.Description );
return sb.ToString();
}
答案 0 :(得分:7)
基本上,您的问题是根据提供的API(可能已修复)为您的类创建自定义序列化程序。
尽可能地向separate concerns,此功能通常与您的实体类分开实现,尽可能将它们(如果可能的话)保留为POCO或与序列化无关的哑DTO。因此,就像您使用XmlSerializer
或DataContractSerializer
将类序列化为XML或Protobuf.NET将其序列化为协议缓冲区一样,可以说最常用的方法是创建自己的序列化程序。
当然,正如您在日常编程中遇到的所有其他问题一样,您需要权衡潜在的收益并决定您希望投入多少精力进行重构。如果您的案例数量很少,那么没有人会受到一些复制/粘贴硬编码方法的伤害,类似于您现在正在做的事情。此外,如果这只是一个小小的“宠物项目”,那么您可能会认为您不想浪费时间在尝试重构为更通用的解决方案(您可能永远不会再需要)时可能遇到的潜在问题。
但是,如果您做选择花一些时间编写序列化程序,那么您很快就会注意到大多数序列化框架都试图依赖约定进行序列化尽可能。换句话说,如果你的班级是:
public class ClientData
{
public string Name { get; set; }
public string Email { get; set; }
}
然后XmlSerializer
将完成以下任何配置:
<ClientData>
<Name>...</Name>
<Email>...</Email>
</ClientData>
拥有一个只会为该对象吐出?name=...&email=...
的课程真的很酷,绝对没有额外的工作。如果可行,那么您所拥有的课程不仅可以删除现有代码的重复,还可以为以后的API扩展节省时间。
因此,如果您是基于API编写类,那么尽可能将属性命名为API成员(并使用基于约定的序列化)可能是有意义的,但仍然保持开放以便能够处理分别为几个边缘案例。
public class ClientData
{
public string Name {get; set;}
public string Email {get; set;}
}
// customer really insisted that the property is
// named `PaymentAmount` as opposed to simply `Amount`,
// so we'll add a custom attribute here
public class PaymentData
{
[MyApiName("payment")]
public decimal PaymentAmount {get; set;}
public string Description {get; set;}
}
MyApiName
属性非常简单,只接受一个字符串参数:
public class MyApiNameAttribute : Attribute
{
private readonly string _name;
public string Name
{ get { return _name; } }
public MyApiNameAttribute(string name)
{ _name = name; }
}
有了这个,我们现在可以使用一些反射来呈现查询:
public static string Serialize(object obj)
{
var sb = new StringBuilder();
foreach (var p in obj.GetType().GetProperties())
{
// default key name is the lowercase property name
var key = p.Name.ToLowerInvariant();
// we need to UrlEncode all values passed to an url
var value = Uri.EscapeDataString(p.GetValue(obj, null).ToString());
// if custom attribute is specified, use that value instead
var attr = p
.GetCustomAttributes(typeof(MyApiNameAttribute), false)
.FirstOrDefault() as MyApiNameAttribute;
if (attr != null)
key = attr.Name;
sb.AppendFormat(
System.Globalization.CultureInfo.InvariantCulture,
"{0}={1}&",
key, value);
}
// trim trailing ampersand
if (sb.Length > 0 && sb[sb.Length - 1] == '&')
sb.Length--;
return sb.ToString();
}
用法:
var payment = new PaymentData()
{
Description = "some stuff",
PaymentAmount = 50.0m
};
// this will produce "payment=50.0&description=some%20stuff"
var query = MyApiSerializer.Serialize(payment)
正如评论中所指出的,反思的力量确实会导致性能下降。在大多数情况下,这应该不是很重要。在这种情况下,如果您将构建查询字符串的成本(可能在10微秒的范围内)与执行HTTP请求的成本进行比较,您会发现它几乎可以忽略不计。
但是,如果您决定要进行优化,则可以在分析完成后,通过更改caching property information甚至compiling delegates完成所有工作的单一方法,轻松完成此操作。 。分离关注点很好;重复的代码很难优化。
答案 1 :(得分:4)
定义界面:
public interface IWhatsit
{
string ToApiString();
}
现在让您的数据对象实现它。 ToApiString
应返回此特定对象的查询字符串部分:
public class ClientData : IWhatsit
{
public string Name {get; set;}
public string Email {get; set;}
...
public string ToApiString()
{
// Do whatever you need here - use a string builder if you want
return string.Format("Name={0}&Email={1}",Name,Email);
}
}
现在您可以使用一种方法进行API调用:
public string BuildApiCall(IWhatsit thing)
{
StringBuilder sb = new StringBuilder();
sb.Append("http://mytestapi.com/");
sb.append(thing.ToApiString());
return sb.ToString();
}
注意:如果您愿意,可以在界面中使用属性而不是方法。
另一种方法是使用抽象基类并从中继承。然后你可以做这样的事情:
public abstract class BaseData
{
protected abstract string ToApiString();
public string BuildApiCall()
{
StringBuilder sb = new StringBuilder();
sb.Append("http://mytestapi.com/");
sb.append(ToApiString());
return sb.ToString();
}
}
然后每个班级看起来像这样:
public class ClientData : BaseData
{
public string Name {get; set;}
public string Email {get; set;}
...
protected override string ToApiString()
{
// Do whatever you need here - use a string builder if you want
return string.Format("Name={0}&Email={1}",Name,Email);
}
}
它允许您将BuildApiCall
放入类本身并具有基本实现。当然,如果您确实需要BuildApiCall
在这些类之外,那么您可以这样做。它只需要BaseData
,您必须公开ToApiString
而不是保护。