在数据分析和商业智能领域,OLAP(在线分析处理)是一种重要的技术,它允许用户从多个维度对数据进行分析。然而,现有的OLAP实现往往在处理复杂场景时显得不够灵活。本文将介绍一种面向对象的OLAP标签系统,它通过为实体赋予不同的标签值,实现了对不同实体状态的同时记忆和灵活切换。
在尝试寻找面向对象OLAP的资料时,发现相关的文档和资源非常有限。例如,在亚马逊网站上搜索“面向对象OLAP”时,只能找到21个相关项目,而且这些项目的文档质量参差不齐。
本文中展示的代码仅用于说明目的,不期望它是完美的。实际上,预计会有更好的实现方式。
假设需要开发一个应用程序,它根据不同实体的特定值进行计算。这些值可以是真实的,比如支付的金额或销售的商品数量,也可以是虚构的。虚构的值可以有多种类型,比如“最坏情况”或“所有交易只使用一个银行”等。有些组合是计划内的,即根据它们做出预期;其他组合则作为选择最优解所需的原材料。无论如何,OLAP“立方体”应该是灵活的。
简单来说,每个可以为建模目的而改变的实体都需要同时记住不同的状态。为了方便用户,用户可以选择特定实体的特定状态,并查看它如何影响结果。作为解决方案,建议(目前)使用“标签”,这些标签将是访问特定实体状态的标记。
首先,声明一个实体的接口:
public interface IBonus
{
double Amount { get; set; }
DateTime Date { get; set; }
}
这是一个简单的例子:在特定日期,一个人将收到多少(假设是美元)的奖金。
现在,为什么这个人不是接口的一部分?为什么接口看起来不像这样:
public interface IBonusNotImplemented
{
double Amount { get; set; }
DateTime Date { get; set; }
string Person { get; set; }
}
答案很简单:因为正在建模奖金的大小和日期,而不是人。毕竟,如果想让人没有奖金,总是可以通过标记Person实体来实现。
回到正题。定义一个通用接口,用于所有支持标签的实体:
public interface ILabeled
{
string GetCurrentLabel();
void SetCurrentLabel(string label);
void AddLabel(string label);
ICollection Labels { get; }
T GetEntityByLabel(string label);
}
标签可以是一个比字符串更复杂的对象,但即使是字符串也非常有用。注意这里没有RemoveLabel方法。那是因为不想在这里实现它,因为有一些含义尚未完全理解。
在这里,声明一个新的接口,它实现了前两个接口:
public interface IBonusLabeled : IBonus, ILabeled
{
}
声明将实现所需功能的具象类:
internal class Bonus : IBonus
{
private double amount_;
private DateTime date_;
public double Amount
{
get { return amount_; }
set { amount_ = value; }
}
public DateTime Date
{
get { return date_; }
set { date_ = value; }
}
}
internal class BonusLabeled : Labeled, IBonusLabeled
{
private string person_;
public string Person
{
get { return person_; }
set { person_ = value; }
}
public double Amount
{
get { return base.Current.Amount; }
set { base.Current.Amount = value; }
}
public DateTime Date
{
get { return base.Current.Date; }
set { base.Current.Date = value; }
}
public new IBonusLabeled GetEntityByLabel(string label)
{
return base.GetEntityByLabel(label) as IBonusLabeled;
}
}
Labeled
public class Labeled : ILabeled where T : class, new()
{
private Dictionary entities_;
private string currentLabel_;
public Labeled()
{
entities_ = new Dictionary();
}
public string GetCurrentLabel()
{
return currentLabel_;
}
public void SetCurrentLabel(string label)
{
if (!entities_.ContainsKey(label))
{
// throw a nasty exception or do nothing or...
}
currentLabel_ = label;
}
public void AddLabel(string label)
{
if (entities_.ContainsKey(label))
{
// throw a nasty exception or do nothing or...
}
entities_.Add(label, new T());
}
public ICollection Labels
{
get { return entities_.Keys; }
}
public T GetEntityByLabel(string label)
{
// This piece of code has to be implemented separately, it is not a simple
// thing, but is unimportant here. Yeah, I'm being lazy :)
return MagicFactory.CreateNewLabeledProxy(this, label);
}
protected T Current
{
get { return entities_[currentLabel_]; }
}
}
BonusLabeled是Bonus集合的代理。MagicFactory将创建一个新的BonusLabeled对象,它将有另一个标签作为当前标签,但将共享相同的entities_集合。
不幸的是,这还不是全部。例如,可能希望有些字段被标记,有些则没有。比如Person字段(见下文)。
public interface IBonusLabeled : IBonus, ILabeled
{
string Person { get; set; }
}
上述代码表明(或应该读作):Person不会被标记。在现实世界中,这可能意味着要么支付奖金,要么不支付。一个特定的奖金属于一个特定的人。那么,问题是什么?问题是“共享”的Person字段值必须以这样的方式实现,以至于不能突然出现这种情况:
IBonusLabeled bonusA = new BonusLabeled { Person = "Smith" };
string labelA = "what-if-we-pay-standard";
bonusA.AddLabel(labelA);
bonusA.SetCurrentLabel(labelA);
bonusA.Amount = 10.0;
bonusA.Date = DateTime.Now;
string labelB = "what-if-we-pay-more";
bonusA.AddLabel(labelB);
IBonusLabeled bonusB = bonusA.GetEntityByLabel(labelB);
bonusB.Person = "Clay";
bool equals = bonusB.Person == bonusA.Person;
if (!equals)
{
// terrible things
}