.LegCount(); }}AnimalLegCount(animals);上面AnimalLegCount函数中,只需调用统一的LegCount方法。它所关心的就是传入的参数类型必须是Animal类型,即Animal类或其子类。
Animal类现在必须定义LegCount方法:
class Animal { //... LegCount();}其子类必须实现LegCount方法:
//...class Lion extends Animal{ //... LegCount() { //... }}//...当传递给AnimalLegCount函数时,它返回狮子的腿数。
你会发现,AnimalLegCount函数只管调用Animal的LegCount方法,而不需要知道Animal的具体类型即可返回其腿数。因为根据规则,Animal类的子类必须实现LegCount函数。
接口隔离原则接口隔离原则(Interface Segregation Principle):定制客户端的细粒度接口,不应强迫客户端依赖于不使用的接口。该原理解决了实现大接口的缺点。
让我们看下面的IShape接口:
interface IShape { drawCircle(); drawSquare(); drawRectangle();}该接口有绘制正方形,圆形,矩形三个方法。实现IShape接口的Circle,Square或Rectangle类必须同时实现drawCircle(),drawSquare(),drawRectangle()方法,如下所示:
class Circle implements IShape { drawCircle(){ //... } drawSquare(){ //... } drawRectangle(){ //... } }class Square implements IShape { drawCircle(){ //... } drawSquare(){ //... } drawRectangle(){ //... } }class Rectangle implements IShape { drawCircle(){ //... } drawSquare(){ //... } drawRectangle(){ //... } }看上面的代码很有意思。Rectangle类实现了它没有使用的方法(drawCircle和drawSquare),同样Square类实现了drawCircle和drawRectangle方法,Circle类也实现了drawSquare,drawSquare方法。
如果我们向IShape接口添加另一个方法,例如drawTriangle(),
interface IShape { drawCircle(); drawSquare(); drawRectangle(); drawTriangle();}这些类必须实现新方法,否则会编译报错。
接口隔离原则不赞成使用以上IShape接口的设计。不应强迫客户端(Rectangle,Circle和Square类)依赖于不需要或不使用的方法。另外,接口隔离原则也指出接口应该仅仅完成一项独立的工作(就像单一职责原理一样),任何额外的行为都应该抽象到另一个接口中。
为了使我们的IShape接口符合接口隔离原则,我们将不同绘制方法分离到不同的接口中,如下:
interface IShape { draw();}interface ICircle { drawCircle();}interface ISquare { drawSquare();}interface IRectangle { drawRectangle();}interface ITriangle { drawTriangle();}class Circle implements ICircle { drawCircle() { //... }}class Square implements ISquare { drawSquare() { //... }}class Rectangle implements IRectangle { drawRectangle() { //... } }class Triangle implements ITriangle { drawTriangle() { //... }}class CustomShape implements IShape { draw(){ //... }}ICircle接口仅处理图形,IShape处理任何形状的图形,ISquare仅处理正方形的图形,IRectangle处理矩形的图形。
当然,还有另一个设计是这样:
类(圆形,矩形,正方形,三角形等)可以仅从IShape接口继承并实现其自己的draw行为,如下所示。
class Circle implements IShape { draw(){ //... }}class Triangle implements IShape { draw(){ //... }}class Square implements IShape { draw(){ //... }}class Rectangle implements IShape { draw(){ //... }} 依赖倒置原则依赖倒置原则(Dependency Inversion Principle):依赖应该基于抽象而不是具体。高级模块不应依赖于低级模块,两者都应依赖抽象。
先看下面的代码:
class XMLHttpService extends XMLHttpRequestService {}class Http { constructor(private xmlhttpService: XMLHttpService) { } get(url: string , options: any) { this.xmlhttpService.request(url,'GET'); } post() { this.xmlhttpService.request(url,'POST'); } //...}在这里,Http是高级组件,而HttpService是低级组件。此设计违反了依赖倒置原则:高级模块不应依赖于低级模块,它应取决于其抽象。
Http类被强制依赖于XMLHttpService类。如果我们要修改Http请求方法代码(如:我们想通过Node.js模拟HTTP服务)我们将不得不修改Http类的所有方法实现,这就违反了开闭原则。
怎样才是更好的设计?我们可以创建一个Connection接口:
interface Connection { request(url: string, opts:any);}该Connection接口具有请求方法。这样,我们将类型的参数传递Connection给Http类:
class Http { constructor(private httpConnection: Connection) { } get(url: string , options: any) { this.httpConnection.request(url,'GET'); } post() { this.httpConnection.request(url,'POST'); } //...}现在,无论我们调用Http类的哪个方法,它都可以轻松发出请求,而无需理会底层到底是什么样实现代码。
我们可以重新设计XMLHttpService类,让其实现Connection接口:
class XMLHttpService implements Connection { const xhr = new XMLHttpRequest(); //... request(url: string, opts:any) { xhr.open(); xhr.send(); }}以此类推,我们可以创建许多Connection类型的实现类,并将其传递给Http类。
class NodeHttpService implements Connection { request(url: string, opts:any) { //... }}class MockHttpService implements Connection { request(url: string, opts:any) { //... } }现在,我们可以看到高级模块和低级模块都依赖于抽象。Http类(高级模块)依赖于Connection接口(抽象),而XMLHttpService类、MockHttpService 、或NodeHttpService类 (低级模块)也是依赖于Connection接口(抽象)。
与此同时,依赖倒置原则也迫使我们不违反里氏替换原则:上面的实现类Node- XML- MockHttpService可以替代他们的父类型Connection。