接口隔离原则

97 阅读3分钟

接口隔离原则

接口隔离原则(Interface Segregation Principle,ISP)

核心思想:类之间的依赖关系应该建立在最小的接口上。客户端不应该依赖它不需要的接口。用多个细粒度的接口来替代由多个方法组成的复杂接口,每一个接口服务于一个子模块。

类 A 通过接口 interface F依赖类 C,类 B 通过接口 interface F 依赖类 D,如果接口 interface F 对于类 A 和类 B 来说不是最小接口,则类 C 和类 D 必须去实现他们不需要的方法。

案例分析:

案例一:

蝙蝠是哺乳动物却能飞行;鲸鱼哺乳动物却生活在海中;天鹅是鸟类,能在天上飞,也能在水里游,还能在地上走。上面的示例中将动物根据活动场所分为水生动物、陆生动物和飞行动物是不够准确的,因为奔跑、游泳、飞翔是动物的一种行为,应该抽象成接口,而且有些动物可能同时具有多种行为。应该根据生理特征来分类,如哺乳类、鸟类、鱼类;哺乳类动物具有恒温,胎生,哺乳等生理特征;鸟类动物具有恒温,卵生,前肢成翅等生理特征;鱼类动物具有流线型体形,用鳃呼吸等生理特征。

abstract class Animal {
  constructor(public name) {}
  getName() {
    return this.name
  }
  abstract feature(): string
  abstract moving(): void
}
interface IRunnable {
  running(): void
}
interface IFlyable {
  flying(): void
}
interface INatatory {
  swimming(): void
}
// 哺乳动物
class MammalAnimal extends Animal implements IRunnable {
  feature() {
    return this.name + '的生理特征:恒温,胎生,哺乳。'
  }
  running() {
    console.log('在陆上跑...')
  }
  moving() {
    console.log(this.name + '的活动方式:')
    this.running()
  }
}
class BirdAnimal extends Animal implements IFlyable {
  feature() {
    return this.name + '的生理特征:恒温,卵生,前肢成翅。'
  }
  flying() {
    console.log('在天空飞...')
  }
  moving() {
    console.log(this.name + '的活动方式:')
    this.flying()
  }
}
class FishAnimal extends Animal implements INatatory {
  feature() {
    return this.name + '的生理特征:流线型体形,用鳃呼吸。'
  }
  swimming() {
    console.log('在水里游...')
  }
  moving() {
    console.log(this.name + '的活动方式:')
    this.swimming()
  }
}
class Bat extends MammalAnimal implements IFlyable {
  running() {
    console.log('行走功能已经退化。')
  }
  flying() {
    console.log('在天空飞...')
  }
  moving() {
    console.log(this.name + '的活动方式:')
    this.flying()
    this.running()
  }
}
// 天鹅
class Swan extends BirdAnimal implements IRunnable, INatatory {
  running() {
    console.log('在陆上跑...')
  }
  swimming() {
    console.log('在水里游...')
  }
  moving() {
    console.log(this.name + '的活动方式:')
    this.running()
    this.swimming()
    this.flying()
  }
}
// 鲫鱼
class CrucianCarp extends FishAnimal {}
function test() {
  const bat = new Bat('蝙蝠')
  console.log(bat.feature())
  bat.moving()
  const swan = new Swan('天鹅')
  console.log(swan.feature())
  swan.moving()
  const crucianCarp = new CrucianCarp('鲫鱼')
  console.log(crucianCarp.feature())
  crucianCarp.moving()
}
test()

这里将奔跑、游泳、飞翔抽象成接口就是对接口的一种细粒度拆分,提高程序设计灵活性。

案例二:

定义了一个大的接口IWorker,它包含了各种工作的方法,不管是工人是否需要所有这些方法。

interface IWorker {
    work(): void;
    eat(): void;
    sleep(): void;
}
​
class Worker implements IWorker {
    work(): void {
        console.log('Working');
    }
    eat(): void {
        console.log('Eating lunch');
    }
    sleep(): void {
        console.log('Sleeping');
    }
}
​
class Robot implements IWorker {
    work(): void {
        console.log('Working much more efficiently');
    }
    eat(): void {
        // 问题:机器人不需要吃,但是被迫实现该方法
    }
    sleep(): void {
        // 问题:机器人不需要睡觉,但是被迫实现该方法
    }
}

应该将IWorker接口拆分为几个更小的接口,每个接口专注于一组相关的功能。

interface IWork {
    work(): void;
}
​
interface IEat {
    eat(): void;
}
​
interface ISleep {
    sleep(): void;
}
​
class Worker implements IWork, IEat, ISleep {
    work(): void {
        console.log('Working');
    }
    eat(): void {
        console.log('Eating lunch');
    }
    sleep(): void {
        console.log('Sleeping');
    }
}
​
class Robot implements IWork {
    work(): void {
        console.log('Working much more efficiently');
    }
    // 现在,Robot类只需要关心work方法,不必实现它不需要的eat和sleep方法
}