第八章、接口

8.1 接口的概念和基本特征

在 Java 语言中,接口有两种意思:

  • 一是指概念性的接口,即指系统对外提供的所有服务。类的所有能被外部使用者访问的方法构成了类的接口。
  • 二是指用 interface 关键字定义的实实在在的接口,也称为接口类型。它用于明确地描述系统对外提供的所有服务,它能够更加清晰地把系统的实现细节与接口分离。

接口对其成员变量和方法做了许多限制,接口的特征归纳如下:

  • 接口中的 成员变量 默认都是 public static final类型的,必须被显式初始化。并且接口中只能包含 public、static、final 类型的成员变量。
  • 接口中的 方法 默认都是 public abstract 类型的。
  • 在 JDK8 以前的版本中,接口只能包含抽象方法。从 JDK8 开始,为了提高代码的可重用性,允许在接口中定义默认方法和静态方法。 默认方法用 default 关键字来声明,拥有默认的实现。接口的实现类既可以直接访问默认方法,也可以覆盖它,重新实现该方法。
    //以下MyIFC接口中分别定义了一个默认方法、一个静态方法和一个抽象方法 
    public interface MyIFC {
        int var = 0;
        default void m1() {
            System.out.println("default method");   //声明一个默认方法
        }
        static void m2(){
            System.out.println("static method");    //声明一个静态方法
        }
        void method();                              //声明一个抽象方法       
    }
    //以下Tester类实现了MylFC接口。该类作为非抽象类,必须实现 MyIFC 接口的抽象方法。 
    // Tester 的实例可以直接访问在接口中定义的默认方法
    public class Tester implements MyIFC {
        public void method() {
            System.out.println("method");           //实现抽象方法  
        }
        public static void main(String[] args) {
            Tester t = new Tester();
            t.m1();                             //直接访问接口中的默认方法
            t.m2();                             //编译出错,Tester实例不能访问MyIFC接口静态方法
            MyIFC.m2();                         //合法,通过接口名字访问其静态方法
            t.method();
        }
    }
    
  • 接口没有构造方法,不能被实例化。在接口中定义构造方法是非法的。
  • 一个接口不能实现另一个接口,但它可以继承多个其他接口。
    public interface A {
        void methodA();
    }
    public interface B {
        void methodB();
    }
    public interface C extends A, B {
        void methodC();                             //接口C是复合接口
    }
    
  • 与子类继承抽象父类相似,当类实现了某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须被定义为抽象类。
  • 不允许创建接口的实例,但允许定义接口类型的引用变量,该变量引用实现了这个接口的类的实例。
    Photographable o = new Camera();     //引用变量t被声明为Photographble接口类型并引用Camera实例
    
  • 一个类只能继承一个直接的父类,但能实现多个接口。

8.2 比较抽象类与接口

  • 抽象类与接口都位于继承树的上层,它们具有以下 相同 点:

    • 代表系统的抽象层,当一个系统使用一棵继承树上的类时,应该尽可能地把引用变量声明为继承树的上层抽象类型,这可以提高两个系统之间的松耦合。
    • 都不能被实例化 。
    • 都能包含抽象方法,这些抽象方法用于描述系统能提供哪些服务,但不必提供具体的实现。
    • 从 JDK8 版本开始,不仅抽象类能为部分方法提供默认的实现,接口也具有这一特性。该特性可以避免在子类或者实现类中重复实现方法,这能提高代码的可重用性。
  • 抽象类与接口主要有两大 区别

    • 接口中的 成员变量 只能是 public、static 和 final 类型的,而在抽象类中可以定义各种类型的实例变量和静态变量,这是抽象类的优势所在,它可以包含所有子类的共同成员变量,避免在子类中重复定义。
    • 一个类只能继承一个直接的父类,这个父类有可能是抽象类;但一个类可以 实现多个接口,这是接口的优势所在。

借助接口,可以方便地对已经存在的系统进行自下而上的抽象。对于任意两个类,不管它们是否属于同一个父类,只要它们存在着相同的功能,就能从中抽象出一个接口类型。接口中定义了系统对外提供的一组相关的服务,接口并不强迫它的实现类在语义上是同一种类型。对于两个不同的系统,通过接口交互比通过抽 象类来交互能够获得更好的松耦合。

  • 接口和抽象类的总的 使用原则 如下:
    • 用接口作为系统与外界交互的窗口,站在外界使用者(另一个系统)的角度,接口向使用者承诺系统能提供哪些服务;站在系统本身的角度,接口指定系统必须实现哪些服务。接口是系统中最高层次的抽象类型。这里的系统既可以指整个大系统,也可以指完成特定功能的相对独立的局部系统。
    • 由于外界使用者依赖系统的接口,并且系统内部会实现接口,因此接口本身必须十分稳定,接口一旦指定,就不允许随意修改,否则会对外界使用者及系统内部都造成影响。
    • 用抽象类来定制系统中的扩展点。可以把抽象类看作是介于“抽象”和“实现”之间的半成品,抽象类力所能及地完成了部分实现,但还有一些功能有待于它的子类去实现。

8.3 与接口相关的设计模式

考虑以下问题:
• 如何设计接口?定制服务模式提出了设计精粒度的接口的原则。
• 当两个系统之间接口不匹配时,如何处理?适配器模式提供了接口转换方案。
• 当系统 A 无法便捷地引用系统 B 的接口的实现类实例时,如何处理? 代理模式提供了为系统 B 的 接口的实现类提供代理类的解决方案。

定制服务模式

例如,电信公司会制定各种各样的服务套餐,满足各种客户的需求。

//抽象出3个精粒度的接口代表三种服务:
//宽带上网服务BroadBandService 网络硬盘服务NetworkDiskService 邮箱服务MailboxService
public interface BroadBandService { 
    void connecect();
    void disconnect();
}
public interface NetworkDiskService { 
    void write();
    void read();
}
public interface MailboxService {
    void sendMail();
    void receiveMail();
}

//两种套餐属于两种定制服务接口,通过继承以上接口而成,称作复合接口
public interface TelecomPackage1 
    extends BroadBandService, MailboxService {}

public interface TelecomPackage2 
    extends BroadBandService, MailboxService, NetworkDiskService {}

//宽带上网服务接口实现
public class BroadBandServiceImpl implements BroadBandService {
    private int speed;
    public BroadBandServiceImpl(int speed) {
        this.speed = speed;
    }
    public void connect(String username, String password) {...}
    public void disconnect(){...}
}

// 套餐付费方式价格属性放入Payment类中
public class Payment {
    public static final String TYPE_PER_YEAR = "按年付费";
    public static final String TYPE_PER_MONTH = "按月付费";

    private String type;
    private double price;
    public Payment(String type, double price) {
        this.type = type;
        this.price = price;
    }

    // get set 方法
    ...
}

//套餐一类的源程序
public class TelecomPackage1Impl implements TelecomPackage1 {
    private BroadBandService broadBandService;
    private MailboxService mailboxService;
    private Payment payment;

    public TelecomPackage1Impl (BroadBandService broadBandService,
                    MailboxService mailboxService,
                    Payment payment) {
        this.broadBandService = broadBandService;
        this.mailboxService = mailboxService;
        this.payment = payment;
    }

    public void connect(String username, String password) {
        broadBandService.connect(username, password);
    }
    public void disconnect() {
        broadBandService.disconnect();
    }
    public void sendMail() {
        mailboxService.sendMail();
    }
    public void receiveMail() {
        mailboxService.receiveMail();
    }
    public Payment getPayment() {
        reutrn payment;
    }
}
//创建套餐一的实例
Payment payment = new Payment(Payment.TYPE_PER_YEAR, 2999);
BroadBandService broadBandService = new BroadBandServiceImpl(2);
MailboxService mailboxService = new  MailboxServiceImpl(50);

TelecomPackage1 telecomPackage1 = new TelecomPackage1Impl(broadBandService, mailboxService, payment);

适配器模式

日常生活中,会经常遇到一些适配器,例如笔记本电脑的变压器,就是典型的电源适配器,它是连接笔记本电脑和普通电源的桥梁。

在面向对象领域,也采用适配器模式来进行接口的转换,适配器模式有两种实现方式:

  • 继承实现方式:
    //SourceIFC 和 TargetIFC 分别代表源接口和目标接口
    //在SourceIFC接口中只有add(a,b)方法而在目标接口中有add(a,b)和addOne(a)方法
    public interface SourceIFC {
        int add(int a, int b);
    }
    public interface TargetIFC {
        int add(int a, int b);
        int addOne(int a);
    }
    
    //TargetImpl 为适配器,实现了TargetIFC接口并且继承Sourcelmpl类,从而能重用sourceImpl类的add()
    public class SourceImpl implements SourceIFC {
        public int add(int a, int b) {return a + b};
    }
    public class TargetImpl extends SourceImpl implements TargetIFC {
        public int addOne(int a) {return add(a, 1); }   //调用父类add(a, b)方法
    }
    
  • 组合实现方式。
    //TargetImpl 为适配器,实现了TargetIFC接口并且包装了SourceIFC的实现类,从而能重用Sourcelmpl 类的add() 
    //TargetImpl类对SourceImpl类进行了包装,从而生成新的接口。采用这种实现方式的适配器模式也称为包装类模式
    public class TargeImpl implements TargeIFC {
        private SourceIFC source;
        public TargetImpl(SourceIFC source) {
            this.source = source;
        }
        public int add(int a, int b) {return source.add(a, b);}
        public int addOne(int a) {return source.add(a, 1);}
    }
    

组合关系比继承关系更有利于系统的维护和扩展,而且组合关系能够将多个源接口转换为一个目标接口。而继承关系只能把一个源接口转换为一个目标接口,因此应该优先考虑用组合关系来实现适配器。

代理模式

代理模式也可以运用到面向对象的软件开发领域,它的特征是:代理类与委托类有同样的接口。代理类主要负责为委托类预处理消息、过滤消息及把消息转发给委托类等,代理类与委托类之间为组合关系。

下面以房屋出租人的代理为例,介绍代理模式的运用。出租人 Renter 和代理 Deputy 都具有 RenterIFC 接口。Tenant 类代表租赁人,HouseMarket 类代表整个房产市场,它记录了所有房屋出租代理人的信息,出租人从房产市场上找到房屋出租代理人。

RenterIFC 接口的源程序如下,它定义了出租人的两个行为,即决定是否同意按租赁人提出的价格出租房屋,以及收房租:

public interface RenterIFC{
    //是否同意按租赁人提出的价格出租房屋
    public boolean isAgree(double expectedRent); 
    
    //收房租
    public void fetchRent(double rent); 
}

Renter类:

public class Renter implements RenterIFC { 
    //房屋租金最低价格
    private double rentDeadLine; 
    //存款
    private double money; 

    public Renter(double rentDeadLine,double money){ 
        this.rentDeadLine = rentDeadLine; 
        this.money = money; 
    } 
    
    public double getRentDeadLine(){ 
        return rentDeadLine; 
    }
    public boolean isAgree(double expected.Rent){ 
        //如果租赁者的期望价格比房屋租金最低价格多 100 元,则同意出租
        return expectedRent - this.rentDeadLine > 100; 
    }

    public void fetchRent(double rent) { 
        money += rent; 
    }
} 

Deputy类:

public class Deputy implements RenterIFC { 
    private Renter renter; 
    public void registerRenter(Renter renter) { 
        this.renter=renter; 
    }
    public boolean isAgree(double expectedRent) { 
        //如果租赁者的期望价格小千房屋租金最低价格,则不同意出租
        if(expectedRent < renter.getRentDeadLine())
            return false; 
        //否则请示租房者的意见
        return renter.isAgree(expectedRent);
    }

    public void fetchRent(double rent){ 
        renter.fetchRent(rent); 
    }
} 

HouseMarket类

import java.util.Set; 
import java.util.HashSet; 
public class HouseMarket{ 
    private static Set<RenterIFC> renters = new HashSet<RenterIFC>(); 
    public static void registerRenter(RenterIFC renter){ 
        renters.add(renter); 
    } 
    public static RenterIFC findRenter() { 
        return(RenterIFC)renters.iterator().next();
    }
} 

Tenant类:

public class Tenant { 
    private double money; 
    public Tenant(double money){
        this.money=money; 
    }
    public boolean rentHouse(double expectedRent){ 
        //从房产市场找到一个房屋出租代理
        RenterIFC renter = HouseMarket.findRent();

        //如果代理不同意预期的租金价格,就拉倒,否则继续执行
        if (!renter.isAgree(expectedRent)) { 
            System.out.println("failed");
            return false;
        }

        //从存款中取出预付租金
        money -= expectedRent; 
        
        //把租金交给房屋代理
        renter. fetchRent(expected.Rent); 
        
        System.out.println(“ 租房成功”)return true;
    }
}

在软件系统中,假如 Tenant 对象和 Renter 对象分布在不同的机器上,即运行在不同的 Java 虚拟机进程中,那么 Tenant 对象和 Renter 对象进行频繁的远程通信的效率会比较低。在这种情况下,可以在 Tenant 对象所在的机器上安置 Renter 对象的代理 Deputy对象, Tenant 对象只和 Deputy 对象通信,当 Tenant 对象提出的租金价格太低, Deputy 对象能立即给出拒绝答复,无须把消息转发到远程 Renter 对象,这可以减少远程通信的次数。

以下 AppMain 类的 main()方法演示了房屋租赁交易的运作过程。

public class AppMain { 
    public static void main(String[] args)throws Exception { 
        //创建一个房屋出租者,房屋租金最低价格 2000 元,存款 1 万元
        Renter renter = new Renter(2000, 10000);
        //创建一个房产代理人
        Deputy deputy=new Deputy(); 
        //房产代理人到房产市场登记
        HouseMarket.registerRenter(deputy);
        //建立房屋出租者和代理人的委托关系
        deputy.registerRenter(renter); 

        //创建一个房屋租赁者,存款为 2 万元
        Tenant tenant=new Tenant(20000); 
        
        //房屋租赁者试图租赁期望租金为 1800 元的房屋,遭到代理人拒绝
        tenant.rentHouse(1800); 
        //房屋租赁者试图租赁期望租金为 2000 元的房屋,租房成功
        tenant.rentHouse(2300);
    }
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值