抽象类与接口

抽象类与接口

面向接口编程可以消除类之间的依赖关系,使得业务只依赖接口。

接口的本质

1.接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则。体现了自然界“如果你是……则必须能……”的理念。

2.接口是在一定粒度视图上同类事物的抽象表示。注意这里我强调了在一定粒度视图上,因为“同类事物”这个概念是相对的,它因为粒度视图不同而不同。

3.在系统分析和架构中,分清层次和依赖关系,每个层次不是直接向其上层提供服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,而不依赖具体类。

接口思想

1.面向接口编程”中的接口是一种思想层面的用于实现多态性、提高软件灵活性和可维护性的架构部件,而具体语言中的“接口”是将这种思想中的部件具体实施到代码里的手段。

  • 看到有朋友对IPerson这个接口的质疑,我个人的理解是,IPerson这个接口该不该定义,关键看具体应用中是怎么个情况。如果我们的项目中有Women和Man,都继承Person,而且Women和Man绝大多数方法都相同,只有一个方法DoSomethingInWC()不同(例子比较粗俗,各位见谅),那么当然定义一个AbstractPerson抽象类比较合理,因为它可以把其他所有方法都包含进去,子类只定义DoSomethingInWC(),大大减少了重复代码量。

  • 但是,如果我们程序中的Women和Man两个类基本没有共同代码,而且有一个PersonHandle类需要实例化他们,并且不希望知道他们是男是女,而只需把他们当作人看待,并实现多态,那么定义成接口就有必要了。

  • 总而言之,接口与抽象类的区别主要在于使用的动机,而不在于其本身。而一个东西该定义成抽象类还是接口,要根据具体环境的上下文决定。
    2.抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。所以,如果你在为某个地方该使用接口还是抽象类而犹豫不决时,那么可以想想你的动机是什么。

接口和abstract类的比较如下

不同

  • 接口中只可以有常量,不能有变量,而abstract类中既可以有常量也可以有变量
  • abstract类中也可以有非abstract方法,接口不可以

相同

  • abstract类和接口都可以有abstract方法

核心

使用接口编程的最重要的核心思想就是使用接口回调,及接口变量存放实现该接口的类的对象的引用,从而接口变量就可以回调类实现的接口方法。

接口

	public interface Advertisement {
		public void showAdvertisement();
		public String getCorpName();
	}

实现类A

	public class Acorp implement Advertisement {
		public void showAdvertisement(){
			System.out.println("AAAAAAAAAAAAAAAAA");
		}
		public String getCorpName(){
			return "A Corp";
		}
	}

实现类B

	public class Bcorp implement Advertisement {
		public void showAdvertisement(){
			System.out.println("BBBBBBBBBBBBBBBBB");
		}
		public String getCorpName(){
			return "B Corp";
		}
	}

设计AdvertisementBoard类(广告牌),该类有一个show(Advertisement adver)方法,该方法的参数是接口Advertisement的类型,显然该参数adver可以存放任何实现Advertisement接口的类的对象的引用,并回调类重写的接口方法showAdvertisement()来显示公司的广告词,回调类重写的接口方法getCorpName来获取公司名称。

方法里面的参数都是接口,跟具体实现类无关

	public class AdvertisementBoard{
		public void show (Advertisement adver){
			System.out.println(adver.getCorpName()+"广告词");
			adver.showAdvertisement(); //接口回调
		}
	}

运行住程序:

		public class test (){
			public state void main (string args[]{
					AdvertisementBoard board = new AdvertisementBoard();
					board.show(new Acorp());
					board.show(new Bcorp());
			}
	}

我们在定义方法的时间,传入参数都是接口,具体实现的时间,传入的是接口的实现类,这样就完成了多态的调用。

我们再来看另外一个深入一点的案例

背景

现在我们要开发一个应用,模拟移动存储设备的读写,即计算机与U盘、MP3、移动硬盘等设备进行数据交换。

上下文(环境)

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

名词定义

数据交换={读,写}

解决方案列举

方案一

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

方案二

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

方案三

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

方案四

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

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

  • 首先,方案一最直白,实现起来最简单,但是它有一个致命的弱点:可扩展性差。或者说,不符合“开放-关闭原则”(注:意为对扩展开放,对修改关闭)。当将来有了第三方扩展移动存储设备时,必须对Computer进行修改。这就如在一个真实的计算机上,为每一种移动存储设备实现一个不同的插口、并分别有各自的驱动程序。当有了一种新的移动存储设备后,我们就要将计算机大卸八块,然后增加一个新的插口,在编写一套针对此新设备的驱动程序。这种设计显然不可取。 此方案的另一个缺点在于,冗余代码多。如果有100种移动存储,那我们的Computer中岂不是要至少写200个方法,这是不能接受的!

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

  • 我们先来评价这种方案:首先它解决了代码冗余的问题,因为可以动态替换移动设备,并且都实现了共同的接口,所以不管有多少种移动设备,只要一个Read方法和一个Write方法,多态性就帮我们解决问题了。而对第一个问题,由于可以运行时动态替换,而不必将移动存储类硬编码在Computer中,所以有了新的第三方设备,完全可以替换进去运行。这就是所谓的“依赖接口,而不是依赖与具体类”,不信你看看,Computer类只有一个MobileStorage类型或IMobileStorage类型的成员变量,至于这个变量具体是什么类型,它并不知道,这取决于我们在运行时给这个变量的赋值。如此一来,Computer和移动存储器类的耦合度大大下降。
    那么 这里该选抽象类还是接口呢?还记得第一篇文章我对抽象类和接口选择的建议吗?看动机。这里,我们的动机显然是实现多态性而不是为了代码复用,所以当然要用接口。

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

在这里,我们姑且认为以后的移动存储都是能读又能写的,所以我们选方案三。

实现

下面,我们要将解决方案加以实现。我选择的语言是Java,所以使用其他语言的朋友一样可以参考。

首先编写IMobileStorage接口:

  • Code:IMobileStorage
		public interface IMobileStorage {
				void Read();                    // 读取数据
				void Write();                   // 写入数据
			}
  • Code:FlashDisk
		public class FlashDisk implements IMobileStorage{
			@Override
			public void Read() {
				System.out.println("Reading from FlashDisk……");
				System.out.println("Read finished!");
			}

			@Override
			public void Write() {
				System.out.println("Writing to FlashDisk……");
				System.out.println("Write finished!");
			}
		}
  • 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!");
			}
		}
  • 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();
		}
		}
  • 运行结果如下:
    在这里插入图片描述
  • 后来

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

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

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

	}

运行结果:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值