设计模式学习系列


一、为什么学习设计模式?

1、设计模式已经成为了软件开发人员的“标准词汇”

很多软件开发人员在相互交流的时候,只是使用了设计模式的名字而没有表述这种设计模式的具体内容。就如同我们在使用汉语里的成语一样,我们在使用成语的时候,一般情况下是不会将这个成语的典故讲出来的。举个例子来说,开发人员A遇到了一个问题,然后去问开发人员BB告诉A,使用XXX设计模式就可以了,如果A不懂这个设计模式的话,那么A还是不知到该怎么解决这个问题,那么AB的交流也将变得很困难。所以,为了团队内部的轻松交流,学习设计模式还是很有必要的。

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类其他的部分就可以不用改变。在我们设计鸭子的行为时,我们希望一切都具有弹性。这时候,我们给出了另外一个设计原则:针对接口编程,而不是针对实现编程。所谓“针对接口编程”,其实就是针对超类型编程。这里的接口有多个含义,接口是一个概念,也是javaInterface构造。针对接口编程,关键在于多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行具体的行为,不会被绑死在超类型的行为里。“针对超类型编程”可以更明确的表述为“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要具体实现此超类型的类所产生的对象,都可以指定给这个对象。这也意味着,声明类时不用理会以后执行时的真正对象”。下面给出一个简单的多态例子:假设有一个抽象类Animal,有两个具体的实现(DogCat)继承自Animal。做法如下:

针对实现编程:Dog d=newDog(); d.bark();

针对接口/超类型编程:Animalanimal=new Dog(); animal.makeSound();

ok,这些是基础的面向对象的知识,下面我们开始解决我们的问题。首先将flyquack分离出来。我们用两个接口来实现。

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,下面是我们就要说到的第三个设计原则:

多用组合,少用继承。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值