我做了与@Guffa 的回答类似的事情,但我没有使用为属性保存“Hook”包装器的对象,而是使用 lambdas 使用其原始值类型获取/设置属性:
大师班:
class MasterClass
{
Dictionary<string, HookObj> dict = new Dictionary<string, HookObj>();
//Data came in from an external source, see if we know what variable to assign the value to
public void receiveData(string key, string value)
{
if (dict.ContainsKey(key))
assignVal(dict[key], value);
else
throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
}
//Cast the value-string to the proper type and assign it
private void assignVal(HookObj hookobj, string value)
{
try
{
if (hookobj.theType == typeof(string))
hookobj.SetValue(value);
else if (hookobj.theType == typeof(int))
hookobj.SetValue(Int32.Parse(value));
else if (hookobj.theType == typeof(float))
hookobj.SetValue(float.Parse(value));
else
throw new NotImplementedException();
}
catch (RuntimeBinderException ex) { throw new NotImplementedException("", ex); }
catch (System.FormatException ex) { throw new NotImplementedException("", ex); }
}
protected void newHookAndAdd<T>(Action<T> setter, Func<T> getter, string name)
{
HookObj hook = new HookObj<T>(setter, getter);
dict.Add(name, hook);
}
}
HookObj
public class HookObj<T> : HookObj
{
public Action<T> setMethod { get; private set; }
public Func<T> getMethod { get; private set; }
public HookObj(Action<T> setMethod, Func<T> getMethod)
: base(typeof(T))
{
this.setMethod = setMethod;
this.getMethod = getMethod;
}
public override void SetValue(object value)
{
setMethod((T)value);
}
public override object GetValue()
{
return getMethod();
}
}
public abstract class HookObj
{
public Type theType { get; private set; }
public HookObj(Type theType)
{
this.theType = theType;
}
public abstract void SetValue(object value);
public abstract object GetValue();
}
那么你的 MasterClass 的子类:
class Attachvariable : MasterClass
{
public int UserID { get; set; }
public string Position { get; set; }
public Attachvariable()
{
hookMethod();
}
protected void hookMethod()
{
newHookAndAdd(value => UserID = value, () => UserID, "userID");
newHookAndAdd(value => Position = value, () => Position, "position");
}
}
你甚至可以设置你的钩子注册者来提供一个解析器:
class YesNoVariable : MasterClass
{
public bool YesNo { get; set; }
public YesNoVariable()
{
hookMethod();
}
protected void hookMethod()
{
newHookAndAdd(value => YesNo = value, () => YesNo, "yesno", (input) => input == "yes");
}
}
我没有完成将解析器作为可选参数添加到基本处理程序的动作,我将把它留给你,因为在这个阶段它是微不足道的。本质上,您的assignValue 将检查HookObj 是否分配了解析器,如果有,则使用它。否则,它会执行您已经拥有的相同动作。
事实上,我会让所有的钩子都使用解析器并有特定的IntHookObj、FloatHookObj 等等。 newHookAndAdd 方法本质上是一个工厂。如果用户提供他们自己的解析器,它将使用该自定义解析器创建一个HookObj。如果 T 是 int/float/string 它将实例化一个已知的 HookObj 实现,因此您不会将所有解析逻辑与分配过程混合。
编辑:我最终将使用自定义解析器的实现组合在一起,并完全放弃了 if/elseif/elseif 类型检查:
挂钩基类
public abstract class HookObj<T> : HookObj
{
public Action<T> setMethod { get; private set; }
public Func<T> getMethod { get; private set; }
protected HookObj()
: base(typeof(T))
{
}
public void SetSetter(Action<T> setMethod)
{
this.setMethod = setMethod;
}
public void SetGetter(Func<T> getMethod)
{
this.getMethod = getMethod;
}
protected abstract T Parse(string value);
public override void SetValue(string value)
{
T parsedValue = Parse(value);
setMethod(parsedValue);
}
public override object GetValue()
{
return getMethod();
}
}
public abstract class HookObj
{
public Type theType { get; private set; }
public HookObj(Type theType)
{
this.theType = theType;
}
public abstract void SetValue(string value);
public abstract object GetValue();
}
标准挂钩实现
public class StringHook : HookObj<string>
{
protected override string Parse(string value)
{
return value;
}
}
public class IntHook : HookObj<int>
{
protected override int Parse(string value)
{
return Int32.Parse(value);
}
}
public class FloatHook : HookObj<float>
{
protected override float Parse(string value)
{
return float.Parse(value);
}
}
自定义挂钩解析器处理程序
public class CustomHook<T> : HookObj<T>
{
public Func<string, T> inputParser { get; private set; }
public CustomHook(Func<string, T> parser)
{
if (parser == null)
throw new ArgumentNullException("parser");
this.inputParser = parser;
}
protected override T Parse(string value)
{
return inputParser(value);
}
}
大师班:
class MasterClass
{
Dictionary<string, HookObj> dict = new Dictionary<string, HookObj>();
//Data came in from an external source, see if we know what variable to assign the value to
public void receiveData(string key, string value)
{
if (dict.ContainsKey(key))
assignVal(dict[key], value);
else
throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
}
//Cast the value-string to the proper type and assign it
private void assignVal(HookObj hookobj, string value)
{
hookobj.SetValue(value);
}
protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, Func<string, T> inputParser)
{
var hook = new CustomHook<T>(inputParser);
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty(Action<string> setter, Func<string> getter, string name)
{
var hook = new StringHook();
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty(Action<int> setter, Func<int> getter, string name)
{
var hook = new IntHook();
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty(Action<float> setter, Func<float> getter, string name)
{
var hook = new FloatHook();
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
}
现在,MasterClass 可能需要做一些工作。具体来说,我感觉 RegisterProperty 方法(它取代了 newHookAndAdd)正在重复工作,您必须为您支持的每个“标准”挂钩类型添加条目。我确信有更好的方法可以做到这一点,但现在它提供了您的“Attachvariable”子类,而不关心使用了哪些钩子,它们知道本机支持哪些类型及其类型安全。任何原生不支持的类型,它们必须提供类型安全的解析器。
附上变量样本:
class Attachvariable : MasterClass
{
public int UserID { get; set; }
public string Position { get; set; }
public bool YesNo { get; set; }
public bool ProperBoolean { get; set; }
public Attachvariable()
{
RegisterProperties();
}
protected void RegisterProperties()
{
RegisterProperty(value => UserID = value, () => UserID, "userID");
RegisterProperty(value => Position = value, () => Position, "position");
RegisterProperty(value => YesNo = value, () => YesNo, "yesno", (input) => input == "yes");
RegisterProperty(value => ProperBoolean = value, () => ProperBoolean, "ProperBoolean", (input) => Boolean.Parse(input));
}
}
本机支持string 和int 属性;他们遇到了与他们的类型相关的RegisterProperty 重载。但是,bool 类型本身不受支持,因此它们提供了自己的解析逻辑。 “yesno”只是检查字符串是否等于“yes”。 “ProperBoolean”执行标准 Boolean.Parse。用法如下:
Attachvariable obj = new Attachvariable();
obj.receiveData("userID", "9001");
obj.receiveData("position", "Hello World!");
obj.receiveData("yesno", "yes");
obj.receiveData("ProperBoolean", "True");
Console.WriteLine(obj.UserID); //9001
Console.WriteLine(obj.Position); //"Hello World!"
Console.WriteLine(obj.YesNo); //True
Console.WriteLine(obj.ProperBoolean); //True
obj.receiveData("yesno", "something else!");
Console.WriteLine(obj.YesNo); //False
obj.receiveData("ProperBoolean", "Invalid Boolean!"); //throws exception on `Boolean.Parse` step as intended
我想过让“标准”挂钩从 CustomHook 继承,然后将解析方法向下传递,但这样您就可以创建新的标准挂钩,这些挂钩可能具有更复杂的解析逻辑,因此应该更容易阅读/实施。无论如何,我把它删掉了,如果我要在生产中使用它,我会花更多的时间来清理/改进/测试。
一个 biiiiiiiig 的优点是,它非常容易 对您的个人钩子类型实现进行单元测试。可能会重构 MasterClass 以使其更容易进行单元测试(尽管现在很简单),可能会将钩子创建者移动到单独的工厂/构建器中。
编辑:哎呀,现在只需丢弃 HookObj 基础上的 theType 字段并将其替换为接口:
public interface IHookObj
{
void SetValue(string value);
object GetValue();
}
您可以在整个过程中传播更改(HookObj<T> 不再调用传入typeof(T) 的基本构造函数,MasterClass 现在绑定到IHookObj 接口)。这意味着您可以更轻松地定义使用 他们想要的任何逻辑、解析或其他方式的钩子,并且应该更容易测试。
编辑:是的,这是一个示例实现,您的 API 的第三方使用者可以提供他们自己的钩子,可以在他们的应用程序中重用。如果他们在任何地方都使用了Person 对象,他们只需定义一个钩子,它就可以被重用:
class SomeCustomUsage : MasterClass
{
public Person SomeoneUnimportant { get; set; }
public SomeCustomUsage()
{
RegisterProperty(value => SomeoneUnimportant = value, () => SomeoneUnimportant, "SomeoneUnimportant", new PersonHook());
}
}
他们的PersonHook 是:
public class PersonHook : HookObj<Person>
{
protected override Person Parse(string value)
{
string[] parts = value.Split(',');
var person = new Person(parts[0], parts[1]);
if (person.FirstName == "Bob")
throw new Exception("You have a silly name and I don't like you.");
if (String.IsNullOrWhiteSpace(person.FirstName))
throw new Exception("No first name provided.");
if (String.IsNullOrWhiteSpace(person.LastName))
throw new Exception("No last name provided.");
return person;
}
}
为RegisterProperty 提供HookObj<T> 重载也将所有重载压缩为更简单的代码重复更少的东西(但它仍然不太感觉正确):
protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, HookObj<T> hook)
{
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, Func<string, T> inputParser)
{
var hook = new CustomHook<T>(inputParser);
RegisterProperty(setter, getter, name, hook);
}
protected void RegisterProperty(Action<string> setter, Func<string> getter, string name)
{
var hook = new StringHook();
RegisterProperty(setter, getter, name, hook);
}
protected void RegisterProperty(Action<int> setter, Func<int> getter, string name)
{
var hook = new IntHook();
RegisterProperty(setter, getter, name, hook);
}
protected void RegisterProperty(Action<float> setter, Func<float> getter, string name)
{
var hook = new FloatHook();
RegisterProperty(setter, getter, name, hook);
}
结果可能如下所示:
SomeCustomUsage customObj = new SomeCustomUsage();
customObj.receiveData("SomeoneUnimportant", "John,Doe");
Console.WriteLine(customObj.SomeoneUnimportant.LastName + ", " + customObj.SomeoneUnimportant.FirstName); //Doe, John
customObj.receiveData("SomeoneUnimportant", "Bob,Marley"); //exception: "You have a silly name and I don't like you."
customObj.receiveData("SomeoneUnimportant", ",Doe"); //exception: "No first name provided."
customObj.receiveData("SomeoneUnimportant", "John,"); //exception: "No last name provided."