一、为什么学习设计模式?
1、设计模式已经成为了软件开发人员的“标准词汇”
很多软件开发人员在相互交流的时候,只是使用了设计模式的名字而没有表述这种设计模式的具体内容。就如同我们在使用汉语里的成语一样,我们在使用成语的时候,一般情况下是不会将这个成语的典故讲出来的。举个例子来说,开发人员A遇到了一个问题,然后去问开发人员B,B告诉A,使用XXX设计模式就可以了,如果A不懂这个设计模式的话,那么A还是不知到该怎么解决这个问题,那么A和B的交流也将变得很困难。所以,为了团队内部的轻松交流,学习设计模式还是很有必要的。
2、学习设计模式是个人技能快速提高的捷径
设计模式是很多有经验的编程大师的心血累积,他们在以往的编程过程中遇到了很多问题,然后通过不断的摸索、累积,总结了各种不同的方法来解决不同的问题。我们在遇到类似问题的时候,就可以灵活的使用前辈的经验来轻松解决。日积月累,我们的编程水平自然会上升一个台阶。
3、不用重复设计
设计模式是解决某些特定问题的特定方案。当我们遇到类似问题的时候,我们就可以将与之相关的设计模式拿出来解决我们目前的问题。因此,我们可以讲,这肯定比我们自己从头来想辙解决问题的方案要好。使用成熟的设计模式可以帮助我们减少很多无谓的开发时间,也会使我们的代码更具有扩展性和易维护性。
二、怎么学习设计模式?
这个大家可以谷歌、百度,最最标准的答案就是多思考、请练习。给大家分享下我的学习方法:找一本比较权威的书,刚开始尽量浅显易懂的。针对一种设计模式,先看理论知识,然后思考;再跟着上面的例子自己走一遍,然后想想自己遇到类似的问题通常是怎么解决的,用了这种设计模式有什么好处,这个可以暂时想不通。然后自己动手按照这个设计模式的思想完成一个类似的问题。给大家推荐一本书:《HeadFirst 设计模式》。
三、设计模式开讲
这里我主要跟大家分享几个设计模式的学习经验,至于几个,取决于时间。我这里自己准备了三个例子。
1、策略模式(StrategyPattern)
给出问题:设计一款鸭子游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。这个问题出来,我们根据以往面向对象的编程经验,第一时间肯定会想到,设计一个鸭子的超类。并让各种鸭子继承此类。
-
DUCK
quack()
swim()
display()
我们在超类里给出了三个方法。其中,因为每个鸭子都会游泳及呱呱叫,因此我们在超类里实现了quack()以及swim()这两个方法,子类直接继承超类便拥有了这两个特征。又因为每个鸭子的外形不同,所以我们给出了抽象方法display(),这就需要我们在子类中对这个抽象方法进行重写。OK,目前为止这个方案还是蛮不错,很好的解决了我们的问题。接下来,再下一个产品的迭代中,我们加入了新的需求,我们需要加入会飞的鸭子。这个需求对我们来说好像没有什么难度:重写DUCK类即可:
-
DUCK
quack()
swim()
display()
fly()
我们也只是在超类里加了一个fly()方法而已,很简单嘛。但是,当我们拿出我们的程序进行演示的时候,我们发现:游戏里有一种橡皮鸭子竟然也会飞!!!这就不对了,我们忽然想到,游戏里并不是所有的鸭子都可以飞。这时我们意识到对程序局部的修改,影响的可不单单是局部!出现了这个问题,我们就要考虑解决的方法。A可能想到了一个解决方法:我们可以在橡皮鸭子这个子类里将fly()给覆盖掉,我们可以重写fly()方法,让它什么都不做:
publicvoid fly(){
//覆盖,什么都不做
}
这看起来很不错,但是这时候老大站出来了,温和的对你提出了一个问题,如果以后我要加很多不会飞的鸭子,你都要这样一个一个的去覆盖吗?如果我以后再加入一些不会飞也不会叫的鸭子,你都要这样吗?这时候,你是不是要挠头了?是不是很期盼设计模式能够骑着白马来帮你解决这个棘手的问题?
好吧,让我们从头开始。现在我们已经知道,依靠继承是不能很好的解决我们的问题的。我们需要知道一些设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。把会变化的部分取出来“封装”起来,好让其他部分不受影响。这两句话是所有设计模式的精髓,所有的设计模式都提供了一套的方法来让“系统中的部分改变不会影响到其他部分”。
所以我们开始行动,把DUCK类中变化和不变化的部分分开。我们发现在DUCK类中,除了fly()和quack()方法外,其他的一切还算正常,因此我们只需要将这两个方法拿出来,DUCK类其他的部分就可以不用改变。在我们设计鸭子的行为时,我们希望一切都具有弹性。这时候,我们给出了另外一个设计原则:针对接口编程,而不是针对实现编程。所谓“针对接口编程”,其实就是针对超类型编程。这里的接口有多个含义,接口是一个概念,也是java的Interface构造。针对接口编程,关键在于多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行具体的行为,不会被绑死在超类型的行为里。“针对超类型编程”可以更明确的表述为“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要具体实现此超类型的类所产生的对象,都可以指定给这个对象。这也意味着,声明类时不用理会以后执行时的真正对象”。下面给出一个简单的多态例子:假设有一个抽象类Animal,有两个具体的实现(Dog与Cat)继承自Animal。做法如下:
针对实现编程:Dog d=newDog(); d.bark();
针对接口/超类型编程:Animalanimal=new Dog(); animal.makeSound();
ok,这些是基础的面向对象的知识,下面我们开始解决我们的问题。首先将fly和quack分离出来。我们用两个接口来实现。
Public interface FlyBehavior{
public void fly();
}
public interface QuackBehavior{
public void quack();
}
接下来,我们针对“飞”这个接口来实现两个行为类:FlyWithWings(有翅膀可以飞)和FlyNoWay(别想飞)。
Public Class FlyWithWings implements FlyBehavior{
public void fly(){
System.out.println(“Ihave Wings so I can Fly!”);
}
}
public class FlyNoWay implementsFlyBehavior{
public void fly(){
System.out.println(“Icant fly never”);
}
}
然后,我们针对“呱呱叫”这个接口来实现两个行为类:Quack(可以叫)和MuteQuack(这个真不可以叫):
public class Quack implementsQuackBehavior{
public void quack(){
System.out.println(“Quack”);
}
}
public class MuteQuack implementsQuackBehavior{
public void quack(){
System.out.println(“Ireally cant Quack!”);
}
}
到目前为止,我们已经把要改变的从DUCK类里分离出来了。下面我们开始构建我们的超类DUCK。
Publicabstract class Duck{
//为行为接口类型声明两个引用变量,所有鸭子子类都继承它们。
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){}
public abstract voiddisplay();//抽象方法,所有的子类都要重写它。
Public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
public void swim(){
System.out.println(“AllDucks can swim!”);
}
}
OK,接下来我们编写一个子类TestDuck继承自抽象类Duck.
Public class TestDuck extends Duck{
public TestDuck(){
quackBehavior=newQuack();
flyBehavior=newFlyWithWings();
}
publicvoid display(){
System.out.println(“Iam a testDuck!”);
}
}
接下来就是见证奇迹的时刻,我们测试下之前的代码:
public class TestProject{
public static voidmain(String[] args){
Duck duck=newTestDuck();//还记得吗,这是针对接口/超类型编程。
duck.display();
duck.performQuack();
duck.performFly();
}
}
输出的答案,大家肯定都很清楚了。
I am a testDuck!
Quack
Ihave Wings so I can Fly!
这样我们的工作似乎是完成了,我们不妨多一些思考,在构造函数里实现实例化够优雅吗?我们是不是可以在Duck里加入一些方法,使我们可以动态的设定行为呢?答案很简单,为什么不可以呢?
我们可以在Duck类加入两个新的方法:
public voidsetFlyBehavior(FlyBehavior fb){
flyBehavior=fb;
}
public voidsetQuackBehavior(QuackBehavior qb){
quackBehavior=qb;
}
我们的游戏又加入了一种模拟鸭ModelDuck:
public void ModelDuck extends Duck{
public ModelDuck(){
flyBehavior=newFlyNoWay();//绝对不会飞的鸭子
quackBehavior=newQuack();//这个会叫
}
public voiddisplay(){//这个方法必须重载
System.out.println(“iam a model duck!”);
}
}
然后我们建立一个新的FlyBehavior类型:
public class FlyRocketPoweredimplements FlyBehavior{
public void fly(){
System.out.println(“Ican fly with a rocket!”);
}
}
最后我们就可以在我们的测试工程里进行下测试:
publicclass TestProject{
public static voidmain(String[] args){
Duck duck=newTestDuck();//还记得吗,这是针对接口/超类型编程。
duck.display();
duck.performQuack();
duck.performFly();
//以下是新加的
Duckmodel=new ModelDuck();//这个你懂的
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}
这个输出是什么?
Icant fly never
Ican fly with a rocket!
我们整个鸭子游戏的编程已经差不多完成了,你有没有发现我们在编写的过程中,用到了很多“HAS-A(有一个)”关系,避免用到“IS-A(是一个)”关系。OK,下面是我们就要说到的第三个设计原则:
多用组合,少用继承。