Java设计模式系列:
- 设计模式入门:https://blog.youkuaiyun.com/u011863006/article/details/89223282
- 单例模式:https://blog.youkuaiyun.com/u011863006/article/details/84201592
本文代码地址:https://github.com/shelimingming/DesignPattern/tree/master/src/main/java/com/sheliming/strategy
Java各种技术、各种框架更新的速度越来越快,学习成本越来越高,但是我们学习Java要学习其中不变的部分,其中设计模式就是最高层次的抽象,是高出框架、语言的。所以学习的收益也是最高的,不会被时代淘汰,并且几乎在任何一个面试中都会被问到。
但是设计模式看似又比较抽象,比较难学,学会了也不会应用,最近重温《Head First设计模式》这本书,所以准备将其中的感悟结合平时的积累总结一下,写一个比较通俗易懂的设计模式系列。
首先不说什么是设计模式,先从一个简单的例子去感受下设计模式,这个例子也是我在大一的时候在一个讲座中听到的,当时完全不懂编程,但是听进去之后觉得说的确实很有道理,我们一起来感受一下:
一.鸭子游戏的故事
1.模拟鸭子游戏
某公司需要做一个模拟鸭子的游戏,游戏中会有各种鸭子,鸭子一边游泳,一边嘎嘎叫。
有一点面向对象基础的人就可以很快的撸出以下代码:
首先有一个鸭子的基类Duck,他是一个抽象类,具体的鸭子由子类实现。
package com.sheliming.strategy.step1;
public abstract class Duck {
public void quack() {
System.out.println("鸭子嘎嘎叫。。。");
}
public void swim() {
System.out.println("鸭子游泳。。。");
}
public abstract void display();
}
第一个子类绿头鸭,继承于Duck类,重写display方法。
package com.sheliming.strategy.step1;
public class GreenheadDuck extends Duck{
@Override
public void display() {
System.out.println("我是绿头鸭");
}
}
第二个子类红头鸭,红头鸭仅仅展示方法和绿头鸭不一样。
package com.sheliming.strategy.step1;
public class RedheadDuck extends Duck{
@Override
public void display() {
System.out.println("我是红头鸭");
}
}
第三个子类是橡皮鸭,橡皮鸭除了展示方法和其他的不一样,它的叫声也和其他鸭子不一样,是吱吱叫。
package com.sheliming.strategy.step1;
public class RubberDuck extends Duck{
//实现和父类不同,所以覆盖父类的方法
@Override
public void quack() {
System.out.println("吱吱叫。。。");
}
@Override
public void display() {
System.out.println("我是橡皮鸭");
}
}
类图如下:
到此,是不是感觉这个设计还是很完美的,相同的方法放在父类,不同于父类的方法子类重写。但是,继续往下看就会发现,这样的设计是有缺陷的。
2.鸭子增加会飞的功能
但是随着市场的发展,有很多类似的游戏都出现了,项目经理说我们的游戏需要让鸭子增加飞的功能来吸引更多的用户。作为面向对象开发的你完全不慌,只需要在基类Duck中增加fly()方法。
package com.sheliming.strategy.step2;
public abstract class Duck {
public void quack() {
System.out.println("鸭子嘎嘎叫。。。");
}
public void swim() {
System.out.println("鸭子游泳。。。");
}
public abstract void display();
public void fly() {
System.out.println("鸭子飞起来了。。。");
}
}
但是问题来了,在基类中增加了fly()的方法之后,所有子类都继承的该方法,都有飞的功能。但是还记得我们的橡皮鸭吗,它是不应该飞起来的。然而你又可以很机智的想到,只要像橡皮鸭覆盖quack()方法一样,覆盖fly()方法即可:
package com.sheliming.strategy.step2;
public class RubberDuck extends Duck {
//实现和父类不同,所以覆盖父类的方法
@Override
public void quack() {
System.out.println("吱吱叫。。。");
}
@Override
public void display() {
System.out.println("我是橡皮鸭");
}
//橡皮鸭不会飞,所以什么都不做
@Override
public void fly() {
}
}
但是这时又需要增加一种鸭子木头鸭,它不会飞也不会叫,这时你只能这么写
package com.sheliming.strategy.step2;
//木头鸭不会飞也不会叫,所以覆盖quack()和fly()方法
public class WoodDuck extends Duck{
@Override
public void display() {
System.out.println("我是木头鸭");
}
@Override
public void quack() {
}
@Override
public void fly() {
}
}
这样的代码你会发现,改变父类的一个行为,会牵一发而动全身,造成其他鸭子不想要的改变。
3.使用接口试试看
这时你会发现继承并不能完全解决问题,这时你想到了接口。首先fly和quack应该设计成两个接口,拥有该功能的鸭子去实现这两个接口,没有该功能的鸭子就不去实现。
代码如下:
package com.sheliming.strategy.step3.capability;
public interface Flyable {
void fly();
}
package com.sheliming.strategy.step3.capability;
public interface Quackable {
void quack();
}
鸭子基类只需要实现不变的方法,其他的方法放在接口中:
package com.sheliming.strategy.step3.Duck;
public abstract class Duck {
public void swim() {
System.out.println("鸭子游泳。。。");
}
public abstract void display();
}
绿头鸭会飞会叫,需要实现Flyable, Quackable接口:
package com.sheliming.strategy.step3.Duck;
import com.sheliming.strategy.step3.capability.Flyable;
import com.sheliming.strategy.step3.capability.Quackable;
public class GreenheadDuck extends Duck implements Flyable, Quackable {
@Override
public void display() {
System.out.println("我是绿头鸭");
}
public void fly() {
System.out.println("鸭子飞起来了。。。");
}
public void quack() {
System.out.println("鸭子嘎嘎叫。。。");
}
}
橡皮鸭只会叫,不会飞,只需要实现Flyable接口:
package com.sheliming.strategy.step3.Duck;
import com.sheliming.strategy.step3.capability.Quackable;
public class RubberDuck extends Duck implements Quackable {
public void quack() {
System.out.println("吱吱叫。。。");
}
@Override
public void display() {
System.out.println("我是橡皮鸭");
}
}
木头鸭不会飞也不会叫,就不用实现接口:
package com.sheliming.strategy.step3.Duck;
//木头鸭不会飞也不会叫
public class WoodDuck extends Duck {
@Override
public void display() {
System.out.println("我是木头鸭");
}
}
但是,问题来了!!我们这个游戏一共有99只鸭子,很多鸭子都会飞和会叫,他们都要实现了Flyable, Quackable接口(代码无法复用),更糟的是,如果需要修改的话要修改几十个地方。这简直就是一场噩梦!
四. 使用组合代替继承
在设计模式中组合往往比继承更加灵活(组合就是一个对象包含另一个对象的引用),我们用组合来试一下。首先我们可以将不变的东西抽出来,比如鸭子的行为,FlyBehavior、QuackBehavior,而具体的行为就是实现了其中的接口的类。类图如下:
上代码!!!
飞的接口:
package com.sheliming.strategy.step4.capablity;
public interface Flyable {
void fly();
}
使用翅膀飞的行为:
package com.sheliming.strategy.step4.capablity;
public class FlyWithWings implements Flyable {
public void fly() {
System.out.println("鸭子飞起来了。。。");
}
}
不会飞的行为:
package com.sheliming.strategy.step4.capablity;
public class FlyNoWay implements Flyable{
public void fly() {
System.out.println("鸭子不会飞。。。");
}
}
叫也是类似的实现:
package com.sheliming.strategy.step4.capablity;
public interface Quackable {
void quack();
}
嘎嘎叫:
package com.sheliming.strategy.step4.capablity;
public class QuackWithGaGa implements Quackable {
public void quack() {
System.out.println("鸭子嘎嘎叫。。。");
}
}
不会叫:
package com.sheliming.strategy.step4.capablity;
public class QuackNoNoise implements Quackable{
public void quack() {
System.out.println("鸭子不会叫。。。");
}
}
下面就是鸭子的基类,有两个成员变量,flyable、quackable,这就是组合。注意这里是使用的接口,这就是面向接口编程,子类具体使用什么实现类在子类中实现,父类只能看到接口。将具体的fly(),quack()方法改成performFly(),performQuack()方法,调用成员变量中的方法实现。
package com.sheliming.strategy.step4.duck;
import com.sheliming.strategy.step4.capablity.Flyable;
import com.sheliming.strategy.step4.capablity.Quackable;
public abstract class Duck {
protected Flyable flyable;
protected Quackable quackable;
public void swim() {
System.out.println("鸭子游泳。。。");
}
public abstract void display();
//调用Flyable来实现飞,代替fly方法
public void performFly() {
flyable.fly();
}
//调用Quackable来实现叫,代替quack方法
public void performQuack() {
quackable.quack();
}
}
我们来看下子类:
具体的鸭子在初始化中将父类中的两个变量初始化为自己需要的能力:
package com.sheliming.strategy.step4.duck;
import com.sheliming.strategy.step4.capablity.FlyWithWings;
import com.sheliming.strategy.step4.capablity.QuackWithGaGa;
public class GreenheadDuck extends Duck{
public GreenheadDuck() {
this.flyable = new FlyWithWings();
this.quackable=new QuackWithGaGa();
}
@Override
public void display() {
System.out.println("我是绿头鸭");
}
}
package com.sheliming.strategy.step4.duck;
import com.sheliming.strategy.step4.capablity.FlyNoWay;
import com.sheliming.strategy.step4.capablity.QuackWithGaGa;
public class RubberDuck extends Duck{
public RubberDuck() {
this.flyable =new FlyNoWay();
this.quackable = new QuackWithGaGa();
}
@Override
public void display() {
System.out.println("我是橡皮鸭");
}
}
package com.sheliming.strategy.step4.duck;
import com.sheliming.strategy.step4.capablity.FlyNoWay;
import com.sheliming.strategy.step4.capablity.QuackNoNoise;
//木头鸭不会飞也不会叫
public class WoodDuck extends Duck {
public WoodDuck() {
this.flyable = new FlyNoWay();
this.quackable = new QuackNoNoise();
}
@Override
public void display() {
System.out.println("我是木头鸭");
}
}
现在我们来用一下:
package com.sheliming.strategy.step4;
import com.sheliming.strategy.step4.duck.Duck;
import com.sheliming.strategy.step4.duck.GreenheadDuck;
import com.sheliming.strategy.step4.duck.RubberDuck;
import com.sheliming.strategy.step4.duck.WoodDuck;
public class Test {
public static void main(String[] args) {
Duck greenheadDuck = new GreenheadDuck();
greenheadDuck.display();
greenheadDuck.performFly();
greenheadDuck.performQuack();
Duck rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.performFly();
rubberDuck.performQuack();
Duck woodDuck = new WoodDuck();
woodDuck.display();
woodDuck.performFly();
woodDuck.performQuack();
}
}
运行结果:
我是绿头鸭
鸭子飞起来了。。。
鸭子嘎嘎叫。。。
我是橡皮鸭
鸭子不会飞。。。
鸭子嘎嘎叫。。。
我是木头鸭
鸭子不会飞。。。
鸭子不会叫。。。
好了,有人会问了,这样的设计的好处是什么呢??
首先,修改某种鸭子的行为时,只需要将该鸭子的成员变量换一个值就可以了,甚至可以加一个set方法去动态的修改。
其次,在某种能力需要修改的时候,只需要修改具体能力的类就可以了,只需要改一处,所有拥有该能力的都会修改。
比如,我们我们的木头鸭是不会飞的,但是我们给他加了飞行器之后就可以飞了。
首先增加一个坐火箭飞的类:
package com.sheliming.strategy.step4.capablity;
public class FlyWithRocket implements Flyable {
public void fly() {
System.out.println("鸭子坐着火箭飞起来了。。。");
}
}
在鸭基类中增加set方法
package com.sheliming.strategy.step4.duck;
import com.sheliming.strategy.step4.capablity.Flyable;
import com.sheliming.strategy.step4.capablity.Quackable;
public abstract class Duck {
protected Flyable flyable;
protected Quackable quackable;
public void setFlyable(Flyable flyable) {
this.flyable = flyable;
}
public void setQuackable(Quackable quackable) {
this.quackable = quackable;
}
public void swim() {
System.out.println("鸭子游泳。。。");
}
public abstract void display();
//调用Flyable来实现飞,代替fly方法
public void performFly() {
flyable.fly();
}
//调用Quackable来实现叫,代替quack方法
public void performQuack() {
quackable.quack();
}
}
调用一下:
package com.sheliming.strategy.step4;
import com.sheliming.strategy.step4.capablity.FlyWithRocket;
import com.sheliming.strategy.step4.duck.Duck;
import com.sheliming.strategy.step4.duck.WoodDuck;
public class Test2 {
public static void main(String[] args) {
Duck woodDuck = new WoodDuck();
woodDuck.display();
woodDuck.performFly();
woodDuck.setFlyable(new FlyWithRocket());
woodDuck.performFly();
}
}
结果:
我是木头鸭
鸭子不会飞。。。
鸭子坐着火箭飞起来了。。。
二.鸭子故事的背后
怎么样?有没有点感觉了?恭喜你!你已经学会了一个设计模式:策略模式!
策略模式就是定义了一些算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
到这里需要说一下到底什么是设计模式了:设计模式就是一些程序员在以往的开发中总结的一些场景的最佳实现。1995 年,GoF(Gang of Four,四人组/四人帮)这几位大佬给总结了一下,出了一本书《设计模式:可复用面向对象软件的基础》,一共有23中设计模式。
后面的系列节目中会陆续的和大家介绍,尽情期待~~