我做了类似于@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);
}
}
钩子对象
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
and属性;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
以使单元测试更容易(尽管现在很简单),也许将钩子创建者移动到单独的工厂/构建器中。
编辑:哎呀,现在只需扔掉底座theType
上的字段HookObj
并用接口替换它:
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;
}
}
提供HookObj<T>
重载也将所有重载折叠为更简单的代码重复更少的东西(但仍然感觉RegisterProperty
不太对劲):
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."