打赏

继承、组合和接口用法——策略模式复习总结

前言——为什么继承不被优先推荐使用

先看这样一个案例——有一群鸭子,有的鸭子会游泳,有的鸭子会呱呱叫,每一种鸭子的外貌都不同。

第一版——使用继承

RD 设计了一个鸭子类,作为所有鸭子的超类。鸭子会呱呱叫(Quack)、也会游泳(Swim),那么由超类负责处理这部分的实现, 还有一个负责展示鸭子的外貌的 display 方法,它是抽象的,由各个具体的鸭子描述自己的外貌。代码如下:

public abstract class Duck {
    public void quack() {
        System.out.println("呱呱叫");
    }

    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();
}
 
/////////////////////////////////
public class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外观是绿头");
    }
}
 
/////////////////////////////////
public class RedheadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外观是红头");
    }
}

增加需求

产品:经过市场调研,发现市面上很多类似的系统,都实现了鸭子飞翔的功能,为了保持竞争力,希望我们的鸭子也能飞

RD :只要在 Duck 抽象类中加上 fly() 方法,然后让所有鸭子都继承fly(),就ok了

问题:并不是所有的鸭子都必须会飞,这也符合产品(动物)的多样性原理。故实际的实现中,所有的鸭子都继承了这个超类,从而都有了(有的是被动的)飞翔的能力,这显然是不好的设计,甚至后患无穷。

当涉及“维护”时,为了“复用”目的而使用继承,并不完美

实际开发中经常见有人这样做——子类如果不需要父类的某个方法,就强行覆盖。

问题:利用继承来提供 Duck 的行为,会导致下列的一些问题:

1、代码在多个子类中无意义的重复——比如玩具鸭子不需要飞翔,但是还得必须显示的覆盖这部分的代码。

2、运行时的行为不容易改变——代码都继承到了子类,等于代码是被写死了。

3、无法灵活的扩展——比如,新加入了木头的玩具鸭子,木头的鸭子不会呱呱叫,也不会飞翔,这就仍然需要很笨重的给木头鸭子覆盖呱呱叫+飞翔的方法,让其什么都不做。

4、很难知道所有鸭子全部真正的行为,无法容易的得到,某个鸭子类,到底需要实现的行为是什么。

5、牵一发动全身,造成其他鸭子不想要的改变——比如又要给鸭子增加跳舞的行为,那么所有的不需要跳舞的鸭子,也都要去修改…… 

第二版——使用接口+组合

第一版方案不是很完美,所以需要一个更清晰的策略,只让部分鸭子类型可飞或可叫。可以把 fly() 从 Duck 超类中抽象出来,用一个 FlyAble 接口来实现,让只有会飞的鸭子实现此接口,同样的方式,也可以设计一个 QuackAble 接口。

public interface Quackable {
    void quack();
}
 
////////////////////
public interface Flyable {
    void fly();
}
 
/////////////////////
public abstract class Duck {
    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();
}
 
////////////////////
public class MallardDuck extends Duck implements Flyable, Quackable {
    @Override
    public void display() {
        System.out.println("外观是绿头");
    }

    @Override
    public void fly() {
        System.out.println("飞翔");
    }

    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}

为了使用接口而使用接口——效果会适得其反

在本案例中,虽然使用 Flyable 与 Quackable 接口可以解决第一版继承带来的问题,但是却产生了代码无法复用的新问题。因为 Java 的接口在 1.8 之前,不能具有实现代码,所以除非你能肯定所有使用这个系统的人都是使用的 JDK 8及其以后的版本,否则继承接口无法 100% 确保达到代码的复用目的。

这就意味着:无论何时你需要修改某个行为,你必须得往下追踪,并在每一个定义此行为的类中修改它,一不小心,可能会造成新的错误。幸运的是,有一些设计原则,恰好适用于此状况。

第一设计原则:找出应用中变化之处,把它们独立,不要和那些不需要变化的代码混在一起

把会变化的代码提取,并封装为类,以便未来可以轻易地改动或扩充此部分,而不会影响其他不需要变化的部分。可以说这个原则是每个设计模式的精髓。

分析:最开始的 Duck 抽象类里的 fly 方法和咕咕叫(quack)方法都会随着鸭子的种类不同,而被改变。那么可以把这两个方法提取,建立一组新的类来实现。建立两个类,一个是“fly”相关的,一个是“quack”相关的,每一个类能实现各自的多样化的动作。比如:

1、叫的动作,可以是 “呱呱叫”,“吱吱叫”,或者 “安静(不叫)”等

2、飞的动作,可以是“快速的飞”,“不能飞”等,能满足,类似橡皮鸭不会飞这种新需求

把动作相关的代码,提取为类,看着好像不是特别好,毕竟大家都知道:接口才代表的是行为(或者说动作),类代表的是某种事物的具体类型或者抽象的某种类型。参考:何时使用接口(抽象类)?没错,确实需要接口来表示动作,因此引出第二个设计原则。

第二设计原则:面向接口编程而不是面向具体实现

当然不能直接用类来实现真正的动作,所以利用接口代表每个行为,设计两个代表鸭子动作的接口:FlyBehavior 与 QuackBehavior,而第一原则里说的抽取出的两种新类(一个是“fly”相关的,一个是“quack”相关的),作为具体的行为的实现类,即让 “fly” 相关的,和 “quack” 相关的代表鸭子的所有具体动作的类都实现其中的一个接口。

public interface FlyBehavior {
    void fly();
}
 
////////////////////
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("飞翔");
    }
}
 
//////////////////
public class FlyWithoutWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不能飞");
    }
}

1、鸭子类 Duck 不再负责实现 Flying 与 Quacking 接口,反而是额外制造一组其他类专门实现 FlyBehavior 与 QuackBehavior,这就是所谓的分离变和不变的部分,把变的部分抽取(抽象)——由专门的行为类而不是 Duck 类来实现行为接口。从次以后,Duck类就不需要维护经常需要变动的行为了。

public abstract class Duck {
    public void swim() {
        System.out.println("所有的鸭子都会游泳,不会改变");
    }
 
    abstract void display(); // 所有鸭子都有外貌
}

2、使用接口代表抽象的行为,具体的鸭子类只需要按照自身的需求,去实现对应的行为接口( FlyBehavior 、 QuackBehavior等,以后可以扩展),等 Duck 需要使用某个行为的时候,具体的实现不会绑死在具体的鸭子类上。

public abstract class Duck {
    FlyBehavior flyBehavior; // 面向接口编程,这也是组合的体现
    QuackBehavior quackBehavior; // 面向接口编程// 面向接口编程
    public void setFlyBehavior (FlyBehavior fb) {
        flyBehavior = fb;
    }
 
    // 面向接口编程
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
 
    abstract void display(); // 所有鸭子都有外貌
 
    public void performFly() {
        flyBehavior.fly();
    }
 
    public void performQuack() {
        quackBehavior.quack();
    }
 
    public void swim() {
        System.out.println("所有的鸭子都会游泳,不会改变");
    }
}

以后,在新设计中,鸭子的具体的类将使用接口(FlyBehavior与QuackBehavior)表示行为,所以实际的“实现”不会被绑死在鸭子的子类中。

而且,这里之所以使用抽象类代表Duck,还是考虑到,既然Duck没有需要变化的部分了,那么完全可以让其代表一个类型——鸭子,故没有必要使用接口。

问题:为什么非要把行为设计成接口,而不用抽象类

这个问题说明没有真正理解接口,要理解 “面向接口编程” 的真正意思——针对超类型编程。这里所谓的“接口”有多个含义,接口是一个广义的 “概念”,只不过在 Java 里特指的 interface。

面向接口编程,关键就在实现多态,和能利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上,只不过这里优先使用的接口。

public interface FlyBehavior {
    void fly();
}
 
/////////////////////////
public interface QuackBehavior {
    void quack();
}
 
///////////////////////
public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying!!");
    }
}
 
public class FlyRocketPowered implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying with a rocket");
    }
}
 
public class FlyNoWay implements FlyBehavior {
    public void fly() {
        System.out.println("I can't fly");
    }
}
 
//////////////////////
// 呱呱的叫
public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("Quack");
    }
}
 
// 嘶哑的叫
public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("MuteQuack");
    }
}

///////////////////
public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;public void setFlyBehavior (FlyBehavior fb) {
        flyBehavior = fb;
    }
 
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
 
    abstract void display();
 
    public void performFly() {
        flyBehavior.fly();
    }
 
    public void performQuack() {
        quackBehavior.quack();
    }
 
    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }
}
 
// 能呱呱的叫,能飞
public class MallardDuck extends Duck {
    public MallardDuck() {
        setQuackBehavior(new Quack());
        setFlyBehavior(new FlyWithWings());
    }
 
    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}
 
// 不能飞,能嘶哑的叫
public class DecoyDuck extends Duck {
    public DecoyDuck() {
        setFlyBehavior(new FlyNoWay());
        setQuackBehavior(new MuteQuack());
    }
    public void display() {
        System.out.println("I'm a duck Decoy");
    }
}
 
////////////////// 客户端
public class MiniDuckSimulator1 {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck(); 
        mallard.performQuack(); // 咕咕叫
        mallard.performFly(); // 能飞
   
        Duck decoyDuck = new DecoyDuck();
        decoyDuck.performFly(); // 不能飞
        // 客户端可以动态的改变鸭子的行为,让它能飞了
        decoyDuck.setFlyBehavior(new FlyRocketPowered());
        decoyDuck.performFly(); // 嘶哑的叫

    }
}

如上,也是策略模式的体现——既可以实现代码复用,又能实现责任分离,即使新增了行为,也不会影响现有的鸭子类。

总结:什么时候开始优化

虽然,过早优化是万恶之源,但是类似接口,抽象类,继承等这样基本的思想还是要从最开始就伴随整个系统的,比如策略模式这种简单的设计模式,完全可以在开始设计的时候,就考虑进去,直接实现,而不是后期重构。

类能否代表行为?

前面的例子里,优化的方案,使用了类代表行为,虽然具体的行为实现,还是应用的接口,那么这里合理么?

合理,众所周知,在OOP中,类代表某个事物,某个具有状态的事物的类型,而行为有时候也是会有状态的,比如飞翔,可以有飞翔速度等属性,因此,在本例,飞行这个行为也是一种类型,只不过是凑巧的。为了方便,使用的类代表具体行为,而行为的动作实现,仍然是接口代表

算法族的概念

到这里,不再把鸭子的各个行为说成“一组行为”、或者“相关的行为”了,而是开始把行为看成是“一族算法”。算法代表鸭子能做的事情(不同的叫法和飞法)

总结:遵循组合(聚合)优于继承的设计原则

记住,考虑到在同样可行的情况下,优先使用组合而不是继承,也有书中,管他叫:has-a 关系,好于 is-a 关系,前者是组合,后者是继承。

结合前面的例子,知道继承关系的耦合度很高,一处改可能会导致处处需要修改。如果一个业务功能,继承可以实现,组合也能实现,优先使用组合。因为:

1、组合的关系具有很大的弹性

2、组合更好的分离了责任

3、组合的副作用很小

……

很多的设计模式,都巧妙的体现了该原则。

《Thinking in java》:“继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承……当用继承的时候,肯定是需要利用多态的特性。如果用不到多态的特性,继承的关系是无用的”

《effective Java》:“只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在is-a关系的时候,类B才应该继续类A。”

JDK 8 中的接口新特性,可以改善一些旧问题

https://www.zhihu.com/question/41166418

Java 8的接口,即便有了default method,还暂时无法完全替代抽象类。它不能拥有状态,只能提供公有虚方法的默认实现。Java 9的接口已经可以有非公有的静态方法了。未来的Java版本的接口可能会有更强的功能,或许能更大程度地替代原本需要使用抽象类的场景。


作者:RednaxelaFX
链接:https://www.zhihu.com/question/41166418/answer/139494009
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

策略模式到底是怎么运行的

StrategyPattern:策略模式也算比较简单的,同工厂模式一样都属于面向接口编程……策略模式是对象的行为模式之一,而工厂模式是对象的创建模式

策略模式对一系列的算法加以封装,为所有算法定义一个抽象的接口,并通过继承(实现)该接口,对所有算法加以封装和实现,具体的算法选择由客户端决定(策略)。

策略模式使得算法可以在不影响到客户端的情况下发生变化。Strategy 模式主要用来平滑地处理算法的切换 。

简单说:策略模式可以让我们在程序中随意的、快速的替换接口的实现“算法”,而且还不用修改接口,也不需要修改客户端。可以说策略模式,是接口的典型应用。

前面说了策略模式封装算法,自然有一个算法接口 IStrategy,扩展的不同的策略(算法封装)StrategyABuilder,StrategyBBuilder……再来一个策略的容器——其实就是一个工厂,下面总结下策略模式的角色:

Strategy接口 : 策略(算法)的接口

ConcreteStrategy :各种策略(算法)的具体实现

Context:策略的外部封装类,或者说策略的容器类。它根据不同策略执行不同的行为。策略由外部环境决定,自然这个工厂就需要聚合策略接口的引用,并配有对应的执行策略的方法。

比如有这样一个程序,给文件加密的程序,当前有三种加密方法可以选用,分别是MD5、RSA,和AES加密算法,下面运用策略模式,看代码实现;

public interface EncryptStrategy {
    /**
     * 加密算法的抽象接口
     */
    void doEncrypt();
}

public class AESEncryptBuilder implements EncryptStrategy {
    @Override
    public void doEncrypt() {
        System.out.println("进行AES加密!");
    }
}

public class MD5EncryptBuilder implements EncryptStrategy {
    @Override
    public void doEncrypt() {
        System.out.println("进行MD5加密!");
    }
}

public class RSAEncryptBuilder implements EncryptStrategy {
    @Override
    public void doEncrypt() {
        System.out.println("进行RSA加密!");
    }
}

public class Factory {
    /**
     * 聚合的算法接口
     */
    private EncryptStrategy encryptStrategy;

    public Factory(EncryptStrategy encryptStrategy) {
        this.encryptStrategy = encryptStrategy;
    }

    /**
     * 执行加密操作
     * 本身不实现加密算法,而是转而去调用聚合的算法接口持有的方法
     */
    public void execute() {
        this.encryptStrategy.doEncrypt();
    }
}

public class TestStrategy {
    public static void main(String[] args) {
        Factory factory = new Factory(new MD5EncryptBuilder());
        factory.execute();
    }
}

是不是非常简单,和工厂模式有有些类似。想到这里,联系之前的工厂模式(静态工厂,简单工厂,抽象工厂):

Java反射+简单工厂模式总结

工厂方法模式总结

抽象工厂模式和三种工厂的对比总结

不禁发问了:抽象工厂模式就是策略模式吧?只不过这个策略是个简单工厂而已?

策略模式和工厂模式的对比

两个模式比较重要的一个区别在于:工厂模式是创建型的设计模式,偏重于创建不同的对象,而策略模式是行为型的设计模式,偏重于通过不同的方式实现同样的行为。

反过来,创建对象本用的就是一个方法,通过不同的工厂动态指定实际的创建方法,就是封装了这个方法,工厂模式的目的最终是为了创建对象,而策略模式的目的并不仅限于创建对象,可以说工厂模式应用了策略模式更好地说法是,抽象工厂模式和策略模式都应用了面向接口编程的思想。

通过上面的示例可以看出,策略模式仅仅封装算法,提供新的算法插入到已有系统中,以及可以把不需要的算法从系统中除去,策略模式本身不决定在何时使用何种算法,在什么情况下使用什么算法是由客户端决定的。

策略模式的一个很重要的特点:运行时策略的唯一性——也就是策略模式运行期间,客户端在每一个时刻只能使用一个具体的策略实现,虽然可以动态地在不同的策略实现中切换,但同时只能使用一个。

再看一个例子:购物结账问题,收银系统根据不同的折扣活动,得到商品的不同价格

public interface Strategy {
    /**
     * 花费的价钱*/
    double cost(double num);
}

public class StrategyA implements Strategy {
    /**
     * 打8折*/
    @Override
    public double cost(double num) {
        return num * 0.8;
    }
}

public class StrategyB implements Strategy {
    /**
     * 9折*/
    @Override
    public double cost(double num) {
        return num * 0.9;
    }
}

public class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public double costs(double num) {
        return this.strategy.cost(num);
    }
}

public class MainDemo {
    public static void main(String[] args) {
        double num = 200;
        Context context = new Context(new StrategyB());
        double lastNum = context.costs(num);

        System.out.println(lastNum);// 180.0
    }
}

策略的注入方式

前面的例子,环境类都是在构造器里注入的策略实现,当然也可以不使用构造器注入,而使用setter方法注入具体的算法实现

为什么有人实现策略模式用抽象类而不是接口,是无所谓还是另有原因?

这就要说到经常见到的一种场景:有时候具体策略类有一些公有的行为、属性,这时候就应把这些公有的行为、属性统一提取放到共同的抽象策略角色里面。当然这时候抽象策略角色必须要用抽象类来实现。这其实也是典型的将代码向继承等级结构的上方集中的标准做法。

什么场景要使用策略模式?究竟使用策略模式有什么实际的好处呢?

有一个例子,如图,人需要出去旅游,出门赶路的方式有,骑自行车去,开汽车去,直接做火车,或者飞机去……我们应该能想到这是策略模式的一个应用场景。

两种实现代码,第一种实现使用了策略模式:

Person person = new Person(new Bike()); 
person.travel();

person = new Person(new Car());
person.travel();

第二种实现方式,不用策略模式,如下:

Bike bike = new Bike();
bike.travel();

Car car = new Car();
car.travel();

先理解接口的意义,参考:何时使用接口(抽象类)?

论规模,这个场景其实更类似是多态,回答问题之前,首先要明白一个道理——到底是 “人” 旅游,还是火车、汽车、自行车、飞机这些交通工具旅游?

如果采用第二种实现方式,那旅游这件事就和人没有关系了,只和交通工具有关。正常来说,应该是人选择xxx交通工具去xxx地旅游,而不是直接让交通工具去自己选自己……人选交通工具,又有这么多工具可以选择,毫无疑问交通工具是易于变化的,所以把对应的交通工具嵌入到人这个类是合理的。

再来看第二种做法的坏处,我们来把场景详细说下:现在有两个人(Person),分别是台湾的小明,和上海的老王,他们是老同学,相约去北京旅游,他们可以通过火车、飞机和汽车这三种不同的交通工具到达北京见面,到北京之后,两个人同时通过自行车到达天安门和另一个老同学小丽见面……现在把程序扩展——旅行结束后,每个人旅行的总用时都要记录下来,用程序实现之。

如果是第二种做法,仅仅是通过调用 car.travel(),plane.travel(),train.travel() 和 bike.Travel() 方法的话,如何记录不同人旅行的用时?也许强行的通过不同的交通工具对象来记录,但是假如两人到达北京以后,小明通过自行车到天安门,而老王通过汽车car临时又去了故宫……此时,记录每个人旅行总用时更加麻烦了,因为总用时是跟每个人,而不是某种交通工具有关的,所以这就是为什么要使用 person.travel() 的原因,就是说我们要明白某种行为(操作)的主体是什么。

面向对象的思想实际上就是将把现实世界中的万事万物看成是对象,并进行抽象,然后以计算机世界的方式对现实世界进行模拟。如果对现实世界的抽象错误,那么在之后就会碰到一系列很别扭,甚至无法解决的问题。

策略模式的优点就在于替换或是增加用于实现相同的功能的算法非常方便(基于多态这个特性)。为了达到这样的优点,“面向接口编程” 是必须的另外,策略模式一般强调行为,不关注不同的策略类本身的属性,也就是说对于图片中的例子,我们是不区分不同的 plane,bus 等交通工具对象的,我们需要的只是各个交通工具类的 travel() 方法。

总结——使用策略模式的场景:

1、当系统中相互独立的算法较多

2、这些算法的功能都有共同接口

3、这些算法以后可能发生改变(增删改等)

4、需要隐藏算法的实现

5、如果不用策略模式,会出现大量if-else的时候……

如果满足如上条件,就可以考虑使用策略模式,现在麻烦点,以后就方便点,减少工作量,提高系统弹性

什么场景不适用策略模式

前面明确说到:必须是相互独立的算法…… 且策略模式的特点就是运行时算法的唯一性。故,简单说,如果一个操作的完成不是依靠一个算法,而是复合了多个算法,或者嵌套多个算法才能完成,此时不能使用策略模式。如果偏要使用,可以把算法粒度变粗,整合为一个算法,仍然能使用策略模式。但是最好的解决方案,是使用装饰模式,参考:对复合(协作)算法/策略的封装方法——装饰模式总结

策略模式的优点小结

策略模式关注的不是如何实现算法,而是如何调用这些算法,它的优点有:

1、策略模式提供了管理相关的平等的算法族的办法

策略类的等级结构定义了一个算法族。恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代码。策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间相互替换。所有的策略算法在实现上也是相互独立的,所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现

2、策略模式提供了可以替换继承关系的办法(聚合抽象的引用)

继承可以处理多种算法或行为。如果不用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承的缺陷就是使得动态改变算法或行为变得不可能

3、使用策略模式可以避免使用多重 if 语句

多重 if 语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后

说了那么多,策略模式就没有缺点么?

1、前面我们发现要想使用策略模式,客户端(调用者)必须清楚所有的策略都是什么,才能决定使用哪一个策略,也就是说,客户端必须理解所有算法的不同,才能适时的选择合适的算法,如果客户端不清楚以上,那么策略模式不适用。

2、策略模式策略类膨胀的问题,这一度被认为是策略模式的阴暗面。因为不论是为了解决多重 if-else 语句,还是解决 switch case 分支过多,并且扩展性差的问题,一旦可替换的策略(算法)越来越多,虽然扩展性得到解决,但是策略(算法)类太多了,每一个分支对应一个……又非常繁琐,这就是它的一个缺点。换句话说,策略模式造成很多的策略类。

怎么解决策略类膨胀的问题?

有时候可以通过把依赖于环境类的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。下面就趁热打铁,说一说享元模式——减小内存的占用问题——享元模式和单例模式的对比分析 

JDK里使用了策略模式的类都有哪些?

javax.servlet.http.HttpServlet 类:

HTTP 请求实际上只是一个 HTTP 请求报文,Web 容器会自动将这个 HTTP 请求报文包装成一个 HttpServletRequest 对象,并且自动调用 HttpServlet的 service() 方法来解析这个HTTP请求,service()方法会解析HTTP请求行,而HTTP请求行由method,URI,HTTPVersion三个组成,method就是get或者post,service() 方法根据 method 来决定是执行 doGet 还是 doPost,这一切都是服务器(容器)自动完成的,HTTP的格式也自动被解析。

只要自定义的类继承了HttpServlet,并且在web.xml里面配置了相应的servlet和mapping,服务器就会自动执行以上过程。而我们自定义的每一个Servlet都必须要实现Servlet接口,而图里的GenericServlet是个通用的、不特定于任何协议的Servlet,它实现了 Servlet 接口 

而 HttpServlet 继承于 GenericServlet,因此 HttpServlet 也实现了 Servlet 接口,所以我们定义的Servlet只需要继承HttpServlet父类即可。Servlet接口中定义了一个service方法:

View Code

HttpServlet对该方法进行了实现,实现方式就是将ServletRequest与ServletResponse转换为HttpServletRequest与HttpServletResponse。转换完毕后,会调用HttpServlet类中自己定义的service方法

View Code

发现下面这个 service 是 protected方法

View Code

在该转换之后的 service 方法中,首先获得到请求的方法名,然后根据方法名调用对应的doXXX方法,比如说请求方法为GET,那么就去调用doGet方法;请求方法为POST,那么就去调用doPost方法。比如:

doPut方法
doGet方法

在HttpServlet类中所提供的doGet、doPost……方法都是直接返回错误信息,所以我们需要在自己定义的Servlet类中重写这些方法,经过上面的过程,我们发现 HttpServlet 类就是一个策略抽象类,我们自己定义的servlet类去覆盖HttpServlet里的这些方法,自然不同的人,不同的项目里,需要重写的内容和方法都不尽相同,这就是一个策略模式的思想。

 

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

 

posted @ 2016-02-02 00:09  dashuai的博客  阅读(3224)  评论(4编辑  收藏  举报
Flag Counter欢迎关注微信公众号