强相关类和类型安全抽象

时间:2016-03-18 15:54:55

标签: c#

我正在设计一个适用于多个数据源的系统。这些数据源具有可在创建时对其进行某些检查的标识符。它允许获取历史和实时数据。我目前的抽象涉及三个基类。

DataSource类负责连接服务并维护该数据源的深奥内容,包括如何根据具体情况打开和关闭连接以及线程安全问题。

DataContext类负责获取哪些值,无论是实时的还是历史的,以及在什么日期等等,可能还有其他上下文,您可能希望混合历史日期(参数化)以及其他因素,这就是我想要使用的原因多态性来实现这一目标。

Identifier类负责解析字符串并对正则表达式进行验证,以确保传入的字符串标识符在某种程度上至少有效。它也用于类型安全,因为不允许将一个数据源的标识符传递到另一个数据源。

参见下面的示例代码;

public class DataSource
{
    // base class for all data sources
    // maintains connections opening and closing plus 
    // thread safety concerns
}
public class FooDataSource : DataSource { }

public class BarDataSource : DataSource { }

public abstract class Identifier
{
    public string Value { get; internal set; }

    public Identifier(string value)
    {
        Value = value; 
    }
}

public class FooIdentifier : Identifier
{
    public FooIdentifier(string value) : base(value)
    {
        // checks go here on the string that comes in 
        // specific to the foo data source
    }
}

public class BarIdentifier : Identifier
{
    public BarIdentifier(string value) : base(value)
    {
        // checks on the string values that come in for the Bar 
        // source 
    }
}


public abstract class DataContext<TIdentifier> where TIdentifier : Identifier
{
    public abstract double GetValue(TIdentifier id); 
}

public abstract class FooDataContext : DataContext<FooIdentifier> { }

public abstract class BarDataContext : DataContext<BarIdentifier> { }

public class FooRealTimeDataContext : FooDataContext
{
    public override double GetValue(FooIdentifier id)
    {
        // real implementation here 
        return -1; 
    }
}

public class BarRealTimeDataContext : BarDataContext
{
    public override double GetValue(BarIdentifier id)
    {
        // real implementation here
        return -10; 
    }
}


[TestFixture]
public static class TestMe
{
    [Test]
    public static void MyTest()
    {
        // create the data context (to get data from) 
        var ctx = new FooRealTimeDataContext();

        ctx.GetValue(new FooIdentifier("onetuhoenthuo")); // compiles (good)
        // ctx.GetValue(new BarIdentifier("noetuhneoth")); // does not compile (also good)
    }
}

问题(最后)是如何创建一个实际遵循OOP主体来填写以下类shell的类?

public class UniversalRealTimeDataSource : DataSource<Identifier> {
    public double GetValue(Identifier id) {
        // there would have to be code in here that says "if(id is FooIdentifier) ... else ... 
        // which is (IMO) an anti-pattern so, how to avoid this? 
    }
}

编辑:我一直在努力保持编译时类型的安全保证。对于一些if(!(id is FooIdentifier))抛出异常类型的代码,这将是相当简单的,但我想让它在编译时不可能发生。

1 个答案:

答案 0 :(得分:0)

我的最终解决方案在编译时类型安全方面有点妥协。标识符从数据源菜单(通用数据源)中选择自己的数据源。除非程序员错误地使用代码,否则这可以防止运行时错误。很多东西可能都是私有的,公共层将包含所有DataContext和Identifier子类。

public abstract class DataSource
{
    // base class for all data sources
    // maintains connections opening and closing plus 
    // thread safety concerns

    // these classes will most likely be private
    // maybe even within the universal data source class as inner classes

    // THE TWO METHODS BELOW ARE ONLY TO BE CALLED FROM WITHIN THE UniversalDataSource CLASS
    public abstract double GetRealTimeValue(Identifier id);

    public abstract double GetHistoricalValue(Identifier id, DateTime asof); 
}
public class FooDataSource : DataSource {
    public override double GetRealTimeValue(Identifier id)
    {
        return -1;
        // real implementation here, must be identifier type upcasting with runtime check
    }

    public override double GetHistoricalValue(Identifier id, DateTime asof)
    {
        return -2; 
        // real implementation here, must be identifier type upcasting with runtime check
    }
}

public class BarDataSource : DataSource {
    public override double GetRealTimeValue(Identifier id)
    {
        return -3; 
        // real implementation here, must be identifier type upcasting with runtime check
    }

    public override double GetHistoricalValue(Identifier id, DateTime asof)
    {
        return -4; 
        // real implementation here, must be identifier type upcasting with runtime check
    }
}

/// <summary>
/// holds initialized references to all possible data sources
/// </summary>
public class UniversalDataSource 
{

    public FooDataSource FooDS { get; internal set; }
    public BarDataSource BarDS { get; internal set; }

    public UniversalDataSource(FooDataSource fooDs, BarDataSource barDs)
    {
        this.FooDS = fooDs;
        this.BarDS = barDs;
    }

    public double GetRealTimeValue(Identifier id)
    {
        var specificDS = id.GetDataSource(this);
        return specificDS.GetRealTimeValue(id); 
    }

    public double GetHistoricalValue(Identifier id, DateTime asof)
    {
        var specificDS = id.GetDataSource(this);
        return specificDS.GetHistoricalValue(id, asof); 
    }
}

public abstract class Identifier
{
    public string Value { get; internal set; }

    public Identifier(string value)
    {
        Value = value; 
    }

    /// <summary>
    /// returns the appropriate data source for THIS kind of identifier (abstractly)
    /// </summary>
    /// <param name="universalDataSource"></param>
    /// <returns></returns>
    public abstract DataSource GetDataSource(UniversalDataSource universalDataSource); 
}

public class FooIdentifier : Identifier
{
    public FooIdentifier(string value) : base(value)
    {
        // checks go here on the string that comes in 
        // specific to the foo data source
    }

    public override DataSource GetDataSource(UniversalDataSource universalDataSource)
    {
        return universalDataSource.FooDS; 
    }
}

public class BarIdentifier : Identifier
{
    public BarIdentifier(string value) : base(value)
    {
        // checks on the string values that come in for the Bar 
        // source 
    }

    public override DataSource GetDataSource(UniversalDataSource universalDataSource)
    {
        return universalDataSource.BarDS; 
    }
}


public abstract class DataContext
{
    public UniversalDataSource DataSource { get; internal set; }

    protected DataContext(UniversalDataSource dataSource)
    {
        DataSource = dataSource;
    }

    public abstract double GetValue(Identifier id); 
}

public class RealTimeDataContext : DataContext {

    public RealTimeDataContext(UniversalDataSource dataSource) : base(dataSource)
    {
    }

    public override double GetValue(Identifier id)
    {
        return DataSource.GetRealTimeValue(id); 
    }
}

public class HistoricalDataContext : DataContext {

    public DateTime AsOf { get; internal set; }

    public HistoricalDataContext(UniversalDataSource dataSource, DateTime asof) : base(dataSource)
    {
        AsOf = asof; 
    }

    public override double GetValue(Identifier id)
    {
        return DataSource.GetHistoricalValue(id, AsOf); 
    }

}

[TestFixture]
public static class TestMe
{
    [Test]
    public static void MyTest()
    {
        // create the data context (to get data from) 
        var ds = new UniversalDataSource(
            new FooDataSource(),
            new BarDataSource()
            );

        var realTimeDataContext = new RealTimeDataContext(ds);
        var historicalDataContext = new HistoricalDataContext(ds, DateTime.MinValue);

        var fooId = new FooIdentifier("onetuhoenthuo");
        var barId = new BarIdentifier("onetuhoenthuo"); 

        // testing dispatch
        Assert.AreEqual(-1, realTimeDataContext.GetValue(fooId));
        Assert.AreEqual(-2, historicalDataContext.GetValue(fooId));
        Assert.AreEqual(-3, realTimeDataContext.GetValue(barId));
        Assert.AreEqual(-4, historicalDataContext.GetValue(barId)); 

    }
}

也许这个解决方案可以清除我从一开始就尝试做的事情。数据上下文类甚至存在的原因是由于处理财务数据,周末和假日可能导致NA值和不同标识符的混洗日期在历史数据请求设置中可能有意义。这些策略将通过覆盖GetValue方法以多态方式实现。