1

基类MasterClass包含一个包含string键和HookObj值的 Dictionary,其中HookObj包含(来自派生类)变量 Type,并引用它的 get/set 方法。

现在,当基类MasterClass从某个源接收数据时,它将像这样转换/分配它:

//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.setMethod(value);
        else if (hookobj.theType == typeof(int))
            hookobj.setMethod(Int32.Parse(value));
        else if (hookobj.theType == typeof(float))
            hookobj.setMethod(float.Parse(value));
        else
            throw new NotImplementedException();
    }
    catch (RuntimeBinderException ex) { throw new NotImplementedException("", ex); }
    catch (System.FormatException ex) { throw new NotImplementedException("", ex); }
}

存在HookObj

internal class HookObj
{
    public Type theType { get; private set; }
    public Action<dynamic> setMethod { get; private set; }
    public Func<dynamic> getMethod { get; private set; }

    public HookObj(Type theType, Action<dynamic> setMethod, Func<dynamic> getMethod)
    {
        this.theType = theType;
        this.setMethod = setMethod;
        this.getMethod = getMethod;
    }
}

使用 getter 方法不需要解释。所以现在让我们看看这对最终用户来说有多混乱:

class Attachvariable : MasterClass
{
    public Attachvariable(int id) : base(id) { }
    public Attachvariable(int userID, string position)
        : base()
    {
        this.userID = userID;
        this.position = position;
    }

    int userID;
    string position;

    protected override void hookMethod()
    {
        newHookAndAdd(typeof(int), set_userID, get_userID, "userID").
        newHookAndAdd(typeof(string), set_position, get_position, "position");
    }

    public void set_userID(dynamic theVal)
    {
        userID = theVal;
    }
    public void set_position(dynamic theVal)
    {
        position = theVal;
    }

    public dynamic get_userID()
    {
        return userID;
    }
    public dynamic get_position()
    {
        return position;
    }
}

我希望它更像这样:

protected override void hookMethod()
{
    newHookAndAdd(ref userID, "userID");
    //etc
}

但是似乎无法存储参考以供以后使用..有什么方法可以使这对用户更加友好?我猜每个变量创建两个函数会变得非常混乱。

4

3 回答 3

1

您可以创建一个泛型Hook<T>类来保存该值,并为其创建一个抽象Hook基类来保存该类型,以便MasterClass可以在不知道钩子的实际类型的情况下获取类型:

public class MasterClass {

  private Dictionary<string, Hook> _dict;

  public Hook<T> AddHook<T>(string name, T value){
    Hook<T> hook = new Hook<T>(value);
    _dict.Add(name, hook);
    return hook;
  }

  public void receiveData(string key, string value) {
    Hook hook;
    if (_dict.TryGetValue(key, out hook)) {
      if (hook._type == typeof(string)) {
        (hook as Hook<string>).Value = value;
      } else if (hook._type == typeof(int)) {
        (hook as Hook<int>).Value = Int32.Parse(value);
      } else {
        throw new NotImplementedException(); // type not found
      }
    } else {
      throw new NotImplementedException(); // name not found
    }
  }

}

public abstract class Hook {
  internal Type _type;
  internal Hook(Type type) {
    _type = type;
  }
}

public class Hook<T> : Hook {
  public T Value { get; set; }
  public Hook(T value) : base(typeof(T)){
    Value = value;
  }
}

现在用户可以创建Hook<T>对象来保存这些值:

class Attachvariable : MasterClass {

  private Hook<int> userId;
  private Hook<string> position;

  private Attachvariable() : base() {
    userId = AddHook("userID", 0);
    position = AddHook("position", String.Empty);
  }

  public Attachvariable(int id, string pos) : this() {
    userId.Value = id;
    position.Value = pos;
  }

}
于 2012-10-02T22:23:45.407 回答
1

我做了类似于@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));
    }
}

原生支持stringand属性;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."
于 2012-10-02T22:38:05.153 回答
0

为什么所有的烟雾和镜子?像这样更直接的事情有什么问题?

建议一

public abstract class BaseClass
{
    protected virtual int UserId { get; set; }
}

public class ChildClass : BaseClass
{
    private int _userId;

    protected override int UserId
    {
        get { return _userId; }
        set { _userId = value; }
    }
}

建议二

public abstract class BaseClass
{
    protected readonly Dictionary<string, object> Values = new Dictionary<string, object>();
}

public class ChildClass : BaseClass
{
    public ChildClass()
    {
        Values["UserID"] = 123;
        Values["Foo"] = "Bar";
    }
}
于 2012-10-02T22:13:15.503 回答