早在.net 3.0中就引入了一个新的东西,扩展方法,虽然这已经不能叫新东西了,可是在项目中很少使用,闲暇之余,突然想挖掘一下。它支持对已有类增加实现新的方法,而不必修改已有的类,这是对OCP原则(开放-关闭原则,即对扩展开放,对修改关闭)的很好的一个支持。此外,扩展方法对设计模式也有一个不错的促进作用。

其实扩展方法是一个静态方法,而在调用时则像实例方法一样去调用。最经典的例子就是string.Reverse()方法。扩展方法,一般在使用第三方系统或不可修改的解决方案,而且需要扩展的时候,就可能有了应用的场景。通过使用扩展方法这种方式,即干净,又利落,即便在扩展时一不小心扩展了已有的一个方法,也没关系,优先执行实例方法而忽略扩展方法。

在具体的开发中经常会碰到此类问题,就是涉及接口的改动。你不可能直接去修改已有的接口定义。假设原接口定义如下:

public interface IShipmentManagement

{

    bool AddShipment(Shipment shipment);

    bool UpdateShipment(Shipment shipment);

    bool DeleteShipment(Guid shipmentId);

}

原业务实现:

public class ShipmentManagement : IShipmentManagement

{

    #region IShipmentManagement 成员

    public bool AddShipment(Shipment shipment)

    {

        Console.WriteLine("Shipment:{0} added.", shipment.ID);

        return true;

    }

    public bool UpdateShipment(Shipment shipment)

    {

        Console.WriteLine("Shipment:{0} updated.", shipment.ID);

        return true;

    }

    public bool DeleteShipment(Guid shipmentId)

    {

        Console.WriteLine("Shipment:{0} deleted.", shipmentId);

        return true;

    }

    #endregion

}

假设现在新增加了一个业务操作,Evaluate(Shipment shipment)

OO中解决这个问题,一般有两个方案,第一个方案是,创建一个新的接口,继承自需要修改的接口,在新接口中增加需要增加的新的方法,然后基于新的接口重新实现:

public interface IShipmentManagementEx : IShipmentManagement

{

    EvaluateResult Evaluate(Shipment shipment);

}

然后继承自ShipmentManagement类,并实现IShipmentManagementEx接口:

public class ShipmentManagementEx2 : ShipmentManagementIShipmentManagementEx

{

    public EvaluateResult Evaluate(Shipment shipment)

    {

        Console.WriteLine("Shipment:{0}'s evaluated.", shipment.ID);

        return new EvaluateResult { Result = true, Description = "Success" };

    }

}


第二个方案,是使用聚合(Aggregation),新的接口聚合旧的接口:

public interface IShipmentManagementEx

{

    IShipmentManagement ShipmentManagement { getset; }

    EvaluateResult Evaluate(Shipment shipment);

}

然后这样实现该接口:

public class ShipmentManagementEx : IShipmentManagementEx

{

    public IShipmentManagement ShipmentManagement { getset; }

    public EvaluateResult Evaluate(Shipment shipment)

    {

        Console.WriteLine("Shipment:{0}'s evaluated.", shipment.ID);

        return new EvaluateResult { Result = true, Description = "Success" };

    }

}

这样也可以达到目的,并且都可以做到尽可能的代码重用。

其实通过扩展方法也可以实现,只要增加一个Helper类即可:

public static class Helper

{

    public static EvaluateResult Evaluate(this IShipmentManagement shipmentManager,Shipment shipment)

    {

        Console.WriteLine("Shipment:{0}'s evaluated.", shipment.ID);

        return new EvaluateResult { Result = true, Description = "Success" };

    }

}

不过或许你会发现一个问题,那就是IShipmentManagement接口的不同实现中的Evaluate(Shipment shipment)方法实现是相同的,换言之,即不能在不同实现中实现该方法。这确实是扩展方法力所不能及的地方,对于不需要继承自接口,而且是不可修改的类型而言,扩展方法的意义非凡。而对于OO中接口的扩展,真的是有点儿跛足。

如果能够实现真正意义上的接口扩展,要比接口继承或聚合更有意义,这个是否可以实现呢?对于这个业务场景,完全是可以实现的。主要思路是,基于接口增加扩展方法,在扩展方法的实现中,根据传入的具体实现类,来调用当前的扩展方法,如果该实现类中不存在该扩展方法,则说明该实现类为旧的版本,反之可以正常执行。

先看看扩展方法的实现部分:

public static class ShipmentManagementExtension

{

    public static EvaluateResult Evaluate(this IShipmentManagement shipmentManager, Shipment shipment)

    {

        try

        {

            return (EvaluateResult)Helper.Execute(shipmentManager, MethodBase.GetCurrentMethod().Name, new object[1] { shipment });

        }

        catch (InvalidImplementationException)

        {

            //对于之前的实现类,将共同实现的放在这里

            Console.WriteLine("Because you selected the old version class, so nothing will happen here.");

            return new EvaluateResult { Result = false, Description = "Not support." };

        }

    }

}

这个扩展方法的执行部分,由于接口中并无扩展方法的定义,所以无法直接调用,当然,这个地方也不能直接调用,否则会进入无穷递归而抛出StackOverflowException的。方法名自然就是当前的扩展方法名,要保证这个方法在实现类中被实现,就能够保证在此处调用正常,否则会抛出异常,我在这里定义了一个InvalidImplementationException类来捕捉这种情况,从而能够给出正确的处理。由于这个扩展方法在实现类中是约定实现的,因此没有像接口一样的强约束来保证类型检查的正确性。

还有一个重要的地方,就是Helper类,在这个类中,根据传入的对象,以及要执行的方法名和参数,来动态在该对象上执行该实例方法:

public static class Helper

{

    public static object Execute(dynamic instance, string method,object[] args)

    {

        Type currentType = instance.GetType();

        MethodInfo methodInfo = currentType.GetMethod(method);

        if (methodInfo == null)

        {

            throw new InvalidImplementationException(string.Format("Invalid instance:{0} selected.", instance.GetType().Name));

        }

        object currentObject = Activator.CreateInstance(currentType);

        Copy(currentType, instance, currentObject);

        return currentType.InvokeMember(method, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, currentObject, args);

    }

    static void Copy(Type type, object source, object destination)

    {

        if (source.GetType() != type || destination.GetType() != type)

        {

            throw new Exception("invalid args.");

        }

        foreach (PropertyInfo property in type.GetProperties())

        {

            property.SetValue(destination, property.GetValue(source, null), null);

        }

    }

}

在这个Helper类中,部分实现可能有待改进,例如对象复制的部分,我只复制了属性成员,我约定实例方法除了依赖参数外,对上下文的依赖主要在于对象的属性,这是一个非常重要的一点,如果该方法执行需要依赖于对象的一个私有成员,那么将会碰到问题。

再来看看实现类:

public class ShipmentManagementEx : ShipmentManagement

{

    public EvaluateResult Evaluate(Shipment shipment)

    {

        Console.WriteLine("Shipment:{0}'s evaluated.", shipment.ID);

        return new EvaluateResult { Result = true, Description = "Success" };

    }

}

为了重用已有的实现,这个实现类继承自已有的实现类。由于IShipmentManagement的扩展方法的实现直接被定向到具体的实现类,从而模拟了接口的扩展。

从实现的步骤上来看,共有两步,一个实现扩展方法,实现方式有点儿怪异,另一个增加实现类,实现约定的方法。这里用到了我上一篇文章中讲到的Duck Typing的思想,这对于我们在探索接口可扩展的方式上是否有一点点启发呢?

作者: Bright Zhang 发表于 2011-05-08 22:07 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"