【问题标题】:Implement a dictionary of classes for a pseudo class factory为伪类工厂实现类字典
【发布时间】:2014-08-07 23:27:11
【问题描述】:

我正在尝试建立一个这样的类工厂:

public class Toyota() {};
public class Bmw() {};
public class Mercedes() {};

public class BrandFactory
{
    private Dictionary<string, object> _Brands = new Dictionary<string, object> {
        {"Toyota", Toyota},
        {"Bmw", Bmw}, 
        {"Mercedes", Mercedes}
    }

   public object GetBrand(string brandName)
   {
       return = BusinessManagers[brandName].Invoke;
   }
}

这是一个想法,但它不起作用 - 我什至无法编译上面的代码,因为 Dictionary 无法将 'object' 与该函数相关联。我也试过Func&lt;&gt;,但在这种情况下,它需要以前的类型。

所以,这是我的问题: 这是实现这个“伪工厂”的正确方法吗?代码来自example code的示例

如果是这样,上面的代码中需要修复什么?

我之所以这么问是因为我需要基于从使用 Ajax 的客户端应用程序接收到的字符串创建一个新对象。喜欢:

AjaxCall -&gt; String Containing Object Name -&gt; Object Inoke -&gt; Method Call -&gt; Send result back to client

方法调用是所有Brand 实现的标准。

请问有人可以帮我吗?

谢谢。

【问题讨论】:

    标签: c# factory


    【解决方案1】:

    您有多种选择。如果您最终使用Dictionary,我建议使其不区分大小写并注意避免使用KeyNotFoundExceptions。

    public class CaseInsensitiveStringComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.Compare(x, y, ignoreCase: true);
        }
    }
    

    第一个选项是使用Dictionary&lt;string, Func&lt;object&gt;&gt;

    private IDictionary<string, Func<object>> _Brands 
        = new Dictionary<string, Func<object>> (new CaseInsensitiveStringComparer())
    {
        {"Toyota", () => new Toyota() },
        {"BMW", () => new Bmw() }, 
        {"Mercedes", () => Mercedes() }
    };
    
    public object GetBrand(string brandName)
    {
        Func<object> func;
        return _Brands.TryGetValue(brandName, out func)
            ? func() // invoking the delegate creates the instance of the brand object
            : null;  // brandName was not in the dictionary
    }
    

    第二个选项是使用Activator。您可以使用Dictionary&lt;string,Type&gt;,但如果您的类型名称与字符串匹配,则可能没有必要(请参阅下面的注释)。

    public object GetBrand(string brandName)
    {
        Type type;
        return  _Brands.TryGetValue(brandName, out type)
            ? Activator.CreateInstance(type) // activator invokes a parameterless constructor
            : null; // brandName was not in the dictionary
    }
    
    // vs.
    
    return Activator.CreateInstance(null, brandName).Unwrap();
    // Case sensitivity would be an issue here.
    // Security could be an issue here.
    // Creating objects based directly off of user input means any class 
    // from any referenced assembly could be created if a hacker can learn
    // out the namespaces and class names.
    

    第三种选择是使用 IoC 容器进行解析。这为您提供了生命周期管理的一些灵活性。

    第二种方法目前假定一个无参数的构造函数,而第一种和第三种方法将允许不同的构造函数签名。

    在所有情况下,结果都是简单的object,这使得这种方法的实用性有限。如果所有这些“品牌”类都可以共享一个公共接口,那么您可以使用IBrandDictionary 中的任何内容作为返回类型。

    我正在检查错误数据(不在Dictionary 中的值)并返回null;如果这对您的用例更有意义,您可以选择抛出 Exception。*

    【讨论】:

    • 是的。我想在 GetBrand 上获取给定类型的新对象。我有很多类,这就是为什么我想把它保存在字典中以避免冗长的 switch 语句。
    • AS 表示反对票和 cmets - 有没有更好的方法来做到这一点?
    • @Mendez string 来自哪里?为什么需要它?
    • 字符串通过 Ajax 调用来自客户端应用程序。客户端将“Bmw”作为字符串发送,我需要创建类并调用列表方法并返回结果。
    【解决方案2】:

    你根本不需要字典:

    public class DynamicFactory<T>  
    {
        public static T Create(string className)
        {
            Type t = typeof(T);
            return (T)Activator.CreateInstance(
                        t.Assembly.FullName, 
                        t.Namespace + "." + className
                      ).Unwrap();
        }
    }
    namespace Brands
    {
        public class CarBrand { }
    
        // The brands should be in the same namespace and assembly with CarBrand
        // and should inherit from CarBrand
        public class Toyota : CarBrand { };
        public class Bmw : CarBrand { };
        public class Mercedes : CarBrand { };
    
        public class Titanic { } // this one is not CarBrand
    
        class BrandFactory: DynamicFactory<CarBrand> { }
    
        // Below are unit tests using NUnit
    
        namespace BrandFactorySpecification 
        {
            static class Create
            {
                [TestCase("Toyota", Result = typeof(Toyota))]
                [TestCase("Bmw", Result = typeof(Bmw))]
                [TestCase("Mercedes", Result = typeof(Mercedes))]
                [TestCase("Titanic", ExpectedException = typeof(InvalidCastException))]
                [TestCase("unknown", ExpectedException = typeof(TypeLoadException))]
                [TestCase("String", ExpectedException = typeof(TypeLoadException))]
                [TestCase("System.String", ExpectedException = typeof(TypeLoadException))]
                [TestCase("ACarBrandFromAnotherNamespace", 
                              ExpectedException = typeof(TypeLoadException))]
                [TestCase("AnotherNamespace.ACarBrandFromAnotherNamespace",
                              ExpectedException = typeof(TypeLoadException))]
                //
                public static Type ShouldReturnCorrectType(string brandName)
                {
                    return BrandFactory.Create(brandName).GetType();
                }
    
                [Test]
                public static void ForTitanic()
                {
                    DynamicFactory<Titanic>.Create("Titanic")
                        .ShouldBeType(typeof(Titanic));
                }
            }
    
            namespace AnotherNamespace
            {
                public class ACarBrandFromAnotherNamespace : CarBrand { };
            }
        }
    }
    

    更新:在以下方面对代码进行了改进:

    • 修复了 cmets 中提到的安全问题
    • 提高了灵活性
      • 一个新的通用 class DynamicFactory&lt;T&gt; 现在可以在其他地方重复使用
      • Brands 可以位于其他命名空间和程序集然后 BrandFactory
    • 添加了单元测试作为用法和规范的示例(使用它们需要的 NUnit

    【讨论】:

    • 确实,如果类名与用户输入匹配,Activator 可以这样使用。我认为从安全角度来看,Dictionary 方法更优越,因为输入必须是已知值。使用这种方法,来自任何地方的 AJAX 调用可能会导致其他非预期的类被实例化。对命名空间进行硬编码确实有帮助,并且可以通过将所有“品牌”类放入它们自己的专用命名空间来进一步缓解这种情况。
    • 这不起作用,因为字符串是"BMW",而类名是Bmw。感谢您编辑我的帖子顺便说一句:)。
    • 我确实有一个 Brand 命名空间,伪工厂就在其中。比字典方法更容易。谢谢。
    • 感谢 AlexD。在我的代码中将宝马更正为宝马。 Ajax 会以正确的大小写调用它。
    • 另一个考虑因素是返回的值必须在此工作之前稍微调整一下,因为某些品牌的空格和字符不能在类名中。 Mercedes-Benz, Aston Martin, Land Rover.
    【解决方案3】:

    这根本不是工厂的运作方式。首先,您需要一个可以作为汽车模型父级的超类:

    public class CarModel() {};
    public class Toyota() : CarModel {};
    public class Bmw() : CarModel  {};
    public class Mercedes() : CarModel  {};
    

    现在您可以创建一个返回正确模型的工厂:

    public class BrandFactory
    {
        public T GetBrand<T>() where T : CarModel
        { 
            return new T(); 
        }
    }
    

    现在,当你想创建一个对象时,它很简单:

    var factory = new BrandFactory();
    var bmw = factory.GetBrand<Bmw>();
    

    【讨论】:

    • 你需要一个新的 T 约束
    • 好的,但是如何将 T 作为字符串传递,而不是固定类型?在我的逻辑中,宝马作为一个字符串出现在我面前,而不是一个类型。
    • ...除了 OP 想要从字符串创建实例。
    • 如果没有 new() 约束,您的通用版本将无法工作,其中 T : new()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-27
    • 2016-12-07
    • 1970-01-01
    • 1970-01-01
    • 2011-01-27
    • 2016-03-31
    相关资源
    最近更新 更多