我是一个自动化团队,负责设计电子元件的测试。我们的框架非常需要的一件事是我们的驱动程序对象的单一源点,用于工作台中的各种测试设备(现在,驱动程序对象创建非常狂野)。
基本上,我们的想法是会有一个基于配置文件构建的对象,这是所有其他测试代码根据名称字符串获取驱动程序对象所需的单个位置。我在这里称之为“DriverSource”。
问题是,这些驱动程序根本不提供类似的接口。一个可能是电源(使用“SetVoltage”和“SetCurrentLimit”等方法),而另一个可能是数字万用表(使用“ReadVoltage”或“ReadCurrent”等方法)。
我提出的最佳解决方案是使用以下声明的方法:
public object GetDriver(string name);
然后,使用我的“DriverSource”对象的测试代码将调用该方法,然后将System.Object强制转换为正确的驱动程序类型(或更准确地说,正确的驱动程序接口,如IPowerSupply)。
我认为这样的投射是可以接受的,因为无论什么测试代码即将使用这个驱动程序都更好地知道接口是什么。但我希望得到一些关于这是否是一种等待咬我的反模式的意见。任何更好的解决这个问题的模式也将受到高度赞赏。
最后一点:我认为这是显而易见的,但对于这个问题,性能基本上不是问题。在可以持续数小时的测试运行中,获取驱动程序的次数将少于100次。
答案 0 :(得分:7)
如果您已经知道了类型,并且无论如何都要转换为接口或类,那么更好的方法是将方法调用交给类型参数。
public T GetDriver<T>(string name);
然后,您可以使用工厂模式从方法中返回相应类型的对象。
public T GetDriver<T>(string name)
{
switch(typeof(T).Name)
{
case "Foo":
// Construct and return a Foo object
case "Bar":
// Construct and return a Bar object
case "Baz":
// Construct and return a Baz object
default:
return default(T);
}
}
用法:
var driver = GetDriver<Foo>(someString); // Returns a Foo object
答案 1 :(得分:2)
如果你真的想要这个通用,我会使用工厂模式。
让我们从识别类型结构开始:
public interface IDriver
{
}
public interface IPowerSupply : IDriver
{
void SetVoltage();
void SetCurrent();
}
public interface IMultimeter : IDriver
{
double MeasureVoltage();
}
您可以根据需要添加或删除。现在我们需要一种方法让工厂自动发现正确的类型并为其提供配置信息。因此,我们创建一个自定义属性:
public class DriverHandlerAttribute : Attribute
{
public Type DriverType { get; set; }
public string ConfigurationName { get; set; }
}
然后我们需要一个存储配置数据的地方。此类型可以包含您想要的任何内容,例如从配置文件加载的键/值字典:
public class Configuration
{
public string DriverName { get; set; }
public string OtherSetting { get; set; }
}
最后我们可以创建一个驱动程序。让我们创建一个IPowerSupply
:
[DriverHandler(DriverType = typeof(IPowerSupply), ConfigurationName="BaseSupply")]
public class BasePowerSupply : IPowerSupply
{
public BasePowerSupply(Configuration config) { /* ... */ }
public void SetVoltage() { /* ... */ }
public void SetCurrent() { /* ... */ }
}
重要的是它用属性修饰并且它有一个构造函数(虽然我创建工厂以便它也可以使用默认构造函数):
public static class DriverFactory
{
public static IDriver Create(Configuration config)
{
Type driverType = GetTypeForDriver(config.DriverName);
if (driverType == null) return null;
if (driverType.GetConstructor(new[] { typeof(Configuration) }) != null)
return Activator.CreateInstance(driverType, config) as IDriver;
else
return Activator.CreateInstance(driverType) as IDriver;
}
public static T Create<T>(Configuration config) where T : IDriver
{
return (T)Create(config);
}
private static Type GetTypeForDriver(string driverName)
{
var type = (from t in Assembly.GetExecutingAssembly().GetTypes()
let attrib = t.GetCustomAttribute<DriverHandlerAttribute>()
where attrib != null && attrib.ConfigurationName == driverName
select t).FirstOrDefault();
return type;
}
}
因此,要使用它,您将读入配置数据(从XML加载,从服务,文件等中读取)。然后,您可以创建如下驱动程序:
var driver = DriverFactory.Create(configuration);
或者,如果您使用的是通用方法,并且您知道配置是针对电源的,则可以调用:
var driver = DriverFactory.Create<IPowerSupply>(configuration);
当您运行测试时,您可以验证您是否获得了正确的数据,例如,在您的测试方法中:
Assert.IsTrue(driver is IPowerSupply);
Assert.IsTrue(driver is BaseSupply);
Assert.DoesWhatever(((IPowerSupply)driver).SetVoltage());
等等等等。
答案 2 :(得分:2)
我会使用这段代码:
public T GetDriver<T>(string name)
{
return ((Func<string, T>)_factories[typeof(T)])(name);
}
_factories
对象如下所示:
private Dictionary<Type, Delegate> _factories = new Dictionary<Type, Delegate>()
{
{ typeof(Foo), (Delegate)(Func<string, Foo>)(s => new Foo(s)) },
{ typeof(Bar), (Delegate)(Func<string, Bar>)(s => new Bar()) },
{ typeof(Baz), (Delegate)(Func<string, Baz>)(s => new Baz()) },
};
基本上_factories
字典包含基于传入的字符串参数创建每个对象类型的所有代码。请注意,在上面的示例中,Foo
类将s
作为构造函数参数。
然后还可以在运行时修改字典以满足您的需求,而无需重新编译代码。
我甚至更进一步。如果您定义此工厂类:
public class Factory
{
private Dictionary<Type, Delegate> _factories = new Dictionary<Type, Delegate>();
public T Build<T>(string name)
{
return ((Func<string, T>)_factories[typeof(T)])(name);
}
public void Define<T>(Func<string, T> create)
{
_factories.Add(typeof(T), create);
}
}
然后您可以编写此代码:
var drivers = new Factory();
drivers.Define(s => new Foo(s));
drivers.Define(s => new Bar());
drivers.Define(s => new Baz());
var driver = drivers.Build<Foo>("foo");
我更喜欢这样。它是强类型的,可以在运行时轻松定制。