面向接口编程详解-Java篇

  相信看到这篇文字的人已经不须要了解什么是接口了,我就再也不过多的作介绍了,直接步入正题,接口测试如何编写。那么在这一篇里,咱们用一个例子,让各位对这个重要的编程思想有个直观的印象。为充分考虑到初学者,因此这个例子很是简单,望各位高手见谅。java

  为了摆脱新手的概念,我这里也尽可能不用main方法,而采用testNG编写测试用例。编程

定义:如今咱们要开发一个应用,模拟移动存储设备的读写,即计算机与U盘、MP三、移动硬盘等设备进行数据交换。设计模式

上下文(环境):已知要实现U盘、MP3播放器、移动硬盘三种移动存储设备,要求计算机能同这三种设备进行数据交换,而且之后可能会有新的第三方的移动存储设备,因此计算机必须有扩展性,能与目前未知而之后可能会出现的存储设备进行数据交换。各个存储设备间读、写的实现方法不一样,U盘和移动硬盘只有这两个方法,MP3Player还有一个PlayMusic方法。ide

名词定义:数据交换={读,写}测试

解决方案列举this

方案一:分别定义FlashDisk、MP3Player、MobileHardDisk三个类,实现各自的Read和Write方法。而后在Computer类中实例化上述三个类,为每一个类分别写读、写方法。例如,为FlashDisk写ReadFromFlashDisk、WriteToFlashDisk两个方法。总共六个方法。编码

方案二:定义抽象类MobileStorage,在里面写虚方法Read和Write,三个存储设备继承此抽象类,并重写Read和Write方法。Computer类中包含一个类型为MobileStorage的成员变量,并为其编写get/set器,这样Computer中只须要两个方法:ReadData和WriteData,并经过多态性实现不一样移动设备的读写。spa

方案三:与方案二基本相同,只是不定义抽象类,而是定义接口IMobileStorage,移动存储器类实现此接口。Computer中经过依赖接口IMobileStorage实现多态性。.net

方案四:定义接口IReadable和IWritable,两个接口分别只包含Read和Write,而后定义接口IMobileStorage接口继承自IReadable和IWritable,剩下的实现与方案三相同。插件

下面,咱们来分析一下以上四种方案:

  首先,方案一最直白,实现起来最简单,可是它有一个致命的弱点:可扩展性差。或者说,不符合“开放-关闭原则”(注:意为对扩展开放,对修改关闭)。当未来有了第三方扩展移动存储设备时,必须对Computer进行修改。这就如在一个真实的计算机上,为每一种移动存储设备实现一个不一样的插口、并分别有各自的驱动程序。当有了一种新的移动存储设备后,咱们就要将计算机大卸八块,而后增长一个新的插口,在编写一套针对此新设备的驱动程序。这种设计显然不可取。

  此方案的另外一个缺点在于,冗余代码多。若是有100种移动存储,那咱们的Computer中岂不是要至少写200个方法,这是不能接受的!

  再看 方案二和方案三,之因此将这两个方案放在一块儿讨论,是由于他们基本是一个方案(从思想层面上来讲),只不过实现手段不一样,一个是使用了抽象类,一个是使用了接口,并且最终达到的目的应该是同样的。

  咱们先来评价这种方案:首先它解决了代码冗余的问题,由于能够动态替换移动设备,而且都实现了共同的接口,因此无论有多少种移动设备,只要一个Read方法和一个Write方法,多态性就帮咱们解决问题了。而对第一个问题,因为能够运行时动态替换,而没必要将移动存储类硬编码在Computer中,因此有了新的第三方设备,彻底能够替换进去运行。这就是所谓的“依赖接口,而不是依赖与具体类”,不信你看看,Computer类只有一个MobileStorage类型或IMobileStorage类型的成员变量,至于这个变量具体是什么类型,它并不知道,这取决于咱们在运行时给这个变量的赋值。如此一来,Computer和移动存储器类的耦合度大大降低。

  那么 这里该选抽象类仍是接口呢?还记得第一篇文章我对抽象类和接口选择的建议吗?看动机。这里,咱们的动机显然是实现多态性而不是为了代码复用,因此固然要用接口。

  最后 咱们再来看一看方案四,它和方案三很相似,只是将“可读”和“可写”两个规则分别抽象成了接口,而后让IMobileStorage再继承它们。这样作,显然进一步提升了灵活性,可是,这有没有设计过分的嫌疑呢?个人观点是:这要看具体状况。若是咱们的应用中可能会出现一些类,这些类只实现读方法或只实现写方法,如只读光盘,那么这样作也是能够的。若是咱们知道之后出现的东西都是能读又能写的,那这两个接口就没有必要了。其实若是将只读设备的Write方法留空或抛出异常,也能够不要这两个接口。总之一句话:理论是死的,人是活的,一切从现实须要来,防止设计不足,也要防止设计过分。

  在这里,咱们姑且认为之后的移动存储都是能读又能写的,因此咱们选方案三

 

实现

下面,咱们要将解决方案加以实现。我选择的语言是Java,因此使用其余语言的朋友同样能够参考。

首先编写IMobileStorage接口:

Code:IMobileStorage

1 public interface IMobileStorage {
2 
3     void Read();                    // 读取数据
4     void Write();                   // 写入数据
5 
6 }

  代码比较简单,只有两个方法,没什么好说的,接下来是三个移动存储设备的具体实现代码:

 

U盘

Code:FlashDisk

 1 public class FlashDisk implements IMobileStorage{
 2     @Override
 3     public void Read() {
 4         System.out.println("Reading from FlashDisk……");
 5         System.out.println("Read finished!");
 6     }
 7 
 8     @Override
 9     public void Write() {
10         System.out.println("Writing to FlashDisk……");
11         System.out.println("Write finished!");
12     }
13 }

MP3

Code:MP3Player

public class MP3Player implements IMobileStorage{
    @Override
    public void Read() {
        System.out.println("Reading from MP3Player……");
        System.out.println("Read finished!");
    }

    @Override
    public void Write() {
        System.out.println("Writing to MP3Player……");
        System.out.println("Write finished!");
    }

    public void PlayMusic(){
        System.out.println("Music is playing……");
    }
}

移动硬盘

Code:MobileHardDisk

public class MobileHardDisk implements IMobileStorage{

    @Override
    public void Read() {
        System.out.println("Reading from MobileHardDisk……");
        System.out.println("Read finished!");
    }

    @Override
    public void Write() {
        System.out.println("Writing to MobileHardDisk……");
        System.out.println("Write finished!");
    }

}

  能够看到,它们都实现了IMobileStorage接口,并重写了各自不一样的Read和Write方法。下面,咱们来写Computer:

Code:Computer

public class Computer {
    private IMobileStorage _usbDrive;

    public IMobileStorage get_usbDrive() {
        return _usbDrive;
    }

    public void set_usbDrive(IMobileStorage _usbDrive) {
        this._usbDrive = _usbDrive;
    }

    public Computer(){}

    public Computer(IMobileStorage _usbDrive) {
        this._usbDrive = _usbDrive;
    }

    public void ReadData(){
        this._usbDrive.Read();
    }

    public void WriteData(){
        this._usbDrive.Write();
    }
}

  其中的UsbDrive就是可替换的移动存储设备,之因此用这个名字,是为了让你们以为直观,就像咱们日常使用电脑上的USB插口插拔设备同样。

OK!下面咱们来测试咱们的“电脑”和“移动存储设备”是否工做正常。我是用的Java控制台程序打印结果,具体代码以下:

Code:测试代码

public class ToTest {
    @Test
    public void program1(){
        Computer computer = new Computer();
        IMobileStorage mp3Player = new MP3Player();
        IMobileStorage flashDisk = new FlashDisk();
        IMobileStorage moblieHardDisk = new MobileHardDisk();

        System.out.println("I inserted my MP3 Player into my computer and copy some music to it:");
        computer.set_usbDrive(mp3Player);
        computer.WriteData();
        System.out.println("====================");

        System.out.println("Well,I also want to copy a great movie to my computer from a mobile hard disk:");
        computer.set_usbDrive(moblieHardDisk);
        computer.ReadData();
        System.out.println("====================");

        System.out.println("OK!I have to read some files from my flash disk and copy another file to it:");
        computer.set_usbDrive(flashDisk);
        computer.ReadData();
        computer.WriteData();
        System.out.println();
    }

 

运行结果以下:

图2.1 各类移动存储设备测试结果

              图2.1 各类移动存储设备测试结果

 

  好的,看来咱们的系统工做良好。

  后来……

 

  刚过了一个星期,就有人送来了新的移动存储设备NewMobileStorage,让我测试能不能用,我微微一笑,心想这不是小菜一碟,让咱们看看面向接口编程的威力吧!将测试程序修改为以下:

 (NewMobileStorage的类请参照u盘、移动硬盘等类编写……也能够自创)

测试代码

    @Test
    public void program2(){
        Computer computer = new Computer();
        IMobileStorage newMobileStorage = new NewMoblieStorage();
        computer.set_usbDrive(newMobileStorage);
        newMobileStorage.Write();
        newMobileStorage.Read();

    }

运行结果:

              图2.2 新设备扩展测试结果

 

  又过了几天,有人通知我说又有一个叫SuperStorage的移动设备要接到咱们的Computer上,我心想来吧,管你是“超级存储”仍是“特级存储”,个人“面向接口编程大法”把大家通通搞定。

  可是,当设备真的送来,我傻眼了,开发这个新设备的团队没有拿到咱们的IMobileStorage接口,天然也没有遵守这个约定。这个设备的读、写方法不叫Read和Write,而是叫rd和wt,这下完了……不符合接口啊,插不上。可是,不要着急,咱们回到现实来找找解决的办法。咱们一块儿想一想:若是你的Computer上只有USB接口,而有人拿来一个PS/2的鼠标要插上用,你该怎么办?想起来了吧,是否是有一种叫“PS/2-USB”转换器的东西?也叫适配器,能够进行不一样接口的转换。对了!程序中也有转换器。

  这里,我要引入一个设计模式,叫“Adapter”。它的做用就如现实中的适配器同样,把接口不一致的两个插件接合起来。因为本篇不是讲设计模式的,并且Adapter设计模式很好理解,因此我就不细讲了,先来看我设计的类图吧:

  如图所示,虽然SuperStorage没有实现IMobileStorage,但咱们定义了一个实现IMobileStorage的SuperStorageAdapter,它聚合了一个SuperStorage,并将rd和wt适配为Read和Write,SuperStorageAdapter(这里注意自行编写SuperStorage的类和他用到的接口

            图2.3 Adapter模式应用示意

  

具体代码以下:

Code:SuperStorageAdapter

 1 public class SuperStorageAdapter implements IMobileStorage {
 2     private SuperStorage _superStorage;
 3 
 4     public SuperStorage get_superStorage() {
 5         return _superStorage;
 6     }
 7 
 8     public void set_superStorage(SuperStorage _superStorage) {
 9         this._superStorage = _superStorage;
10     }
11 
12     @Override
13     public void Read(){
14         this._superStorage.rd();
15     }
16 
17     @Override
18     public void Write() {
19         this._superStorage.wt();
20     }
21 }

好,如今咱们来测试适配过的新设备,测试代码以下:

Code:测试代码

  @Test
    public void program3(){
        Computer computer = new Computer();
        SuperStorageAdapter superStorageAdapter = new SuperStorageAdapter();
        SuperStorage superStorage = new SuperStorage();
        superStorageAdapter.set_superStorage(superStorage);

        System.out.println("Now,I am testing the new super storage with adapter:");
        computer.set_usbDrive(superStorageAdapter);
        computer.ReadData();
        computer.WriteData();
        System.out.println();
    }

运行结果:

          图2.4 利用Adapter模式运行新设备测试结果

 

 

  OK!虽然遇到了一些困难,不过在设计模式的帮助下,咱们仍是在没有修改Computer任何代码的状况下实现了新设备的运行。但愿各位朋友结合第一篇的理论和这个例子,仔细思考面向接口的问题。固然,不要忘告终合现实。

 

原文是C#的语言,在这里作了java的转换编写,感谢原做者,转自C站的一小平民:http://blog.csdn.net/boer521314/article/details/40378151

相关文章
相关标签/搜索