2016-06-28 13:27:05 +0000   |     java thinking in java interface abstract class   |   Viewed times   |    

摘要

这章讲虚拟类和接口。书里非常好地从编译的角度,解释虚拟类和接口产生的内在逻辑。以及他们各自的作用以及优缺点。

本章还比较了虚拟类和接口之间的区别。宏观来讲,接口是单纯的抽象,比虚拟类更抽象。虚拟类更像是抽象接口和具象类的实现中间的过度。但是实际应用的时候,因为接口的多继承性,优先度要高于虚拟类。

由于接口和虚拟类直接导出了很多实用的设计模式。这一章也对Strategy策略模式,State状态模式,Adapter适配器模式,以及FactoryMethod工厂方法模式,做了初步的了解。

虚拟类

换一个角度看虚拟类,理解它产生的逻辑。

动机是为了防止某些抽象概念被错误实例化

有了多态性之后,提炼出一类事物的共性,然后面对这些“共性”编程,让系统结构更松耦合,结构更合理。这让我们就有充分的动力去抽象出这样的共性,其实也就是我们叫做“接口”的东西。

但有的时候,这样的抽象的接口作为实际基类存在的时候,由于过于抽象,只能作为一种概念被理解,并不适合被实例化。比如,书里举的一个栗子是”乐器“,多态都是创建小提琴,钢琴,然后向上转型赋值给乐器类型。但如果不小心创建了一个乐器实例,编译器不会报错,实例不但不能拿来用,还会造成很隐蔽的bug,带来不必要的麻烦。

解决办法就是,把不希望实例化的”抽象的“类标记成”虚拟类“。这样最大的好处是:当程序员试图实例化虚拟类的时候,编译器会报错。

绿色简化版的基类

一个附带的好处是,对那些非共性的方法,原先可能还需要假装实现一下:

void f(){};

现在可以只声明,完全不实现,标记为虚拟方法。当然一个虚拟类也可以完全没有虚拟方法,可以有一个正常类有的所有的一切,仅仅只为了不被实例化。但有虚拟方法的类,必须声明成虚拟类。要想继承虚拟类,产生实际类,必须实现所有方法。

abstract void f();

虚拟类使得基类变得更轻便,更安全,而且也非常地灵活,可以自由调节抽象的程度。

缺点:单继承

实在要说虚拟类的缺点,就只能是郁闷的单继承特性了。

接口

纯抽象:耦合协议

接口在虚拟类的基础上,更进一步抽象化,完全没有方法的实现。只是为了定义一下方法的名称,参数和返回值类型。一个接口的潜台词是说:所有符合我这个接口的类,必须长这个样子,得有我定义的这些参数和方法。

All classes that implement this particular interface will look like this.

所以接口是模块间耦合的标准插槽(互相访问操作的“标准协议”)。面向接口编程,可以使整个系统松耦合套接,模组之间,降低依赖,方便拆换,即插即用。

字段:必static final

接口中定义的字段默认都是静态常量。哪怕没有显示声明:

public int a = 10;

编译之后都会被加上staticfinal的修饰符。

public static final int a = 10;

在Java5以前,接口经常被用来声明类似枚举型enum的常量:

public interface Months {
int
JANUARY = 1, FEBRUARY = 2, MARCH = 3,
APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12;
}

方法:必public

接口中所有的方法都是public的。哪怕我们没有显式地写出来:

interface Instrument {
	void play(Note n); // Automatically public
}

伪“多继承”

一个类可以实现多个接口的设定,使得接口比虚拟类更加灵活,变相实现了类的“多继承”。

接口同时继承多个接口

接口间是允许多继承的:

interface Monster {
void menace();
}
interface Lethal {
void kill();
}
//Vampire同时继承Monster和Lethal接口
interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
}

问题:接口方法重名

既然允许一个类同时继承多个接口,接口的方法名称很容易重复。Java以方法名参数来区分不同方法。只要有相同的方法名和参数,就被认为是同一个方法。所以继承接口的时候要小心。

int doSomething(String s);
String doSomething(int); // this is fine

int doSomething(String s);
String doSomething(String s); // this is not

最好的习惯是,根本不要让不同接口里,有同名的方法

套嵌接口

接口可以被声明在任何一个类或者接口内。

	class A {
		interface B {
			void f();
		}
		public class BImp implements B {
			public void f() {}
		}
		private class BImp2 implements B {
			public void f() {}
		}
	}

接口也能被声明成private。

class A{
	private interface D {
		void f();
	}
	private class DImp implements D {
		public void f() {}
	}
	public class DImp2 implements D {
		public void f() {}
	}
}

这样做的功效是,在A类内部,DImp2类可以实现接口,但在A类外部,禁止把DImp2向上转型给D类型。 作者说我未来总有一天用得到这点的,谁知道,万一呢。

使用虚拟类还是接口?

最后如果一定要回答这个尴尬的问题,我觉得接口会用的更加多一点。虚拟类只在需要减少代码冗余的时候用。当然接口和虚拟类一起用,也是相当好。虚拟类实现接口大部分方法,接下来实际类再完成特殊方法。当用户接口改变的时候,如果改变虚拟类就能解决问题,能省很多人力。如果底层代码被改动,因为接口的存在,完全不会影响到用户应用。

不要滥用接口

我好像已经开始有种强迫症,哪里都要用接口和工厂类。作者小结里的这句话好像就是对我说的:优先使用类,而不是接口,除非有充分的必要。

An appropriate guideline is to prefer classes to interfaces.

Start with classes, and if it becomes clear that interfaces are necessary, then refactor.

State模式 & Strategy模式

编程中一些常用的使用场景,因为在工程上具有普遍的合理性,被“Big 4”提炼成为Pattern(模式)。这些人类智慧的经验,是能让我受益终生的。我第一次学到的时候,是内牛满面的。其中有很多模式就是对多态和接口的直接利用。书看到这里,我就逐个实践一下,然后做一点简单的记录。

Strategy模式

Strategy模式的原始动机非常简单,当某个行为有很多种不同的方法来完成,但返回值是同一个类型。我们就可以把具体完成的策略部分从语境类中分离出去,独立成类。然后再以参数的形式传递给语境类中的方法。如果独立出去的不同策略再抽象出一个通用的接口的话,那语境类就可以完全面向这个接口编程,从而和具体策略类完全解耦。语境类执行的策略完全推迟到runtime决定。

Strategy模式非常适合分离具体语境流程和具体算法。把算法当成一个黑箱,封装起来,然后独立出去当一个模块来用。Design Pattern书里举的例子是文本编辑器里分行算法的例子。

strategyPattern

几个不同的分行算法compose()从文本编辑器class composition()里独立出来,抽象成一个分行器interface compositor()接口,规定所有分行器的分行函数统一叫compose()方法。在文本编辑器内部,分行器compositor以参数的形式传递给后期渲染函数repair()。repair()直接面向compositor接口编程,直接调用接口方法compsitor->compose();不需要关注runtime会具体调用哪个分行器。

strategyPattern2

最后Strategy模式的抽象结构图如上图所示。把语境和算法分离成两个独立的模块:语境类和算法接口。语境类中的具体方法通过接口调用具体算法的引用。再抽象点,语境类也可以抽象出一个接口,这样就是接口对接口了。注意context和strategy之间是菱形的聚合关系,指模块间的相互调用关系。

State模式

State模式和Strategy模式非常相似。目的都是封装语境流程中的不稳定因素。

但State模式比Strategy模式更加地彻底。Strategy模式把算法模块接口直接以参数的形式传给执行策略的行为,从而只作用于某个行为。但State模式直接把策略接口加入到语境类中作为一个字段。这样做的好处是,可以在中途切换策略,只要再加一个专门切换策略的方法就可以。往往用State模式的原因是因为语境中不止一个方法取决于这个策略的选择,而且runtime运行过程中经常切换策略。所以总结起来State模式和Strategy模式的主要区别有三个:

statePattern

从书中举的TCP网络连接的例子,明显可以看出来,State接口几乎和语境类中所有的方法都相关。甚至语境类看起来就像是State接口的一个proxy代理,只是负责调用State接口。这种情境下,当执行切换网络连接状态之后,当前链接几乎所有的行为都发生了改变。这也正是”State“这个名字的意义所在。

但总的来说,State和Strategy两个模式的本质是相似的,都是利用接口的多态性,来去耦合,加强语境类对不同策略的适应性,方便维护和扩展。

实践

下面我简单实现了一下State和Strategy模式。为了突出他们间的区别,我把他们同时用在了同一个例子里。为了模拟State状态的切换,假设我们的语境类为美国。美国每年有很多的政策要执行,比如,移民政策,教育政策,医保政策,等等。但美国每四年选举一届总统,每个总统主要代表的是他所在政党的利益和政策倾向。美国主要有两大政党(接口),”民主党“和”共和党”。不同党派的总统上台,必然导致在所有大政方针都有所改变。这正是State模式所擅长的。

接下来为了突出某个Strategy模式,对某一问题的算法策略的影响,设置了Ideology意识形态接口,分为“普世价值”和“种族隔离”。意识形态作为具体算法会对移民政策产生巨大影响。

语境类Usa还使用了另一个设计模式:Singleton(单例器),为了保证世界上只有一个美国。

最后在runtime,我创建了民主党的克林顿总统和和共和党的布什总统来执政。最后的彩蛋是,如果让种族主义者川普执政会是什么结果?更恐怖一点,如果川普执行种族隔离政策呢?下面为输出结果:

Hello World! I am new President Bill Clinton
Total Illegal Immigrants:   1100000
Education score:   75
Hello World! I am new President George Bush
Total Illegal Immigrants:   1045000
Education score:   80
Hello World! I am new President Donald Trump
Total Illegal Immigrants:   522500
Education score:   70
Now comes the Racial Segregation!
Total Illegal Immigrants:   52250
语境类 Usa.java
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;
import java.lang.Math;

class Usa {
	//switch the states
    public static void presidentElection(PartyMember newPresident){
        unitedStates.president=newPresident;
        System.out.println("Hello World! I am new President "+unitedStates.president.getName());
    }

    //handle the immigrant with our ideology
    public static void dealWithImmigrant(){
        //the president's preference affect the immigrant
        unitedStates.illegalImmigrant=unitedStates.president.immigrantPolicy(unitedStates.illegalImmigrant);
        System.out.println("Total Illegal Immigrants:   "+unitedStates.illegalImmigrant);
    }

    //when ideology involved in
    public static void dealWithImmigrant(Ideology currentIdeology){
        //globle race policy affect the immigrant
        unitedStates.illegalImmigrant=currentIdeology.offset(unitedStates.illegalImmigrant);
        System.out.println("Total Illegal Immigrants:   "+unitedStates.illegalImmigrant);
    }

    public static void education(){
        unitedStates.educationScore=unitedStates.president.educationPolicy(unitedStates.educationScore);
        System.out.println("Education score:   "+unitedStates.educationScore);
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Usa(){
        //founding father: Washington
        PartyMember georgeWashington=new Federalist("George Washington");
        this.president=georgeWashington;
        this.illegalImmigrant=1000000;
        this.educationScore=80;
    }

    /**
     *  PRIVATE FIELDS
     */
    private int illegalImmigrant;
    private int educationScore;
    private PartyMember president;
    //singleton
    private static Usa unitedStates=new Usa();    
}

状态接口 Party.java
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

interface Party {

    public int immigrantPolicy(int illegalImmigrant);
    public int educationPolicy(int educationScore);

}
虚拟类 PartyMember.java
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;
import java.lang.Math;

abstract class PartyMember implements Party {
    //dummy implement
    public int immigrantPolicy(int illegalImmigrant){return illegalImmigrant;}

    public int educationPolicy(int educationScore){return educationScore;}

    public String getName(){return this.name;}

	/**
     *  PACKAGE ACCESS CONSTRUCTOR
     */
    PartyMember(String inputName){
        this.name=inputName;
    }

    /**
     *  PRIVATE FIELDS
     */
    private String name;    
}
民主党员 Democratic.java,共和党员 Republican.java,联邦党员 Federalist.java,种族主义 Racism.java
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;
import java.lang.Math;

class Democratic extends PartyMember {
    public int immigrantPolicy(int illegalImmigrant){
        return (int)(illegalImmigrant*1.1);
    }

    public int educationPolicy(int educationScore){
        return (Math.max(0,(educationScore-5)));
    }

    /**
     *  PACKAGE ACCESS CONSTRUCTOR
     */
    Democratic(String inputName){
        super(inputName);
    }

}
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;
import java.lang.Math;

class Republican extends PartyMember {

    public int immigrantPolicy(int illegalImmigrant){
        return (int)(illegalImmigrant*0.95);
    }

    public int educationPolicy(int educationScore){
        return (Math.min(100,(educationScore+5)));
    }

    /**
     *  PACKAGE ACCESS CONSTRUCTOR
     */
    Republican(String inputName){
        super(inputName);
    }

}
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;
import java.lang.Math;

class Federalist extends PartyMember {

    /**
     *  PACKAGE ACCESS CONSTRUCTOR
     */
    Federalist(String inputName){
        super(inputName);
    }

}
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

class Racism extends PartyMember {

    public int immigrantPolicy(int illegalImmigrant){
        return (illegalImmigrant/2);
    }

    public int educationPolicy(int educationScore){
        return (Math.max(0,(educationScore-10)));
    }

}
意识形态接口 Ideology.java
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

interface Ideology {

    public int offset(int immigrants);

}
普世价值 UniversalValue.java,种族隔离 RatialSegregation.java
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

class UniversalValue implements Ideology {

    public int offset(int immigrants){
        return immigrants*1.5;
    }

}
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

class RacialSegregation implements Ideology {

    public int offset(int immigrants){
        return immigrants/10;
    }

}
Runtime
    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        //Clinton government
        Usa.presidentElection(new Democratic("Bill Clinton"));
        Usa.dealWithImmigrant();
        Usa.education();
        //Bush government
        Usa.presidentElection(new Republican("George Bush"));
        Usa.dealWithImmigrant();
        Usa.education();
        //Donald Trump ???
        Usa.presidentElection(new Racism("Donald Trump"));
        Usa.dealWithImmigrant();
        Usa.education();
        //Donald Trump with Ratial Segregation ???
        Usa.dealWithImmigrant(new RacialSegregation());
    }

Adapter模式

Adapter模式,顾名思义类似电源适配器,电源插头转换器的作用。假设A类只能做a()这件事,做不了b()。但B类能做b()。Adapter类的作用当然是两者兼顾,既能做a(),又能做b()。还有一种情况,就是当A类和B类做的事基本相似,但有不同的接口,也非常适合使用Adaper模式。

组合型Adapter

adapterPattern Adapter类往往有两种构建的方法。第一种,最直观也是最偷懒的办法,如上图所示,Adapter类直接继承Target类,然后把Adaptee类以一个内部字段的形式,组合进来。最后只要适当重写一下接口方法request(),调用一下Adaptee类的specialRequest()方法。这样新的Adapter类就成了Target类的一个加强版。既能完成request(),也能做specialRequest()。

多继承型Adapter

adapterPattern 第二种方法,大同小异。同时实现Target和Adaptee接口,或者直接继承Target类,再实现Adaptee接口。这样就像是完成了多继承,同时满足两组接口协议。然后由主接口request()来调用封装辅接口specialRequest()。

实践1 - 组合型Adapter

假设我有一个音乐播放器MusicPlayer,只能播放mp3和aac格式的音频文件。如果要播放视频,必须使用视频播放器VideoPlayer。如果现在想要一种既能播放音乐又能播放视频的多媒体播放器MultiPlayer。基本思路就是多媒体播放器继承音乐播放器的功能,然后内部内嵌一个视频播放器,遇到视频格式就调用视频播放器。结构很简单,

MusicPlayer.java
interface MusicPlayer {

    public void play(File inFile);
    public void playMp3(File inFile);
    public void playAac(File inFile);

}
MusicPlayers.java
class MusicPlayers implements MusicPlayer {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static MusicPlayers createMusicPlayers(){
        return new MusicPlayers();
    }

    /**
     *  PUBLIC METHODS
     */
    public void play(File inFile){
        if(inFile.getType()=="mp3"){
            playMp3(inFile);
        } else if(inFile.getType()=="aac"){
            playAac(inFile);
        }
    }
    public void playMp3(File inFile){
        System.out.println(inFile.getInfo());
    }
    public void playAac(File inFile){
        System.out.println(inFile.getInfo());
    }

    /**
     *  PACKAGE ACCESS CONSTRUCTOR
     */
    MusicPlayers(){}

}
VideoPlayer.java
interface VideoPlayer {

    public void play(File inFile);
    public void playAvi(File inFile);
    public void playMp4(File inFile);

}
VideoPlayers.java
class VideoPlayers implements VideoPlayer {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static VideoPlayers createVideoPlayers(){
        return new VideoPlayers();
    }

    /**
     *  PUBLIC METHODS
     */
    public void play(File inFile){
        if(inFile.getType()=="avi"){
            playAvi(inFile);
        } else if(inFile.getType()=="mp4"){
            playMp4(inFile);
        }
    }
    public void playMp4(File inFile){
        System.out.println(inFile.getInfo());
    }
    public void playAvi(File inFile){
        System.out.println(inFile.getInfo());
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private VideoPlayers(){}

}
MultiPlayers.java
class MultiPlayers extends MusicPlayers {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static MultiPlayers createMultiPlayers(){
        return new MultiPlayers();
    }

    /**
     *  PUBLIC METHODS
     */
    public void play(File inFile){
        if(inFile.getType()=="mp3"){
            playMp3(inFile);
        } else if(inFile.getType()=="aac"){
            playAac(inFile);
        } else if(inFile.getType()=="avi"){
            adapteePlayer.playAvi(inFile);
        } else if(inFile.getType()=="mp4"){
            adapteePlayer.playMp4(inFile);
        }
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private MultiPlayers(){}

    /**
     *  PRIVATE FIELDS
     */
    private VideoPlayer adapteePlayer =  VideoPlayers.createVideoPlayers();

}
File.java
interface File {

    public String getInfo();
    public String getType();

}
Files.java
class Files implements File {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static Files createFiles(String inInfo, String inType){
        return new Files(inInfo, inType);
    }

    /**
     *  PUBLIC METHODS
     */
    public String getInfo(){
        return this.info;
    }
    public String getType(){
        return this.type;
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Files(String inInfo, String inType){
        this.info=inInfo;
        this.type=inType;
    }

    /**
     *  PRIVATE FIELDS
     */
    private String info;
    private String type;

}
Runtime程序Main()
    public static void main(String[] args){
        File file1=Files.createFiles("Hey Jude","mp3");
        File file2=Files.createFiles("Yesterday","aac");
        File file3=Files.createFiles("Mickle Jackson concert","avi");
        File file4=Files.createFiles("Beatles concert","mp4");

        MultiPlayers myPlayer=MultiPlayers.createMultiPlayers();
        myPlayer.play(file1);
        myPlayer.play(file2);
        myPlayer.play(file3);
        myPlayer.play(file4);
    }

//output:
Hey Jude
Yesterday
Mickle Jackson concert
Beatles concert

实践2 - 多继承型Adapter

中国和加拿大的学制是不同的,尤其是在初级,中级教育系统中的区别很大。一个留学生到了加拿大需要先转换成同等学历,再入学。所以我的模型是这样:

ChineseEducationInterface.java
interface ChineseEducationInterface {

    public void primarySchool(Student s);
    public void middleSchool(Student s);
    public void seniorHighSchool(Student s);
    public void university(Student s);

}
ChineseEducation.java
class ChineseEducation implements ChineseEducationInterface {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static ChineseEducation createChineseEducation(){
        return new ChineseEducation();
    }

    /**
     *  PUBLIC METHODS
     */
    public void primarySchool(Student s){
        if(!s.getPrimarySchoolGraduate()){
            s.primarySchoolGraduate();
            System.out.println("Congratulation "+s.getName()+"! You are graduated from Chinese primary school!");
        } else {
            System.out.println("Sorry, you are not admissible to primary school!");
        }
    }
    public void middleSchool(Student s){
        if(s.getPrimarySchoolGraduate() && !s.getMiddleSchoolGraduate()){
            s.middleSchoolGraduate();
            System.out.println("Congratulation "+s.getName()+"! You are graduated from Chinese middle school!");
        } else {
            System.out.println("Sorry, you are not admissible to middle school!");
        }
    }
    public void seniorHighSchool(Student s){
        if(s.getMiddleSchoolGraduate() && !s.getSeniorHighSchoolGraduate()){
            s.seniorHighSchoolGraduate();
            System.out.println("Congratulation "+s.getName()+"! You are graduated from Chinese senior high school!");
        } else {
            System.out.println("Sorry, you are not admissible to senior high school!");
        }
    }
    public void university(Student s){
        if(s.getSeniorHighSchoolGraduate() && !s.getUniversityGraduate()){
            s.universityGraduate();
            System.out.println("Congratulation "+s.getName()+"! You are graduated from Chinese university!");
        } else {
            System.out.println("Sorry, you are not admissible to university!");
        }
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private ChineseEducation(){}

}
CanadaEducationInterface.java
/**
 *  Chapter 9 - Interface - Adapter Pattern
 *  Canada education has elementary school, high school, preUniversity and university
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

interface CanadaEducationInterface {

    public void elementarySchool(Etudiant e);
    public void highSchool(Etudiant e);
    public void preUniversity(Etudiant e);
    public void university(Etudiant e);

}
CanadaEducation.java
class CanadaEducation implements CanadaEducationInterface {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static CanadaEducation createCanadaEducation(){
        return new CanadaEducation();
    }

    /**
     *  PUBLIC METHODS
     */
    public void elementarySchool(Etudiant e){
        if(!e.getElementarySchoolGraduate()){
            e.elementarySchoolGraduate();
            System.out.println("Congratulation "+e.getName()+"! You are graduated from Canada's elementary school!");
        } else {
            System.out.println("Sorry, you are not admissible to elementary school!");
        }
    }
    public void highSchool(Etudiant e){
        if(e.getElementarySchoolGraduate() && !e.getHighSchoolGraduate()){
            e.highSchoolGraduate();
            System.out.println("Congratulation "+e.getName()+"! You are graduated from Canada's high school!");
        } else {
            System.out.println("Sorry, you are not admissible to high school!");
        }
    }
    public void preUniversity(Etudiant e){
        if(e.getHighSchoolGraduate() && !e.getPreUniversityGraduate()){
            e.preUniversityGraduate();
            System.out.println("Congratulation "+e.getName()+"! You are graduated from Canada's pre-university!");
        } else {
            System.out.println("Sorry, you are not admissible to pre-university!");
        }
    }
    public void university(Etudiant e){
        if(e.getPreUniversityGraduate() && !e.getUniversityGraduate()){
            e.universityGraduate();
            System.out.println("Congratulation "+e.getName()+"! You are graduated from Canada's university!");
        } else {
            System.out.println("Sorry, you are not admissible to university!");
        }
    }

    /**
     *  PIVATE ACCESS CONSTRUCTOR
     */
    private CanadaEducation(){}

}
Student.java
interface Student {

    public void primarySchoolGraduate();
    public void middleSchoolGraduate();
    public void seniorHighSchoolGraduate();
    public void universityGraduate();

    public String getName();
    public boolean getPrimarySchoolGraduate();
    public boolean getMiddleSchoolGraduate();
    public boolean getSeniorHighSchoolGraduate();
    public boolean getUniversityGraduate();

}
Students.java
/**
 *  Chapter 9 - Interface - Adapter Pattern
 *  Chinese students have to study in primary school, middle school, senior high school,and university
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

class Students implements Student {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static Students createStudents(String name){
        return new Students(name);
    }
    /**
     *  PUBLIC METHODS
     */
    public void primarySchoolGraduate(){
        this.primarySchoolGraduate=true;
    }
    public void middleSchoolGraduate(){
        this.middleSchoolGraduate=true;
    }
    public void seniorHighSchoolGraduate(){
        this.seniorHighSchoolGraduate=true;
    }
    public void universityGraduate(){
        this.universityGraduate=true;
    }

    public String getName(){
        return this.name;
    }
    public boolean getPrimarySchoolGraduate(){
        return this.primarySchoolGraduate;
    }
    public boolean getMiddleSchoolGraduate(){
        return this.middleSchoolGraduate;
    }
    public boolean getSeniorHighSchoolGraduate(){
        return this.seniorHighSchoolGraduate;
    }
    public boolean getUniversityGraduate(){
        return this.universityGraduate;
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Students(String inName){
        this.name=inName;
    }
    /**
     *  PRIVATE FIELDS
     */
    private String name;
    private boolean primarySchoolGraduate=false;
    private boolean middleSchoolGraduate=false;
    private boolean seniorHighSchoolGraduate=false;
    private boolean universityGraduate=false;

}
Etudiant.java
interface Etudiant {

    public void elementarySchoolGraduate();
    public void highSchoolGraduate();
    public void preUniversityGraduate();
    public void universityGraduate();

    public String getName();
    public boolean getElementarySchoolGraduate();
    public boolean getHighSchoolGraduate();
    public boolean getPreUniversityGraduate();
    public boolean getUniversityGraduate();

}
Etudiants.java
class Etudiants implements Etudiant {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static Etudiants createEtudiants(String inName){
        return new Etudiants(inName);
    }

    /**
     *  PUBLIC METHODS
     */
    public void elementarySchoolGraduate(){
        this.elementarySchoolGraduate=true;
    }
    public void highSchoolGraduate(){
        this.highSchoolGraduate=true;
    }
    public void preUniversityGraduate(){
        this.preUniversityGraduate=true;
    }
    public void universityGraduate(){
        this.universityGraduate=true;
    }

    public String getName(){
        return this.name;
    }
    public boolean getElementSchoolGraduate(){
        return this.elementarySchoolGraduate;
    }
    public boolean getHighSchoolGraduate(){
        return this.highSchoolGraduate;
    }
    public boolean getPreUniversityGraduate(){
        return this.preUniversityGraduate;
    }
    public boolean getUniversityGraduate(){
        return this.universityGraduate;
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Etudiants(String inName){
        this.name=inName;
    }

    /**
     *  PRIVATE FIELDS
     */
    private boolean elementarySchoolGraduate=false;
    private boolean highSchoolGraduate=false;
    private boolean preUniversityGraduate=false;
    private boolean universityGraduate=false;

}
InternationalStudents.java
class InternationalStudents implements Student, Etudiant {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static InternationalStudents createInternationalStudents(String name){
        return new InternationalStudents(name);
    }

    /**
     *  PUBLIC METHODS
     *  From Student interface
     */
    public void primarySchoolGraduate(){
        this.primarySchoolGraduate=true;
    }
    public void middleSchoolGraduate(){
        this.middleSchoolGraduate=true;
    }
    public void seniorHighSchoolGraduate(){
        this.seniorHighSchoolGraduate=true;
    }
    public void universityGraduate(){
        this.universityGraduate=true;
    }

    public void elementarySchoolGraduate(){
        this.elementarySchoolGraduate=true;
    }
    public void highSchoolGraduate(){
        this.highSchoolGraduate=true;
    }
    public void preUniversityGraduate(){
        this.preUniversityGraduate=true;
    }

    public String getName(){
        return this.name;
    }


    /**
     *  加拿大初等教育=中国小学+初中
     */
    @Override
    public boolean getPrimarySchoolGraduate(){
        if(this.primarySchoolGraduate || this.elementarySchoolGraduate){
            return true;
        } else {
            return false;
        }
    }
    @Override
    public boolean getElementarySchoolGraduate(){
        if(this.elementarySchoolGraduate || (this.primarySchoolGraduate && this.middleSchoolGraduate)){
            return true;
        } else {
            return false;
        }
    }
    @Override
    public boolean getMiddleSchoolGraduate(){
        if(this.middleSchoolGraduate || this.elementarySchoolGraduate){
           return true;
        } else {
            return false;
        }
    }

    /**
     *  加拿大中学+大学预科=中国高中
     */
    @Override
    public boolean getSeniorHighSchoolGraduate(){
        if(this.seniorHighSchoolGraduate || (this.highSchoolGraduate && this.preUniversityGraduate)){
            return true;
        } else {
            return false;
        }
    }
    @Override
    public boolean getHighSchoolGraduate(){
        if(this.highSchoolGraduate || this.seniorHighSchoolGraduate){
            return true;
        } else {
            return false;
        }
    }
    @Override
    public boolean getPreUniversityGraduate(){
        if(this.preUniversityGraduate || this.seniorHighSchoolGraduate){
            return true;
        } else {
            return false;
        }
    }

    /**
     *  加拿大大学=中国大学
     */
    @Override
    public boolean getUniversityGraduate(){
        return this.universityGraduate;
    }


    /**
     *  PRIVATE CONSTRUCTOR
     */
    private InternationalStudents(String inName){
        this.name=inName;
    }
    /**
     *  PRIVATE FIELDS
     *  From Student interface
     */
    private String name;
    private boolean primarySchoolGraduate=false;
    private boolean middleSchoolGraduate=false;
    private boolean seniorHighSchoolGraduate=false;
    private boolean universityGraduate=false;

    /**
     *  PRIVATE FIELDS
     *  From Etudiant interface
     */
    private boolean elementarySchoolGraduate=false;
    private boolean highSchoolGraduate=false;
    private boolean preUniversityGraduate=false;

}
MyEducation.java
class MyEducation {

    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        InternationalStudents me = InternationalStudents.createInternationalStudents("SHEN");
        ChineseEducation chEdu = ChineseEducation.createChineseEducation();
        CanadaEducation caEdu = CanadaEducation.createCanadaEducation();
        chEdu.primarySchool(me);
        chEdu.middleSchool(me);
        //chEdu.seniorHighSchool(me);
        caEdu.highSchool(me);
        caEdu.preUniversity(me);
        caEdu.university(me);
    }

}

//output:
Congratulation SHEN! You are graduated from Chinese primary school!
Congratulation SHEN! You are graduated from Chinese middle school!
Congratulation SHEN! You are graduated from Canada's high school!
Congratulation SHEN! You are graduated from Canada's pre-university!
Congratulation SHEN! You are graduated from Canada's university!

Factory Method模式

之前的几章已经提到过,有一种类叫工厂类,专门负责实例化某一特定类。属于创建型模式的一种。这是一种很有效的分工,因为有的类实例化过程比较复杂,又有很多变种,为了能更有效地重用代码,实体类负责定义类,工厂类负责实例化,包装类。至于为什么必须要分离出工厂类,是基于下面这个事实:

“Creational patterns become important as systems evolve to depend more on object composition than class inheritance.(《Design Pattern》P-81)”

很多小的组件类“组合”而成的比较庞大的类,是逼迫我们独立出工厂类最主要的动因。因为他们组件众多,光是不同组件型号选择的排列组合已经让他的变种呈现指数级,更不用提创建过程中可能需要的众多外部参数。灵活的工厂类的存在,比固定一个死板的类摆在那儿要合理地多。关于为什么要使用”工厂类”,专门有一篇我在知乎上对这个问题的回答,传送门《为什么要使用“工厂”模式?》

工厂方法模式本质上就是一个工厂类。只不过在此基础上,在实体类和工厂类的上层各自抽象出一个接口,完全面向接口编程:Product=FactoryMethod()。把具体实例化的决定,推迟到runtime。所以工厂方法是简单工厂类的多态加强版! factoryMethodPattern

实践

书后练习19题正好让是练习工厂方法模式。骰子和硬币的工厂类,构成非常标准的工厂方法模式。

Exercise 19: (3) Create a framework using Factory Methods that performs both coin tossing and dice tossing.

结构很简单,两个接口:

每个接口下各两个实体类:

最后的大富翁MononPoly的mononRander()方法,根据不同的以不同的工厂为参数,产生不同的随机器,掷骰子或是抛硬币。所以工厂方法的一大作用就是在游戏内核中构建出这种双向都面向接口的通用方法,让具体操作到具体棋盘上再去决定。大富翁棋盘上有掷骰子和抛硬币,或者是摸道具卡的按钮,所有这类需要随机抽取的按钮都可以用这个方法,在游戏运行的runtime再决定。

mononRander()方法,完全面向两大接口,和底层实现完全无关。

    public void mononRander(RanderFactory inFac){
        Rander theRander=inFac.getRander();
        theRander.tossing();
    }
Rander.java
interface Rander {

    public int tossing();

}
Dice.java
class Dice implements Rander {

    /**
     *  PUBLIC PROXY OF CONSTUCTOR
     */
    public static Dice createDice(){return new Dice();}

    /**
     *  METHODS
     */
    public int tossing(){
        int num=(this.diceRander.nextInt(6))+1;
        System.out.println(num);
        return num;
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Dice(){
        this.diceRander=new Random();
    }

    /**
     *  PRIVATE FIELDS
     */
    Random diceRander;

}
Coin.java
class Coin implements Rander {

    /**
     *  PUBLIC PROXY OF CONSTUCTOR
     */
    public static Coin createCoin(){return new Coin();}

    /**
     *  METHODS
     */
    public int tossing(){
        int num=this.coinRander.nextInt(2);
        System.out.println(num);
        return num;
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Coin(){
        this.coinRander=new Random();
    }

    /**
     *  PRIVATE FIELDS
     */
    Random coinRander;

}
RanderFactory.java
interface RanderFactory {

    public Rander getRander();

}
DiceFactory.java
 class DiceFactory implements RanderFactory {

     public Rander getRander(){
         return Dice.createDice();
     }

}
CoinFactory.java
class CoinFactory implements RanderFactory {

    public Rander getRander(){
        return Coin.createCoin();
    }

}
MononPoly.java
class MononPoly {

    /**
     *  METHODS
     */
    public void mononRander(RanderFactory inFac){
        Rander theRander=inFac.getRander();
        theRander.tossing();
    }

    /**
     *  MAIN void
     */
    public static void main(String[] args){
        MononPoly myGame=new MononPoly();
        RanderFactory diceFac=new DiceFactory();
        RanderFactory coinFac=new CoinFactory();
        myGame.mononRander(diceFac);
        myGame.mononRander(coinFac);
    }

}

练习

####Exercise 9

Exercise 9: (3) Refactor Musics.java by moving the common methods in Wind, Percussion and Stringed into an abstract class.

结构很清楚,目的也很清楚:在接口和实际乐器类之间插入一个虚拟乐器类,可以吧乐器共同的两个方法:play()和adjust()上升到虚拟类层面,减少代码冗余。

Instrument.java
interface Instrument {
    // Compile-time constant:
    int VALUE = 5; // static & final
    // Cannot have method definitions:
    void play(Note n); // Automatically public
    void adjust();
}
Instruments.java
abstract class Instruments implements Instrument {

    public void play(Note n) {
        System.out.println(this + ".play() " + n);
    }
    public void adjust() { System.out.println(this + ".adjust()"); }

}
Wind.java
class Wind extends Instruments {

    public String toString() { return "Wind"; }

}
Percussion.java
class Wind extends Instruments {

    public String toString() { return "Percussion"; }

}
Stringed.java
class Wind extends Instruments {

    public String toString() { return "Stringed"; }

}
Note.java
public enum Note{

    MIDDLE_C

}
Music5.java
public class Music5 {
    // Doesn’t care about type, so new types
    // added to the system still work right:
    static void tune(Instrument i) {
        // ...
        i.play(Note.MIDDLE_C);
    }
    static void tuneAll(Instrument[] e) {
        for(Instrument i : e){
            tune(i);
        }
    }
    public static void main(String[] args) {
        // Upcasting during addition to the array:
        Instrument[] orchestra = {
            new Wind(),
            new Percussion(),
            new Stringed()
        };
        tuneAll(orchestra);
    }
}

//output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C

Exercise 10

(3) Modify Musics.java by adding a Playable interface. Move the play( ) declaration from Instrument to Playable. Add Playable to the derived classes by including it in the implement s list. Change tune( ) so that it takes a Playable instead of an Instrument.

目标是把play()方法从乐曲Instrument接口分离出去,重建一个Playable接口。下面的实际类都同时实现Instrument和Playable两个接口。 最后的Music5.tune()调用Playable接口。

Instrument.java
interface Instrument {
    // Compile-time constant:
    int VALUE = 5; // static & final
    // Cannot have method definitions:
    //void play(Note n); // moved to Playable interface
    void adjust();
}
Playable.java
interface Playable {

    void play(Note n);

}
Instruments.java
//implement both the instrument and playable interface
abstract class Instruments implements Instrument, Playable {

    public void play(Note n) {
        System.out.println(this + ".play() " + n);
    }
    public void adjust() { System.out.println(this + ".adjust()"); }

}
Wind.java
class Wind extends Instruments {

    public String toString() { return "Wind"; }

}
Percussion.java
class Wind extends Instruments {

    public String toString() { return "Percussion"; }

}
Stringed.java
class Wind extends Instruments {

    public String toString() { return "Stringed"; }

}
Note.java
public enum Note{

    MIDDLE_C

}
Music5.java
public class Music5 {
    // Doesn’t care about type, so new types
    // added to the system still work right:
    static void tune(Playable i) {
        // ...
        i.play(Note.MIDDLE_C);
    }
    static void tuneAll(Playable[] ps) {
        for(Playable p : ps){
            tune(i);
        }
    }
    public static void main(String[] args) {
        // Upcasting during addition to the array:
        Playable[] orchestra = {
            new Wind(),
            new Percussion(),
            new Stringed()
        };
        tuneAll(orchestra);
    }
}

//output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C

Exercise 11

(4) Create a class with a method that takes a String argument and produces a result that swaps each pair of characters in that argument. Adapt the class so that it works with interfaceprocessor.Apply.process( ).

结构也很简单,有个通用的处理器接口,主要是process(Object input)方法。然后应用类Apply.process()方法调用这个处理器接口。

核心代码是字符换顺序的处理算法。

Processor.java
interface Processor {

    public String name();
    public Object process(Object input);

}
Apply.java
/**
 *  Chapter 9 - Exercise 11
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;

class Apply {

    public static void process(Processor p, Object s) {
		System.out.println("Using Processor " + p.name());
		System.out.println(s);
		System.out.println(p.process(s));
    }

}
SwapsCharacters.java
class SwapsCharacters implements Processor{

    /**
     *  PUBLIC METHODS
     */
    public String name(){
        return name;
    }

    //main algorithm
    public String process(Object input){
        //String to byte[]
        byte[] byteArray=((String)input).getBytes();
        int index=0;    //iteration flag
        int length=((String)input).length();
        String spaceStr=" ";
        byte[] spaceByte=spaceStr.getBytes();
        //seek 2 characters for each time
        while(index<=length-2){
            //swap the characters if is not space
            if(byteArray[index]!=spaceByte[0] && byteArray[index+1]!=spaceByte[0]){
                //manipulate directly on the byte[]
                byte temp=byteArray[index];
                byteArray[index]=byteArray[index+1];
                byteArray[index+1]=temp;
                index+=2;
            //skip the space
            } else {
                index+=1;
            }
        }
        return new String(byteArray);
    }

    /**
     *  PRIVATE FIELDS
     */
    private static String name="<Swaps Character processor>";

    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        Apply.process(new SwapsCharacters(),"If she weighs the same as a duck, she is made of wood.");
    }

}

//output:
Using Processor Swaps Character processor!
If she weighs the same as a duck, she is made of wood.
fI hse ewgish hte asem sa a udkc, hse si amed fo owdo.

Exercise 16

(3) Create a class that produces a sequence of chars. Adapt this class so that it can be an input to a Scanner object.

Scanner类的作用是扫描一段输入文本。用法如下:

Scanner s = new Scanner(Readable inputStream);
        while(s.hasNext()){
            System.out.println(s.next());
        }

Scanner类构造函数,要求一个实现了Readable接口的类型为参数。Readable接口,定义了一个主要接口int read(CharBuffer cb)方法。该方法负责得到输入文本,加载到CharBuffer里,然后int返回每次read()得到的文本长度(返回-1表示读完)。hasNext()负责输出,检查还有没有后续文本,有的话,调用next()函数继续输出。

我照着书上的例子,做了一个日本人名随机生成器。规则是,日本人名由4组音节组成,每组音节包括一个辅音Consonant和一个元音Vowel。日本常用辅音有{k,s,t,n,h,m,y,r,w},元音有{a,i,u,e,o}

这题的关键意义在于,看到了Java标准库是怎么利用接口,让Scanner可应用于用户自定义扩展组件的。关键就是Scanner是面向接口方法read()编程的,把接口方法当做一个黑箱,只负责返回一组特定类型的数据流,及数据流的长度,这样只要实现了read()方法的类都能在Scanner上工作。真正做到了随意扩展,即插即用。

我认为,要做到这种高度灵活的可扩展而且即插即用的模块化,关键要控制两方面的协议规格:

只要控制住以上两个关键接口规格,就可以把被调用模块看成是高度灵活的即插即用模块。

RandomJpName.java
package com.ciaoshen.thinkinjava.chapter9;
import java.util.*;
import java.nio.*;

/**
 *  Only package access
 */
class RandomJpName implements Readable {

    /**
     *  PUBLIC PROXY OF CONSTUCTOR
     */
    public static RandomJpName createJpNameGenerator(int nameNum){
        return new RandomJpName(nameNum);
    }

    /**
     *  METHODS
     */
    //read() method required by Readable interface
    public int read(CharBuffer inBuffer){
        if(count--==0){
            return -1;
        }
        Random charRander=new Random();
        //Japanese name has 4 syllable
        for(int i=0;i<4;i++){
            inBuffer.append(CONSONANT[charRander.nextInt(CONSONANT.length)]);
            inBuffer.append(VOWEL[charRander.nextInt(VOWEL.length)]);
        }
        inBuffer.append(" ");
        return 9;
    }

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private RandomJpName(int nameNum){
        this.count=nameNum; //initialize the number of name to generate.
    }

    /**
     *  PRIVATE FIELDS
     */
    private static final char[] CONSONANT="kstnhmyrw".toCharArray();
    private static final char[] VOWEL="aiueo".toCharArray();
    int count;

    /**
     *  MAIN void
     */
    public static void main(String[] args){
        Scanner s = new Scanner(RandomJpName.createJpNameGenerator(10));
        while(s.hasNext()){
            System.out.println(s.next());
        }
    }

}

//output:
roroninu
totitase
nitemeku
ruwosuma
wuwukiya
ruyehahi
tewakeko
royuyuna
yaseweho
hotenawe