1、场景举例分析
二、场景示例 下面就以我们传统行业的订单操作为例来说明下接口隔离的必要性。 1、胖接口 软件设计最初,我们的想法是相同功能的方法放在同一个接口里面,如下,所有订单的操作都放在订单接口IOrder里面。理论上来说,这貌似没错。我们来看看如何设计。 public interface IOrder { //订单申请操作 void Apply(object order); //订单审核操作 void Approve(object order); //订单结束操作 void End(object order); } 刚开始只有销售订单,我们只需要实现这个接口就好了。 public class SaleOrder:IOrder { public void Apply(object order) { throw new NotImplementedException(); } public void Approve(object order) { throw new NotImplementedException(); } public void End(object order) { throw new NotImplementedException(); } } 后来,随着系统的不断扩展,我们需要加入生产订单,生产订单也有一些单独的接口方法,比如:排产、冻结、导入、导出等操作。于是我们向订单的接口里面继续加入这些方法。于是订单的接口变成这样: public interface IOrder { //订单申请操作 void Apply(object order); //订单审核操作 void Approve(object order); //订单结束操作 void End(object order); //订单下发操作 void PlantProduct(object order); //订单冻结操作 void Hold(object order); //订单删除操作 void Delete(object order); //订单导入操作 void Import(); //订单导出操作 void Export(); } 我们生产订单的实现类如下 //生产订单实现类 public class ProduceOrder : IOrder { /// <summary> /// 对于生产订单来说无用的接口 /// </summary> /// <param name="order"></param> public void Apply(object order) { throw new NotImplementedException(); } /// <summary> /// 对于生产订单来说无用的接口 /// </summary> /// <param name="order"></param> public void Approve(object order) { throw new NotImplementedException(); } /// <summary> /// 对于生产订单来说无用的接口 /// </summary> /// <param name="order"></param> public void End(object order) { throw new NotImplementedException(); } public void PlantProduct(object order) { Console.WriteLine("订单下发排产"); } public void Hold(object order) { Console.WriteLine("订单冻结"); } public void Delete(object order) { Console.WriteLine("订单删除"); } public void Import() { Console.WriteLine("订单导入"); } public void Export() { Console.WriteLine("订单导出"); } } 销售订单的实现类也要相应做修改 //销售订单实现类 public class SaleOrder:IOrder { public void Apply(object order) { Console.WriteLine("订单申请"); } public void Approve(object order) { Console.WriteLine("订单审核处理"); } public void End(object order) { Console.WriteLine("订单结束"); } #region 对于销售订单无用的接口方法 public void PlantProduct(object order) { throw new NotImplementedException(); } public void Hold(object order) { throw new NotImplementedException(); } public void Delete(object order) { throw new NotImplementedException(); } public void Import() { throw new NotImplementedException(); } public void Export() { throw new NotImplementedException(); } #endregion } 需求做完了,上线正常运行。貌似问题也不大。系统运行一段时间之后,新的需求变更来了,要求生成订单需要一个订单撤销排产的功能,那么我们的接口是不是就得增加一个订单撤排的接口方法CancelProduct。于是乎接口变成这样: public interface IOrder { //订单申请操作 void Apply(object order); //订单审核操作 void Approve(object order); //订单结束操作 void End(object order); //订单下发操作 void PlantProduct(object order); //订单撤排操作 void CancelProduct(object order); //订单冻结操作 void Hold(object order); //订单删除操作 void Delete(object order); //订单导入操作 void Import(); //订单导出操作 void Export(); } 这个时候问题就来了,我们的生产订单只要实现这个撤销的接口貌似就OK了,但是我们的销售订单呢,本来销售订单这一块我们不想做任何的变更,可是由于我们IOrder接口里面增加了一个方法,销售订单的实现类是不是也必须要实现一个无效的接口方法?这就是我们常说的“胖接口”导致的问题。由于接口过“胖”,每一个实现类依赖了它们不需要的接口,使得层与层之间的耦合度增加,结果导致了不需要的接口发生变化时,实现类也不得不相应的发生改变。这里就凸显了我们接口隔离原则的必要性,下面我们就来看看如何通过接口隔离来解决上述问题。 2、接口隔离 我们将IOrder接口分成两个接口来设计 //删除订单接口 public interface IProductOrder { //订单下发操作 void PlantProduct(object order); //订单撤排操作 void CancelProduct(object order); //订单冻结操作 void Hold(object order); //订单删除操作 void Delete(object order); //订单导入操作 void Import(); //订单导出操作 void Export(); } //销售订单接口 public interface ISaleOrder { //订单申请操作 void Apply(object order); //订单审核操作 void Approve(object order); //订单结束操作 void End(object order); } 对应的实现类只需要实现自己需要的接口即可 //生产订单实现类 public class ProduceOrder : IProductOrder { public void PlantProduct(object order) { Console.WriteLine("订单下发排产"); } public void CancelProduct(object order) { Console.WriteLine("订单撤排"); } public void Hold(object order) { Console.WriteLine("订单冻结"); } public void Delete(object order) { Console.WriteLine("订单删除"); } public void Import() { Console.WriteLine("订单导入"); } public void Export() { Console.WriteLine("订单导出"); } } //销售订单实现类 public class SaleOrder : ISaleOrder { public void Apply(object order) { Console.WriteLine("订单申请"); } public void Approve(object order) { Console.WriteLine("订单审核处理"); } public void End(object order) { Console.WriteLine("订单结束"); } }
这样设计就能完美解决上述“胖接口”导致的问题,如果需要增加订单操作,只需要在对应的接口和实现类上面修改即可,这样就不存在依赖不需要接口的情况。通过这种设计,降低了单个接口的复杂度,使得接口的“内聚性”更高,“耦合性”更低。由此可以看出接口隔离原则的必要性。
另:
有称六大设计原则:+迪米特法则。
迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
举例:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如图 所示。