程序的与时俱进之一——面向接口编程

本文探讨了如何使程序设计具有良好的可扩展性和多态性,重点介绍了面向接口编程的概念和优势,通过具体的货运设备装货与卸货场景,对比了不同的设计方案,最终选择了使用接口的方式,以实现程序的灵活扩展。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

马克思曾说,运动是物质固有的根本属性,是一切物质形态的存在方式。万事万物无不在运动之中。一个好的程序不但需要经受bug的考验,还得经受时间的冲刷。那些不能够随时代而更迭的程序,无一不被时代淘汰,成为历史。

既然时代在交替,而程序在设计结束之时就完成了,那我们怎么让程序做到与时俱进呢?有一条不得不说的原则:“开放-关闭原则”(注:意为对扩展开放,对修改关闭)。要让程序能够符合这一原则,面向接口编程这一概念就呼之欲出了。

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

如同每台电脑有USB接口、充电器接口等等不同接口。同一个接口,接入的设备不同则对应的功能不同。JAVA的接口也是如此,如果我们开放了接口,程序的可拓展新就能够得到满足。举个例子:同样的“读”、“写”功能,或许U盘和MP3Player的不同,但只要它们继承了接口来重写方法,则最终在电脑里调用时都是调用read()、write()。

 

下面用一个例子来具体说明:

假设:现在我们要开发一个应用,模拟不同类型货车的装货与卸货。

上下文(环境):已知要实现轻型卡车(下面简称轻卡)、重型卡车(下面简称重卡)、火车三种运货设备,要求工厂能同这三种货车进行物资的装载或者卸载,并且以后可能会有新的运货机器出现,所以工厂能够装货的车型必须有扩展性,能与目前未知而以后可能会出现的货车进行物资交换。各种货车间上货、下货的实现方法不同,轻卡和重卡只有这两个方法,火车还有一个车厢分离(假设为Separating())方法。

名词定义:数据交换={装,卸}

解决方案列举

方案一:分别定义LightTruck、HeavyTruck、Train三个类,实现各自的Load()和UnLoad()方法。然后在Factory类中实例化上述三个类,为每个类分别写装,卸方法。例如,为LightTruck写Load、UnLoad两个方法。总共六个方法。

方案二:定义抽象类Trunk,在里面写虚方法Load和UnLoad,三个货运设备继承此抽象类,并重写Load和UnLoad方法。Factory类中包含一个类型为Car的成员变量,并为其编写get/set器,这样Factory中只需要两个方法:Loading_cargo和Unloading_cargo,并通过多态性实现不同移动设备的读写。

方案三:与方案二基本相同,只是不定义抽象类,而是定义接口ITrunk,三个货运类实现此接口。Factory中通过依赖接口ITrunk实现多态性。

 

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

  首先,方案一最容易想到和实现,but它有一个致命的弱点:可扩展性差。当将来时代变迁了,有了新的货运设备时,必须对Factory进行修改。这就如在一个真实的工厂里,为每一种货运设备修一块各自的停车场、并分别有各自的管理设施。当有了一种新的货运设备后,我们就要将原来的场地全部拆除重新划分,再给新的货运设备一块场地。这种设计显然不可取。

  此方案的另一个缺点在于,冗余代码多。如果有100种货运设备,那我们的Factory类中岂不是要至少写200个方法? It's Unacceptable.

  再看 方案二和方案三,他们基本是一个方案,只不过实现手段不同,一个是使用了抽象类,一个是使用了接口,而且最终达到的目的应该是一样的。

  首先很明显的是:它解决了代码冗余的问题。因为可以动态替换货运方式,并且都实现了共同的接口,所以不管有多少种货运方式,只要一个Load方法和一个Unload方法,多态性就帮我们解决问题了。而对第一个问题,由于可以运行时动态替换,所以有了新的设备时,完全可以替换进去运行。这就是所谓的“依赖接口,而不是依赖与具体类”,不信你看看,Factory类只有一个Trunk类型或ITrunk类型的成员变量,至于这个变量具体是什么类型,它并不知道,这取决于我们在运行时给这个变量的赋值。如此一来,Factory和Trunk类的耦合度大大下降。

  那么 这里该选抽象类还是接口呢?看动机。这里,我们的动机显然是实现多态性而不是为了代码复用,所以当然要用接口。

      所以,我们选择方案三来实现:

 

首先编写ITrunk接口:

public interface ITrunk {
	void Load();        //装货
	void Unload();    //卸货
}

然后是三种交通工具的类,都继承ITrunk接口,并重写Load()和Unload():

public class LightTruck implements ITrunk{

	public void Load() {
		System.out.println("Load to LightTruck");
		System.out.println("Load finished!");
	}

	public void Unload() {
		System.out.println("Unload from LightTruck");
		System.out.println("Unload finished!");		
	}	

}
public class HeavyTruck implements ITrunk{
	public void Load() {
		System.out.println("Load to HeavyTruck");
		System.out.println("Load finished!");
	}

	public void Unload() {
		System.out.println("Unload from HeavyTruck");
		System.out.println("Unload finished!");		
	}	
}
public class Train implements ITrunk{
	public void Load() {
		System.out.println("Load to Train");
		System.out.println("Load finished!");
	}

	public void Unload() {
		System.out.println("Unload from Train");
		System.out.println("Unload finished!");		
	}	
	
	//车厢分离
	public void Separating(){
		System.out.println("The carriage is separating.");
	}
}

下面来写Factory类:

public class Factory {
	private ITrunk  trunk;

	public Factory() {
		this.trunk = null;
	}
	
	public Factory(ITrunk trunk) {
		this.trunk = trunk;
	}

	public ITrunk getTrunk() {
		return trunk;
	}

        //设置用何种交通工具
	public void setTrunk(ITrunk trunk) {
		this.trunk = trunk;
	}
	
	//工厂类实现装货
	public void Loading_cargo(){
		this.trunk.Load();
	}
	
	//工厂类实现卸货
	public void Unloading_cargo(){
		this.trunk.Unload();
	}
	
}

到此,所有的类就写完了。结果对不对呢?我们再写一个Test类来检验一下:

public class Test {
	public static void main(String args[]){
		Factory myfac = new Factory();
		ITrunk  mytunk1 = new LightTruck();
		ITrunk  mytunk2 = new HeavyTruck();
		ITrunk  mytunk3 = new Train();
		
		//用三种货车来运输
		System.out.println("1.use LightTruck:");
		myfac.setTrunk(mytunk1);
		myfac.Loading_cargo();
		System.out.println("=================");
		
		System.out.println("2.use HeavyTruck:");
		myfac.setTrunk(mytunk2);
		myfac.Loading_cargo();
		System.out.println("=================");
		
		System.out.println("3.use Train:");
		myfac.setTrunk(mytunk3);
		myfac.Loading_cargo();
		System.out.println("=================");
	}
}

运行结果如下:

可见,结果完全符合预期。如果按照面向接口编程的思路来写程序,则程序的多态性和可扩展性都将变得容易操作。

然而,在java中还有一种机制能实现不断扩展程序的功能,更好的实现与时俱进,名唤“反射”。反射顾名思义,就如同一面镜子,它将当前已经有的类照下来,并复制一份作为副本,这个过程我们可以得到原有类的构造器及参数,得到原有类的方法,我们可以操作这个类去实现新的功能,让它成为一个新的类。

详情请看下一篇文章,程序的与时俱进之二——JAVA的反射机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值