设计模式-入门
什么是设计模式?
我发现有很多人,都没听过设计模式,设计模式很重要,甚至面试的时候也会问你会不会设计模式。可见设计模式是每个程序员必须具备的技能。那么什么是设计模式呢?设计模式是为了解决项目中各种需求和问题的一种代码编写方式,面向对象语言都可以使用设计模式来编写自己的代码,面向过程语言如:C语言就不能使用设计模式,因为设计模式是针对面向对象的开发方式。想了解多的设计模式,点我。
常见的设计模式
比较常见的设计模式:
- 工厂模式
- 单例模式
- 代理模式
- MVC模式
也许还有很多比较常用的,但以上这些肯定是使用频率较高的。
第一个设计模式-策略模式
之前说过,设计模式是因为需求而被发明出来的,所以在说策略模式之前,我先来一个需要(例子来自**《Head First 设计模式》**)。
首先有一套相当成功的模拟鸭子游戏。游戏中会出现各种鸭子,一边游泳,一边呱呱叫。此系统的内部使用了标准的OO技术(面向对象),并让各种鸭子继承超类。
代码不是很难,都是最基础的
代码是这样的:
鸭子类(Duck):
/**
* 鸭子类
*/
abstract class Duck {
public void quack(){
System.out.println("呱呱叫");
};
public void swim(){
System.out.println("游泳。。");
};
public abstract void display();
}
所有的鸭子都会呱呱叫(quack)也会游泳(Swim),所有超类负责处理这部分的实现代码。因为每一种鸭子的外观都不同,所以display()
方法是抽象的。
然后我们创建两个子类继承**鸭子(Duck)**类吧
绿头鸭(MallardDuck)
/**
* 绿头鸭
*/
public class MallardDuck extends Duck{
@Override
public void display() {
System.out.print("我是绿头鸭,不准笑!");
}
}
红头鸭(RedheadDuck)
public class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("我是红头鸭!");
}
}
需求:让鸭子会飞!
实现需求
使用OO设计的话,很容易满足这个需求,只需要把超类添加一个方法,fly()
就可以使用了。
代码
/**
* 鸭子类
*/
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("飞行");
}
}
其他的代码根本不需要做改变,当你刚刚尝到面向对象的甜头,而现实就会毫不犹豫的打你一巴掌。过不了几天,你的经理会给你打电话,告诉你被辞职了。
为什么?
橡皮鸭也是属于鸭子,也会继承鸭子类(Duck)
,但是你在超类中(鸭子类
)加了飞行方法fly()
,使得橡皮鸭也会飞了,并且橡皮鸭的叫声也不是呱呱叫,这些问题,8害的经理在展示的时候很丢脸,但是你苦苦哀求,经理答应在给你一次机会,这时候你会怎么做?
继承
既然是面向对象做的系统,那么我使用覆盖,把橡皮鸭中的fly()
方法覆盖,里面什么都不做,就像这样:
/**
* 橡皮橡鸭
*/
public class MallardDuck extends Duck{
public void fly(){
//什么都不做
}
public void quack(){
System.out.println("吱吱叫");
}
@Override
public void display() {
System.out.print("我是橡皮鸭");
}
}
恩,这样简直完美,准备打电话告诉这个问题如已经解决的时候,又是一个问题出来了,如果以后加入诱饵鸭,不会叫也不会飞,难道也覆盖吗?以后维护怎么办?代码是否出现大量沉余。并且很难知道所有鸭子的全部行为。
接口
既然继承不能解决问题,那么如果用接口呢?
我们把不一样的代码,单独的抽取出来,如
quack()
方法,fly()
方法,这两种方法单独的抽取出来,并将它们整合成两接口,Flyable
和Quackable
接口。然后让需要叫和需要飞的鸭子实现该接口这样是否就能解决代码沉余了呢?
代码
飞行接口:
public interface Flyable {
public void fly();
}
叫接口:
public interface Quackable {
public void quack();
}
然后在超类(鸭子类)中的代码把飞行和发声的代码删掉,子类需要飞行的就实现飞行接口,需要发生的就实现叫接口,两者都需要的就多实现,不都需要实现的则都不用实现。这里就把子类和超类的代码发一下。
鸭子类(Duck):
/**
* 鸭子类
*/
abstract class Duck {
public abstract void display();
}
绿头鸭(MallardDuck)
/**
* 绿头鸭
*/
public class MallardDuck extends Duck implements Flyable,Quackable{
@Override
public void quack() {
System.out.println("呱呱叫。。");
}
@Override
public void fly() {
System.out.println("绿鸭子飞了。");
}
@Override
public void display() {
System.out.print("我是绿头鸭,不准笑!");
}
}
问题:
这个办法虽然能解决,不能鸭子满足不同的需求,也只到所有鸭子的行为,但是这并没有解决代码沉余问题
策略模式登场!
如果换个想法,虽然是鸭子游戏,但是以后可能会更新,变的有青蛙或者其他动物,那么是不是也会有叫这个行为
或者有飞这个行为
?又或者有更多的行为呢?
带着这个问题,我们使用策略模式
就可以解决。
首先我们把需要的行为飞、叫、跑
单独抽取出来,以便以后能够共享使用,避免代码沉余。这些行为我们在一次次改进后已经单独的抽取出一个接口了。(如果你不知道已经改进成接口,请你往上面仔细看代码),但是实现这些接口的不在是鸭子,而是特有的行为,不懂意思?那就看代码吧。
在该例子中,行为有:叫、飞;而每只鸭子叫的方式,和飞的方式不一样,所有我们把它们不一样的地方单独封装成一个类。
呱呱叫类
public class Quack implements Quackable{
@Override
public void quack() {
System.out.println("呱呱叫。。");
}
}
橡皮鸭是“吱吱叫”的,所以我们在创建一个吱吱叫的类
吱吱叫类
public class Squeak implements Quackable {
@Override
public void quack() {
System.out.println("吱吱叫。");
}
}
木头鸭,不会叫也不会飞,这里就不用实现,也没法实现,就算实现了也没有意义。好吧,竟然做教程那么就要做全套是吧!
不会叫类
public class MuteQuack implements Quackable {
@Override
public void quack() {
//什么都不做,不会叫
System.out.println("不会叫啊");
}
}
叫的行为,目前已经全部实现了,现在开始实现飞的行为
用翅膀飞类
public class FlyWithWings implements Flyable {
@Override
public void fly() {
System.out.println("翅膀扇动飞了。");
}
}
不会飞类
public class FlyNoWay implements Flyable {
@Override
public void fly() {
System.out.printf("不会飞啊。。");
}
}
恩,所有的行为已经全部实现了,现在该思考怎么和子类组合在一起。
既然所有的鸭子都继承一个超类(鸭子类
Duck
),那么我们是否可以在鸭子类上做手脚呢?
那就从超类做手脚吧,我们知道所有的鸭子都有飞和叫的行为,(不会叫的、不会飞的已经封装成一个类了),那么我们就在超类中,添加飞和叫的方法。
鸭子类(Duck):
//请思考后在继续往下面看
abstract class Duck {
public void quack(){
这里怎么写?
};
public void swim(){
System.out.println("游泳。。");
};
public abstract void display();
public void fly(){
这里怎么写?
}
}
如果看到这里,就带代表你已经思考过了,那么每只鸭子的飞、叫行为都不一样,可唯一能确定的是这些行为已经封装了一个类,并且都实现了共同的接口,那么我们调用飞、叫方法还不手到勤来?
首先把飞、叫的接口当做鸭子类的属性,然后在鸭子类中的fly()
和quack()
中调用接口中的方法就可以了。
鸭子类(Duck):
abstract class Duck {
//飞的接口
Flyable flyBehavior;
//叫的接口
Quackable quackBehavior;
public void quack(){
//调用
quackBehavior.quack();
};
public void performFly(){
//调用
flyBehavior.fly();
}
public void swim(){
System.out.println("游泳。。");
};
public abstract void display();
}
我们不需要飞和叫是怎么实现的,我们只需要知道这个接口有这个方法,所有我们只管调用就行,具体怎么实现,就交给子类啦。
红头鸭
/**
* 红头鸭翅膀飞,发出呱呱叫的声音
*/
public class RedHeadDuck extends Duck {
public RedHeadDuck() {
flyBehavior=new FlyWithWings();
quackBehavior=new Quack();
}
@Override
public void display() {
System.out.println("我是红头鸭!");
}
}
橡皮鸭
/**
* 橡皮鸭不会飞,发出吱吱叫的声音
*/
public class RubberDuck extends Duck{
public RubberDuck() {
flyBehavior=new FlyNoWay();
quackBehavior=new Squeak();
}
@Override
public void display() {
System.out.println("这是橡皮鸭");
}
}
其他鸭子只需要在构造函数中创建一个行为就可以了,自己动手写绿头鸭,和木头鸭的代码吧。
疑问
虽然解决了问题,但是如果要给木头鸭一个飞的行为呢?虽然木头鸭不会飞,但是我们可以给它装个火箭装置让它飞,或者开始不让它飞,等客户充钱购买了火箭装置在让它飞。怎么做?
解答
不要忘了,既然我们使用了设计模式那么现在的代码是很有弹性的,我们可以给超类(鸭子类)一个Set
方法,这样问题不就解决了吗?而且所有的子类也无需修改代码。
鸭子类(Duck):
abstract class Duck {
Flyable flyBehavior;
Quackable quackBehavior;
public void setFlyBehavior(Flyable flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(Quackable quackBehavior) {
this.quackBehavior = quackBehavior;
}
public void quack(){
quackBehavior.quack();
};
public void performFly(){
flyBehavior.fly();
}
public void swim(){
System.out.println("游泳。。");
};
public abstract void display();
}
看看是如何调用的吧:
//橡皮鸭
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.performFly();
//用户冲了200美元,购买了火箭配置
//这里没有写火箭配置的代码,你们可以试着写,就和写飞行类一样
rubberDuck.setFlyBehavior(new FlyWithWings());
rubberDuck.performFly();
运行结果:
我不会飞。。
翅膀扇动飞了。
好了,策略模式终于写完, 如果有错误 请私信给我,哦,对了,策略模式的正式定义在下面,如果能理解它,那么你就学到了策略模式,如果没有理解,那么请认真重复阅读几遍吧,然后自己多动手敲代码,还是不理解?OMG,我也没办法了。
策略模式定义
策略模式定义了算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户
转载需注明!