使用抽象类和接口进行灵活的代码设计,意味着对面向对象设计概念有了充分的理解。这是需要大量的程序代码累计而成的。
接口的基本定义
抽象类与普通类相比,最大的优势,是可以实现对子类覆写方法的控制。但是在抽象类中依然会保留普通方法。而普通方法里可能会涉及到安全或隐私的操作问题。在开发过程中,如果要想对外隐藏全部的实现细节,则可以通过接口来进行描述。具体在之后的“分布式”开发中,会有具体的解释说明。
接口可以理解为一个纯粹的抽象类,在最原始的定义中,接口中只包含有抽象方法和全局常量。但是,从 JDK 1.8 开始,由于引入了 Lambda 表达式的概念,所以接口的定义也得到了加强,除了抽象方法与全局常量之外,还可以定义普通方法和静态方法。如果从设计本身的角度来讲,接口之中的组成还应该以抽象方法和全局常量为主。
在 Java 中,接口主要使用 interface
关键字来定义。接口的命名,约定俗成的使用大写字母 “I”(Interfacce 的缩写)开头,后面跟上普通类的命名方式(各单词首字母大写)。如:
interface IMessage{
public static final String INFO = "hello";
public abstract void getInfo();
}
此时的接口肯定无法直接产生实例化对象,因为有抽象方法,并没有实现。所以对于接口的使用,原则如下:
- 接口需要被子类实现(
implements
),一个子类可以实现多个接口; - 子类如果不是抽象类,那么一定要覆写接口之中的全部抽象方法;
- 接口对象可以利用子类对象的向上转型进行实例化。
接口的实现,一般在普通的类名后跟上 ”Impl“,表示接口的实现。
在上例的代码中,INFO 常量可以直接通过 IMessage.INFO 调用,因为是全局常量。
getInfo() 方法必须要在子类中实例化后使用。如:
class MessageImpl implements IMessage{
public void getInfo(){
System.out.println("Hello World!");
}
}
使用时:
String str = IMessage.INFO;
IMessage msg = new MessageImpl();
msg.getInfo();
在 Java 中之所以使用接口主要的目的是一个子类可以实现多个接口。利用接口可以实现多继承的概念。 如:
class MessageImpl implements IMessage, IChannel{}
实际举例
接口的概念类似现实生活中的"协议标准"的概念。如:USB 接口协议。凡是采用 USB 接口协议的设备,都可以根据协议,传输数据。
接口的转型问题
实现(多个)接口的子类,既是(多个)接口的子类,同时还是 Object 类的子类。
所以,这个子类的对象,可以向上转型成(多个)接口对象,也可以向上转型成 Object 对象。同理,向上转型后,可以向下转型成(多个中任意一个)接口的对象。
另外 ,在 Java 程序中,接口不允许继承父类。接口不会是 Object 的子类。
所以,可以接收 Object 类对象,就意味着可以接收所有的数据类型:基本数据类型、类对象、接口对象、数组。
访问权限问题
由于接口描述的是公共的定义标准,所以在接口中所有抽象方法的访问权限,都为 public。
虽然可以省略,部分关键字,但是,推荐用上述的方法定义接口中的全局常量与抽象方法。如下重复:
interface 接口名称{
public static final 全局常量类型 全局常量名 = 内容;
public abstract 返回值类型 方法名;
}
抽象类与接口
区别
序号 | 区别 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义 | abstract class 抽象类名称 {} |
interface 接口名称 {} |
2 | 组成 | 构造方法、普通方法、静态方法、全局常量、属性、成员常量 | 抽象方法、全局常量(普通方法、static 方法) |
3 | 权限 | 各种权限定义 | 只能使用 public |
4 | 子类使用 | 子类通过 extends 继承一个父类。 |
子类通过 implements 继承多个父接口。 |
5 | 两者关系 | 抽象类可以实现多个接口。 | 接口不允许继承抽象类,但是允许继承多个父接口。 |
6 | 继承与实现 | (同普通类)继承一个父类,实现多个接口。 | 继承多个接口。 |
共同点
- 抽象类和接口必须定义子类;
- 子类必须覆写父类或父接口的所有抽象方法;
- 通过子类的向上转型实现抽象类或接口的对象实例化。
另外
- 虽然一个接口无法继承一个父类,但是一个接口可以通过
extends
继承若干个父接口,称为接口的多继承。 - 先继承后实现,先
extends
后implements
。 - 当抽象类和接口都可以使用的情况下,优先考虑接口,因为接口可以避免子类的单继承局限。
从代码设计角度而言,也需要通过接口,对项目进行整体的设计。
组合使用
若接口中的方法在多个子类中有完全相同的实现,则可以在接口与普通子类中增加一个抽象类,将其相同的方法实现后,通过子类继承的形式使用。如:
interface IMessage {
public abstract void f1();
public abstract void f2();
}
abstract class MessageAbstractImpl {
public void f1(){
// ...
}
}
class MessageImplA extends MessageAbstractImpl implements IMessage{
public void f2(){ // ... }
// ...
}
class MessageImplB extends MessageAbstractImpl implements IMessage{
public void f2(){ // ... }
}
class MessageImplC implements IMessage{
public void f1(){ // ... }
public void f2(){ // ... }
}
实际用处
在实际的开发过程中,接口的使用往往有三种形式:
- 进行标准设置;
- 表示一种操作能力;
- 暴露远程方法视图,一般在 RPC 分布式开发中使用。
重要的设计模式
工厂设计模式
当一个接口有多个类的去实现的时候,可以通过工厂设计,将类的实例化置于主客户端之外。具体操作:
定义一个 Factory 类,内部定义 public static 的 getInstance() 方法,需要接收参数:String className。代码如下(接 组合使用 中的代码):
class Factory{
public static IMessage getInstance(String className){
if ("MessageImplA".equals(className)){
return new MessageImplA();
}
if ("MessageImplB".equals(className)){
return new MessageImplB();
}
if ("MessageImplC".equals(className)){
return new MessageImplC();
}
}
}
主类(客户端)中,通过 Factory.getInstance() 创建接口的子类并向上转型,得到接口的实现。如下:
IMessage msg = Factory.getInstance("MessageImplB");
代理设计模式
当一个接口实现的调用前后,需要执行许多其他的方法,如准备、检查参数格式与数量、收尾等,不重要但是又必须,适合与该接口实现的调用一同捆绑打包,则可以用一个继承接口的 Proxy 将代码捆绑置于主客户端之外。举例如下(接 组合使用 中的代码):
class Proxy implements IMessage{
private IMessage msg;
public Proxy(IMessage msg){
this.msg = msg;
}
public void f1(){ // 覆写接口方法
// ...
// ...
this.mgs.f1();
// ...
}
}
主类(客户端)中:
IMessage msg = new Proxy(new MessageImplB());
工厂设计模式与代理设计模式的结合
保留上述所有代码,最后主类中的代码替换为:
IMessage msg = new Proxy(Factory.getInstance("MessageImplB"));
即可将接口的实例化与其次要功能捆绑,置于主客户端之外。具体可参考下部分实例,了解其实际运用。
题:模拟绘图。
结果:
代码实现:
interface IDraw{
public abstract void draw(double... numbers);
}
class Point{
private double x;
private double y;
public Point(double x, double y){
this.setX(x);
this.setY(y);
}
public void setX(double x){
this.x = x;
}
public void setY(double y){
this.y = y;
}
public double getX(){
return this.x;
}
public double getY(){
return this.y;
}
public double[] getPoint(){
return new double[]{this.getX(), this.getY()};
}
@Override
public boolean equals(Object anObject){
if (this == anObject){
return true;
}else{
if (anObject instanceof Point){
Point aPoint = (Point) anObject;
if (this.getX() == aPoint.getX() &&
this.getY() == aPoint.getY()){
return true;
}
}
}
return false;
}
@Override
public String toString() {
return "[" + this.getX() + "," + this.getY() + "]";
}
}
class DrawCircle implements IDraw{
@Override
public void draw(double[] numbers) {
Point point = new Point(numbers[0], numbers[1]);
double radius = numbers[2];
System.out.println("【DRAW CIRCLE START】");
System.out.println("Center: " + point + ", radius: " + radius + ".");
System.out.println("Draw a circle!");
System.out.println("Complete!");
}
}
class DrawTriangle implements IDraw{
@Override
public void draw(double[] numbers) {
Point[] points = new Point[3];
for (int n = 0; n <= 2; n++){
points[n] = new Point(numbers[2 * n], numbers[2 * n + 1]);
}
System.out.println("【DRAW TRIANGLE START】");
for (int n = 1; n <= 3; n++){
if (n <= 2){
System.out.println("Point" + n + ": " + points[n - 1]);
System.out.println("Point" + (n + 1) + ": " + points[n]);
System.out.println("Draw a line.");
}else{
System.out.println("Point" + n + ": " + points[n - 1]);
System.out.println("Point" + 1 + ": " + points[0]);
System.out.println("Draw a line.");
}
}
System.out.println("Complete!");
}
}
class DrawRectangle implements IDraw{
@Override
public void draw(double[] numbers) {
Point startPoint = new Point(numbers[0], numbers[1]);
double width = numbers[2];
double height = numbers[3];
Point[] points = new Point[]{
startPoint,
new Point(startPoint.getX() + width, startPoint.getY()),
new Point(startPoint.getX() + width, startPoint.getY() + height),
new Point(startPoint.getX(), startPoint.getY() + height)
};
System.out.println("【DRAW RECTANGLE START】");
for (int n = 1; n <= 4; n++){
if (n <= 3){
System.out.println("Point" + n + ": " + points[n - 1]);
System.out.println("Point" + (n + 1) + ": " + points[n]);
System.out.println("Draw a line.");
}else{
System.out.println("Point" + n + ": " + points[n - 1]);
System.out.println("Point" + 1 + ": " + points[0]);
System.out.println("Draw a line.");
}
}
System.out.println("Complete!");
}
}
class DrawProxy implements IDraw{
IDraw drawInstance;
public DrawProxy(IDraw drawInstance){
this.drawInstance = drawInstance;
}
@Override
public void draw(double... numbers) {
if (drawInstance instanceof DrawCircle){
if (numbers.length != 3){
System.out.println("输入的数字有误!");
}else{
drawInstance.draw(numbers);
}
}
if (drawInstance instanceof DrawTriangle){
if (numbers.length != 6){
System.out.println("输入的数字有误!");
}else{
drawInstance.draw(numbers);
}
}
if (drawInstance instanceof DrawRectangle){
if (numbers.length != 4){
System.out.println("输入的数字有误!");
}else{
drawInstance.draw(numbers);
}
}
}
}
class DrawFactory{
public static IDraw getInstance(String shape){
if ("圆".equals(shape)){
return new DrawCircle();
}
if ("矩形".equals(shape)){
return new DrawRectangle();
}
if ("三角形".equals(shape)){
return new DrawTriangle();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
printLineStart("Start");
System.out.println();
new DrawProxy(DrawFactory.getInstance("圆")).draw(0, 0, 1);
System.out.println();
new DrawProxy(DrawFactory.getInstance("圆")).draw(0, 0);
System.out.println();
new DrawProxy(DrawFactory.getInstance("矩形")).draw(0, 0, 1, 2);
System.out.println();
new DrawProxy(DrawFactory.getInstance("三角形")).draw(0, 0, 0, 1, 1, 2);
System.out.println();
printLineStart("End");
}
public static void printLineStart ( String diff ){
switch (diff){
case "Start":{
System.out.println("------\t程序开始\t------");
break;
}
case "End":{
System.out.println("------\t程序结束\t------");
break;
}
default:{
return;
}
}
}
}