深圳中国电信网站备案,域名没到期 网站打不开,重庆秀山网站建设公司,网络运营主要工作内容第12章 ISP#xff1a;接口隔离原则 不应该强迫客户程序依赖并未使用的方法。 这个原则用来处理“胖”接口所存在的缺点。如果类的接口不是内敛的#xff0c;就表示该类具有“胖”接口。换句话说#xff0c;类的“胖”接口可以分解成多组方法。每一组方法都服务于一组不…第12章 ISP接口隔离原则 不应该强迫客户程序依赖并未使用的方法。 这个原则用来处理“胖”接口所存在的缺点。如果类的接口不是内敛的就表示该类具有“胖”接口。换句话说类的“胖”接口可以分解成多组方法。每一组方法都服务于一组不同的客户程序。这样一些客户程序可以使用一组成员函数而其他客户程序可以使用其他组的成员函数。 ISP承认一些对象确实需要非内敛的接口但是ISP建议客户不应该看到它们作为单一的类存在。相反客户程序看到的应该是多个具有内敛接口的抽象基类。 12.1 接口污染 如果子类的接口中有子类不需要的方法就说这个接口被污染了。在接口中加入方法只为了能给它的其中一个子类带来好处。如果持续这样的话那么每次子类需要一个新方法时这个方法就会加到基类中去。这会进一步污染其基类的接口使它变“胖”。 每次基类中加入一个方法时派生类中就必须实现这个方法或者定义一个默认的实现。事实上一种特定的相关实践可以使派生类无需实现这些方法该实践的方法就是把这些接口合并成一个基类并在这个基类中提供接口中方法的退化实现。但是这种实践违反了LSP会带来维护和重用方面的问题。 12.2 分离客户就是分离接口 不应该强迫客户程序依赖并未使用的方法。如果强迫客户依赖于它们不使用的方法那么这些客户程序就面临着由于这些未使用方法的改变带来的变更。这无意中导致了所有客户程序之间的耦合。换种说法如果一个客户程序依赖于一个含有它不使用的方法的类但是其它客户程序却要使用该方法那么当其他客户程序要求这个类改变时就会影响到这个客户程序。我们希望尽可能的避免这种耦合因此我们希望分类接口。 12.3 类接口与对象接口 一个对象的客户程序不必通过该对象的接口来访问它也可以通过委托或者通过该对象的基类来访问它。 12.3.1 使用委托分离接口 这里的“委托”不是我们说的“委托类型”的委托原文翻译的容易产生歧义。其实就是用适配器来解耦客户程序和接口。 不过这个解决方案还有些不太优雅。每次想要注册一个请求都要去创建一个新的对象。 12.3.2 使用多重继承分离接口 通常我会优先选择这个解决方案。只有当Adapter对象所做的转换是必须的或者不同的时候需要不同的转换时才使用适配器的方案。 12.4 ATM用户界面的例子 ATM自动取款机需要一个非常灵活的界面。它的输出信息需要转换成不同的语言。输出信息可能显示在屏幕上或者盲文书写板或者通过语音合成器说出来。通过创建一个抽象类就可以实现这种需求 同样可以把每个ATM执行的不同事务存储、取款、转账封装为类Transaction的派生类 注意这正好是LSP告我我们应该避免的情形。每个事务所使用的UI的方法其他操作类都不会使用这样对于任何一个Transaction的派生类的改动都会迫使对UI的相应改动从而也影响到了其他所有Transaction的派生类及其他所有依赖于UI接口的类。这样的设计就有了僵化性和脆弱性。 这时需要分类ATM的UI接口 这样每次创建一个Transaction类的新派生类抽象接口UI就需要增加一个相应的基类并且因此UI接口以及所有它的派生类都必须改变。不过这些类并没有被广泛使用。事实上它们可能仅被main或者那些启动系统并创建具体UI实例之类的过程使用。因此增加新的UI基类所带来的影响被减至最小。 查看如下代码 public interface Transaction
{void Execute();
}
public interface DepositUI
{void RequestDepositAmount();
}
public class DepositTransaction : Transaction
{privateDepositUI depositUI;public DepositTransaction(DepositUI ui){depositUI ui;}public virtual void Execute(){/*code*/depositUI.RequestDepositAmount();/*code*/}
}
public interface WithdrawalUI
{void RequestWithdrawalAmount();
}
public class WithdrawalTransaction : Transaction
{private WithdrawalUI withdrawalUI;public WithdrawalTransaction(WithdrawalUI ui){withdrawalUI ui;}public virtual void Execute(){/*code*/withdrawalUI.RequestWithdrawalAmount();/*code*/}
}
public interface TransferUI
{void RequestTransferAmount();
}
public class TransferTransaction : Transaction
{private TransferUI transferUI;public TransferTransaction(TransferUI ui){transferUI ui;}public virtual void Execute(){/*code*/transferUI.RequestTransferAmount();/*code*/}
}
public interface UI : DepositUI, WithdrawalUI, TransferUI
{
} 每个事务都必须以某种方式知晓它的特定UI版本。使每个事务在构造时给它传入特定于它的UI的引用就解决了这个问题。这使我们可以使用如下代码 UI Gui; // global object;
void f()
{DepositTransaction dt new DepositTransaction(Gui);
} 虽然这很方便但是同样要求每个事务都有一个整型对应UI的引用成员。在C#中一种比较有诱惑力的做法是把所有UI组件放到一个单一的类中如下 public class UIGlobals
{public static WithdrawalUI withdrawal;public static DepositUI deposit;public static TransferUI transfer;static UIGlobals(){UI Lui new AtmUI(); // Some UI implementationUIGlobals.deposit Lui;UIGlobals.withdrawal Lui;UIGlobals.transfer Lui;}
} 不过这种做法有一个负面效果。那就是UIGlobal类依赖于DepositUI、WithdrawalUI和TransferUI。UIGlobal类把我们分离的接口重新结合在一起了。 每个原则在应用时都必须小心不能过度使用它们。如果一个类具有数百个不同接口其中一些是根据客户程序分离的另一些是根据版本分离的那么该类就是难以琢磨的这种难以琢磨性是非常令人恐惧的。 12.5 结论 胖类会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求改胖类进行一个改动时会影响到所有其他的客户程序。因此客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定于用户程序的接口可以实现这个目标。每个特定于客户程序的接口仅仅声明它的特定客户或者客户组调用的那些函数。接着该胖类就可以继承所有特定于客户程序的接口并实现它们。这就解除了客户程序和它们没有调用的方法间的依赖关系并使客户程序之间互不依赖。 摘自《敏捷软件开发原则、模式与实践(C#版)》Robert C.Martin Micah Martin 著 转载请注明出处 作者JesseLZJ出处http://jesselzj.cnblogs.com转载于:https://www.cnblogs.com/jesselzj/p/4765247.html