程序开发 ·

程序设计模式之策略模式

做程序员也有段时间了,发现自己对所谓的各种设计模式和设计原则一直处在一个似懂非懂个尴尬局面。因此决定认真去学习一下各种设计模式。以后会写关于设计模式系列的文章以便记录学习过程的遇到的问题和心得,体会。文章中主要是参考Head First一书。

假如我们需要设计一个人类的超类,那么你就会想把人类有共同属性和行为写进一个超类里面。

Person{
//其他共同的属性和行为省略。。。。
    eat(){
        //吃米饭
    }//为了方便就用吃东西的行为来阐述
}

那么这样所有的子类都有eat这个行为,这样设计目前看还是没有问题的,那么以后呢?

  • 当需要添加一个玩具人的子类,但是它没有eat这个行为
  • 当在所有子类中,有的吃面,有的吃水果,有的吃米饭,那么上面这样写肯定不行了
  • 当子类一会吃面,一会吃饭,又一会吃水果呢?

这个时候你就会觉得继承好像不能很好解决上述问题。使用继承会有哪些缺点呢?

  • 代码的复用性不高。
  • 很难知道所有子类的全部行为
  • 运行时的行为不容易改变
  • 不容易维护,再超类中修改一些东西,可能导致子类变,容易出bug。

那么这个时候你就会想到接口,我们把eat这个行为做成一个接口类,让子类自己觉得它的eat行为。这好像解决了第一个问题,但是代码的复用还是不好,因为接口无法实现行为只能子类自己实现。而且无法动态改变子类eat的行为。那么这个时候你就会发现当合理的使用设计模式和设计原则是一件非常nice的事情。那么下面我们就使用一些设计原则和模式来解决上面的问题。

设计原则之一:抽象变化之处,不要使其和不需要变化的混淆。

那么根据以上原则,我们需要把eat写成一个单独的接口是没错的。

IEat{
    eat()
}
设计原则之一:针对接口编程,而不是针对实现编程

之前的做法eat行为都是子类自己实现,没办法改变其行为,这就是针对实现编程。针对接口编程也叫针对超类编程。如下

//针对实现编程
Dog d = new Dog()
dog.quack()

//针对接口编程
Animal a = new Dog()
a.quack()

通常由一个抽象类或者是接口的超类作为声明类而不是子类。更好的是子类的实例化不需要通过硬编码的方式进行,如new Dog(),而是在运行的时候才指定子类的类型。

Animal a = getAnimal()
a.quack()

我们不关心是哪个子类,只关心子类自己知道如何正确的实现其行为(quack)。那么针对IEat接口编程,我们专门创建eat的行为类,而不是让子类自己实现。

EatFruit implement IEat{
    eat(){
        //吃水果的行为
    }
}

EatNoodle implement IEat{
    eat(){
        //吃面条
    }
}

等等具体的eat行为类。然后我们整合下Person类

Person{
    IEat iEat;
    //子类不亲自处理eat的行为,只是委托给实现IEat类型的对象
    performEat(){
        iEat.eat()
    }
    
    setIEat(IEat i){
        iEat = i
    }
}

Man extends Person{
//引用实现IEat对象的类型
    Man(){
        iEat = new EatFruit();
    }
}
main(){
    Person p = new Man()
    p.performEat()
    //动态改变子类的eat行为
    p.setIEat(new EatNoodle())
    p.performEat()
}

那么上面我通过接口,接口的实现类,子类引用接口的实现类来解决上面的问题,各种不同的eat行为代码可以复用,并且可以改变子类eat的行为。这样完成后比之前单独继承或者实现接口要好很多。在上面我们又成功使用了一个设计原则。

设计原则之一:多用组合,少用继承。

上面eat的行为并不是通过继承超类得来的,而是和适当的eat行为对象组合而来的。这就是组合。

这次我使用的是设计模式中的:策略模式

策略模式:定义了算法族,并且封装它们,让它们之间可以相互替换,并且是算法的变化独立于使用算法的客户

书上得来终觉浅,绝知此事要躬行!!!!

参与评论