从一个例子开始吧。
function Duck(type){
this.type = type;
}
Duck.prototype.swim = function(){
console.log("I'm swimming");
}
Duck.prototype.display = function(){
var type = this.type;
if(type === "mallard"){
console.log("I'm a mallard duck");
}else if(type === "redhead"){
console.log("I'm a redhead duck");
}else if(type === "rubber"){
console.log("I'm a rubber duck");
}else if(type === "decoy"){
console.log("I'm a decoy duck");
}else{
console.log("I'm a duck");
}
}
Duck.prototype.quack = function(){
switch(this.type){
case "mallard":
case "redhead":console.log("I can quack");break;
case "rubber":console.log("I can squeak");break;
case "decoy":console.log("I am a mute");break;
default:console.log("I can quack");
}
}
Duck.prototype.fly = function(){
switch(this.type){
case "mallard":
case "redhead":console.log("I'm flying");break;
case "rubber":
case "decoy":console.log("I cannot fly");break;
default:console.log("I'm flying");
}
}
//测试代码
const duck = new Duck();
duck.display();
duck.swim();
duck.quack();
duck.fly();
console.log("-----------------分割线-----------------");
const mallardDuck = new Duck("mallard");
mallardDuck.display();
mallardDuck.swim();
mallardDuck.quack();
mallardDuck.fly();
console.log("-----------------分割线-----------------");
const redheadDuck = new Duck("redhead");
redheadDuck.display();
redheadDuck.swim();
redheadDuck.quack();
redheadDuck.fly();
console.log("-----------------分割线-----------------");
const rubberDuck = new Duck("rubber");
rubberDuck.display();
rubberDuck.swim();
rubberDuck.quack();
rubberDuck.fly();
console.log("-----------------分割线-----------------");
const decoyDuck = new Duck("decoy");
decoyDuck.display();
decoyDuck.swim();
decoyDuck.quack();
decoyDuck.fly();
display()
,用了if...else
,除了普通鸭(Duck)之外,还有绿头鸭(mallardDuck)、红头鸭(redheadDuck),橡皮鸭(rubberDuck)和诱饵鸭(decoyDuck)。swim()
还好,不论啥鸭,不怕水淹就算会游泳了,所以正享受着呢,“I’m swimming”。quack()
,用了个switch
,活鸭能嘎嘎叫(quack),捏着橡皮鸭(rubberDuck)能发出吱吱叫(squeak),而诱饵鸭(decoyDuck)根本不会叫,所以时刻保持沉默(mute)。fly()
,也用了个switch
,橡皮鸭(rubberDuck)和诱饵鸭(decoyDuck)不是活鸭,当然不会飞。
鸭子们swim()
一样,display()
、quack()
和fly()
却各有各的表现,这其实很容易让我们想到另一种实现方式:继承
。
超类
拥有统一的swim()
、display()
、quack()
和fly()
,子类
如果具有自己独特的表现,覆盖掉
就好了。
那就试试吧。
继承
class Duck{
display(){
console.log("I'm a duck");
}
swim(){
console.log("I'm swimming");
}
quack(){
console.log("I can quack");
}
fly(){
console.log("I'm flying");
}
}
class MallardDuck extends Duck{
display(){
console.log("I'm a mallard duck");
}
}
class RedHeadDuck extends Duck{
display(){
console.log("I'm a redhead duck");
}
}
class RubberDuck extends Duck{
display(){
console.log("I'm a rubber duck");
}
quack(){
console.log("I can squeak");
}
fly(){
console.log("I cannot fly");
}
}
class DecoyDuck extends Duck{
display(){
console.log("I'm a decoy duck");
}
quack(){
console.log("I am a mute");
}
fly(){
console.log("I cannot fly");
}
}
//测试代码
const duck = new Duck();
duck.display();
duck.swim();
duck.quack();
duck.fly();
console.log("-----------------分割线-----------------");
const mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.swim();
mallardDuck.quack();
mallardDuck.fly();
console.log("-----------------分割线-----------------");
const redheadDuck = new RedHeadDuck();
redheadDuck.display();
redheadDuck.swim();
redheadDuck.quack();
redheadDuck.fly();
console.log("-----------------分割线-----------------");
const rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.swim();
rubberDuck.quack();
rubberDuck.fly();
console.log("-----------------分割线-----------------");
const decoyDuck = new DecoyDuck();
decoyDuck.display();
decoyDuck.swim();
decoyDuck.quack();
decoyDuck.fly();
可以看到,继承
能够避免部分重复代码
。嗯,继承
能帮我们实现代码复用
。
呀!还得知道小鸭子喜欢吃什么,那就给超类Duck
添加eat()
方法吧。
class Duck{
//新增eat方法
eat(){
console.log("I eat fish and shrimps");
}
}
呃,绿头鸭和红头鸭喜欢吃小鱼小虾,橡皮鸭和诱饵鸭什么也不吃。所以,绿头鸭和红头鸭直接继承超类的eat()就好,橡皮鸭和诱饵鸭就得覆写超类的eat()了。
class RubberDuck extends Duck{
//覆写Duck的eat
eat(){
console.log("I eat nothing");
}
}
class DecoyDuck extends Duck{
//覆写Duck的eat
eat(){
console.log("I eat nothing");
}
}
如果再给超类Duck
添加一个sleep()
呢?哦,我们又得想了:啊,红头鸭绿头鸭会睡,但橡皮鸭和诱饵鸭不用睡。
是不是发现一点问题了?每给超类Duck
添加一个行为,我们都必须考虑下子类
们是不是也具备同样的行为,如果一样是最好了,如果不一样,就得一个个覆写。好在本例只有4个子类,如果有40个呢?是不是每天都可以领加班夜宵了。
所以,继承
不是最好的选择。
用接口
试试?试试就试试。
接口
swim()
大家都有,且一样,可以称作是不变的部分
。
display()
大家都必须有,只是不同而已。
quack()
有“嘎嘎叫”,有“吱吱叫”,还有根本不会出声的。所以quack()
时有时无。
fly()
,要么能飞,要么不能飞,所以fly()
时有时无。
eat()
,要么吃小鱼小虾,要么根本不用吃,所以eat()
时有时无。
“大家必须有”的放在超类Duck
中,“时有时无”的用接口
。“有”的话,子类
就需要去实现接口。像这样:
//TypeScript
class Duck{
display(){
console.log("I'm a duck");
}
swim(){
console.log("I'm swimming");
}
}
interface Flyable{
fly();
}
interface Quackable{
quack();
}
interface Eatable{
eat();
}
class MallardDuck extends Duck implements Flyable,Quackable,Eatable{
display(){
console.log("I'm a mallard duck");
}
quack(){
console.log("I can quack");
}
fly(){
console.log("I can fly");
}
eat(){
console.log("I eat fish and shrimps");
}
}
class RedHeadDuck extends Duck implements Flyable,Quackable,Eatable{
display(){
console.log("I'm a redhead duck");
}
quack(){
console.log("I can quack");
}
fly(){
console.log("I can fly");
}
eat(){
console.log("I eat fish and shrimps");
}
}
class RubberDuck extends Duck implements Quackable{
display(){
console.log("I'm a rubber duck");
}
quack(){
console.log("I can squeak");
}
}
class DecoyDuck extends Duck{
display(){
console.log("I'm a decoy duck");
}
}
有没有感觉代码变多了?
使用继承
时,继承超类Duck的子类拥有了Duck的所有方法,display()
、swim()
、quack()
和fly()
,我们更关注子类要覆盖的方法:quack()
和fly()
。
现在,将quack()
和fly()
从超类Duck中剥离而成为独立接口
:Quackable
和Flyable
,子类无法通过继承
来享有这些方法,要想有,可以,实现接口Quackable
和Flyable
吧。此时,不但没有解决上面遇到的问题,反而失去了继承
达到的代码复用
的优势。
怎么办呢?记住下面的原则吧。
- 把不变的部分和变化的部分分开,并封装变化的部分
- 少用继承,多用组合
- 不要针对实现编程,要针对接口编程
策略模式
为了避免意大利面条,分块放代码。
interface FlyBehavior{
fly();
}
class FlyWithWings implements FlyBehavior{
fly(){
console.log("I'm flying");
}
}
class FlyNoWay implements FlyBehavior{
fly(){
console.log("I cannot fly");
}
}
interface QuackBehavior{
quack();
}
class Quack implements QuackBehavior{
quack(){
console.log("I can quack");
}
}
class Squeak implements QuackBehavior{
quack(){
console.log("I can squeak");
}
}
class Mute implements QuackBehavior{
quack(){
console.log("I am a mute");
}
}
abstract class Duck{
flyBehavior:FlyBehavior;
quackBehavior:QuackBehavior;
constructor(f:FlyBehavior,q:QuackBehavior){
this.flyBehavior = f;
this.quackBehavior = q;
}
setFlyBehavior(f:FlyBehavior){
this.flyBehavior = f;
}
setQuackBehavior(q:QuackBehavior){
this.quackBehavior = q;
}
performFly(){
this.flyBehavior.fly();
}
performQuack(){
this.quackBehavior.quack();
}
abstract display();
swim(){
console.log("I'm swimming");
}
}
class MallardDuck extends Duck{
constructor(){
var f:FlyBehavior = new FlyWithWings();
var q:QuackBehavior = new Quack();
super(f,q);
}
display(){
console.log("I'm a mallard duck");
}
}
class RedHeadDuck extends Duck{
constructor(){
var f:FlyBehavior = new FlyWithWings();
var q:QuackBehavior = new Quack();
super(f,q);
}
display(){
console.log("I'm a redhead duck");
}
}
class RubberDuck extends Duck{
constructor(){
var f:FlyBehavior = new FlyNoWay();
var q:QuackBehavior = new Squeak();
super(f,q);
}
display(){
console.log("I'm a rubber duck");
}
}
class DecoyDuck extends Duck{
constructor(){
var f:FlyBehavior = new FlyNoWay();
var q:QuackBehavior = new Mute();
super(f,q);
}
display(){
console.log("I'm a decoy duck");
}
}
下面则是测试代码。
var mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.swim();
mallardDuck.performQuack();
mallardDuck.performFly();
console.log("-----------------分割线-----------------");
var redheadDuck = new RedHeadDuck();
redheadDuck.display();
redheadDuck.swim();
redheadDuck.performQuack();
redheadDuck.performFly();
console.log("-----------------分割线-----------------");
var rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.swim();
rubberDuck.performQuack();
rubberDuck.performFly();
console.log("-----------------分割线-----------------");
var decoyDuck = new DecoyDuck();
decoyDuck.display();
decoyDuck.swim();
decoyDuck.performQuack();
decoyDuck.performFly();
补充:举例和主要思想来自<HeadFirst设计模式>