在面向对象语言中,我们往往通过继承来实现代码的复用,下面代码就演示了一个简单的Log功能的例子:
class Logger<T>
{
Subject<T> subject = new Subject<T>();
public void LogMessage(T msg)
{
subject.OnNext(msg);
}
public void Subscribe(Action<T> msgHanlder)
{
subject.Subscribe(msgHanlder);
}
}
class MyClass1:Logger<string>
{
}
class MyClass2:Logger<int>
{
}
然而,Log功能一般只是一种附加功能,在实际应用中,我们的子类往往需要从别的更复杂的类中继承而来,但此时如果要服用Log功能该怎么办呢?对于C++这种支持多重继承的语言来说,直接将Logger作为另一个父类即可附加该功能。然而,对于C#这种只支持单一父类的语言来说,一般只能采用组合的方式来复用Log功能了,基本方式如下:
class MyClass1 : Base1
{
Logger<string> logger = new Logger<string>();
public void LogMessage(string msg)
{
logger.LogMessage(msg);
}
public void Subscribe(Action<string> msgHanlder)
{
logger.Subscribe(msgHanlder);
}
}
class MyClass2 : Base2
{
Logger<string> logger = new Logger<string>();
public void LogMessage(string msg)
{
logger.LogMessage(msg);
}
public void Subscribe(Action<string> msgHanlder)
{
logger.Subscribe(msgHanlder);
}
}
这种方法带来的一个不好的地方在于:每一个子类都必须封装一遍LogMessage和Subscribe方法。如果子类较多,代码非常难看。好在C# 3.0带来了扩展属性的方法,通过它,则可以很容易类似C++之类的多重继承的功能。
interface ILogger<T>
{
}
class MyClass1 : ILogger<string>
{
}
class MyClass2 : ILogger<int>
{
}
static class LoggerExtend
{
static Subject<T> GetLoggerInfo<T>(this ILogger<T> logger)
{
throw new NotImplementedException();
}
public static void LogMessage<T>(this ILogger<T> logger, T msg)
{
logger.GetLoggerInfo().OnNext(msg);
}
public static void Subscribe<T>(this ILogger<T> logger, Action<T> msgHanlder)
{
logger.GetLoggerInfo().Subscribe(msgHanlder);
}
}
这种模式就很完美的解决了这一问题,子类只要实现ILogger这一接口,无需写任何代码,就拥有了Log功能。
但是,上面的方法还不能运行,最关键的GetLoggerInfo方法还没有实现,最直接的实现方法如下:
static Dictionary<object, object> dic = new Dictionary<object, object>();
static Subject<T> GetLoggerInfo<T>(this ILogger<T> logger)
{
object subject = null;
if (!dic.TryGetValue(logger, out subject))
{
subject = new Subject<T>();
dic.Add(logger, subject);
}
return subject as Subject<T>;
}
这样处理后,虽然代码功能看起来正常了,然而却存在一个非常大的隐患——logger无法释放:一旦logger使用后,在静态变量dic中就一直保存着该对象的引用,这样就永远无法释放。为了解决这一问题,我们必须将这个方法改一下,改成使用弱引用来维护logger引用,当logger释放后,其关联的subject对象也跟着一起释放。修改后的代码如下:
static class LoggerExtend
{
class WeakObject : WeakReference
{
public object Tag { get; private set; }
public WeakObject(object target,object tag)
: base(target)
{
this.Tag = tag;
}
}
static Dictionary<int, WeakObject> dic = new Dictionary<int, WeakObject>();
static Timer gcTimer = null;
static Subject<T> GetLoggerInfo<T>(this ILogger<T> logger)
{
lock (dic)
{
var key = logger.GetHashCode();
WeakObject weakobj = null;
if (!dic.TryGetValue(key, out weakobj))
{
var subject = new Subject<T>();
weakobj = new WeakObject(logger, subject);
dic.Add(key, weakobj);
Console.WriteLine("add "+key);
if (gcTimer == null)
gcTimer = new Timer(_ => GcWeakObject(), null, 0, 10000);
}
return weakobj.Tag as Subject<T>;
}
}
static void GcWeakObject()
{
lock (dic)
{
var deadObj = dic.Where(i => !i.Value.IsAlive).ToArray();
foreach (var item in deadObj)
{
if (item.Value.Tag is IDisposable)
(item.Value.Tag as IDisposable).Dispose();
dic.Remove(item.Key);
}
if (dic.Count == 0 && gcTimer != null)
{
gcTimer.Dispose();
gcTimer = null;
}
}
}
public static void LogMessage<T>(this ILogger<T> logger, T msg)
{
logger.GetLoggerInfo().OnNext(msg);
}
public static void Subscribe<T>(this ILogger<T> logger, Action<T> msgHanlder)
{
logger.GetLoggerInfo().Subscribe(msgHanlder);
}
}
最后总结一下吧,本文通过C#的扩展方法和弱引用,简单的实现了一个类似C++多重继承的功能,方便那种在大量地方使用的小功能的扩展。当然,这里的解决方案也不完美,一个很明显的问题就是扩展功能对象无法及时回收,需要等到计时器超时,也不支持手动Dispose。这种方式还是比较容易扩展的,我在这里只是抛砖引玉,如果谁有更好的方法,欢迎一起讨论。