• 抽象类和接口【转】


    抽象类
    在 Visual Basic 中,抽象类用 MustInherit 关键字表示。在 C# 中,则使用 abstract 修饰符.abstract 修饰符用于表示所修饰的类是不完整的,并且它只能用作基类。抽象类与非抽象类在以下方面是不同的:

    • 抽象类不能直接实例化,并且对抽象类使用 new 运算符是编译时错误。虽然一些变量和值在编译时的类型可以是抽象的,但是这样的变量和值必须或者为 null,或者含有对非抽象类的实例的引用(此非抽象类是从抽象类派生的)。
    • 允许(但不要求)抽象类包含抽象成员。
    • 抽象类不能被密封。

    当从抽象类派生非抽象类时,这些非抽象类必须具体实现所继承的所有抽象成员,从而重写那些抽象成员。在下面的示例中

    abstract class A
    {   public abstract void F();}

    abstract class B: A
    {   public void G() {}}

    class C: B
    {   public override void F() 
    {      // actual implementation of F   }}


    抽象类 A 引入抽象方法 F。类 B 引入另一个方法 G,但由于它不提供 F 的实现,B 也必须声明为抽象类。类 C 重写 F,并提供一个具体实现。由于 C 中没有了抽象成员,因此可以(但并非必须)将 C 声明为非抽象类。类被实例化的时候先执行基类的构造器再执行子类的构造器

    接口

    接口是使用 interface 关键字定义的。例如:

    interface IComparable
    {
        
    int CompareTo(object obj);
    }

    接口描述可属于任何结构的一组相关行为。接口可由方法、属性、事件、索引器或这四种成员类型的任何组合构成。接口不能包含字段。接口成员一定是公共的。

    类和结构可以按照类继承基类或结构的类似方式继承接口,但有两个例外:

    • 类或结构可继承多个接口。

    • 类或结构继承接口时,仅继承方法名称和签名,因为接口本身不包含实现。例如:

      public class Minivan : Car, IComparable
      {
          
      public int CompareTo(object obj)
          
      {
              
      //implementation of CompareTo
              return 0;  //if the Minivans are equal
          }

      }

      若要实现接口成员,类中的对应成员必须是公共的、非静态的,并且与接口成员具有相同的名称和签名。类的属性和索引器可以为接口上定义的属性或索引器定义额外的访问器。例如,接口可以声明一个带有 get 访问器的属性,而实现该接口的类可以声明同时带有 getset 访问器的同一属性。但是,如果属性或索引器使用显式实现,则访问器必须匹配。

    接口和接口成员是抽象的;接口不提供默认实现。有关更多信息,请参见抽象类、密封类和类成员

    IComparable 接口向对象的用户宣布该对象可以将自身与同一类型的其他对象进行比较,接口的用户不需要知道相关的实现方式。

    接口可以继承其他接口。类可以通过其继承的基类或接口多次继承某个接口。在这种情况下,如果将该接口声明为新类的一部分,则类只能实现该接口一次。如果没有将继承的接口声明为新类的一部分,其实现将由声明它的基类提供。基类可以使用虚拟成员实现接口成员;在这种情况下,继承接口的类可通过重写虚拟成员来更改接口行为。有关虚拟成员的更多信息,请参见多态性

    选择将功能设计为接口还是抽象类
    (在 Visual Basic 中为 MustInherit 类)有时是一件困难的事。“抽象类”是一种不能实例化而必须从中继承的类。抽象类可以完全实现,但更常见的是部分实现或者根本不实现,从而封装继承类的通用功能。有关详细信息,请参见抽象类。相反,“接口”是完全抽象的成员集合,可以被看作是为操作定义合同。接口的实现完全留给开发者去做。接口和抽象类对组件交互都很有用。如果一个方法要求一个参数形式的接口,则任何实现该接口的对象都可以用在该参数中。例如:

    public void Spin (IWidget widget)
    {}

    此方法可以接受任何将 IWidget 实现为小部件参数的对象,即使 IWidget 的实现可能相差很大。抽象类也允许这种多态性,但须注意以下几点:

    • 类可能只是从一个基类继承,所以如果要使用抽象类为一组类提供多态性,这些类必须都是从那个类继承的。
    • 抽象类还可能提供已实现的成员。因此,可以用抽象类确保特定数量的相同功能,但不能用接口这样做。

    这里是一些建议,帮助您决定使用接口还是抽象类为组件提供多态性。

    • 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。
    • 如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
    • 如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。
    • 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。

     Java实例
    假设要为销售部门设计一个软件,这个软件包含一个“发动机”(Motor)实体。显然无法在发动机对象中详细地描述发动机的方方面面,只能描述某些对当前软件来说重要的特征。至于发动机的哪些特征是重要的,则要与用户(销售部门)交流才能确定。

      销售部门的人要求每一个发动机都有一个称为马力的参数。对于他们来说,这是惟一值得关心的参数。基于这一判断,可以把发动机的行为定义为以下行为。

      行为1:查询发动机的马力,发动机将返回一个表示马力的整数。

      虽然现在还不清楚发动机如何取得马力这个参数,但可以肯定发动机一定支持这个行为,而且这是所有发动机惟一值得关注的行为特征。这个行为特征既可以用接口定义,也可以用抽象类定义。为了说明用抽象类定义可能出现的问题,下面用抽象类建立发动机的行为模型,并用Java方法描述行为1,代码如下:

    public abstract Motor{
                
    abstract public int getHorsepower();
                }

    在Motor抽象类的基础上构造出多种具体实现,例如A型发动机、B型发动机等,再加上系统的其它部分,最后得到1.0版的软件并交付使用。一段时间过去了,现在要设计2.0版的软件。在评估2.0版软件需求的过程中,发现一小部分发动机是电池驱动的,而电池需要一定的充电时间。销售部门的人希望能够通过计算机查阅充电时间。根据这一要求定义一个新的行为,如图1所示。

      行为2:查询电驱动发动机的充电时间,发动机将返回一个表示充电时间的整数。

      用Java方法来描述这个行为,代码如下:

    public abstract BatteryPoweredMotor extends Motor{
                
    abstract public int getTimeToRecharge();
                }

      在销售部门的软件中,电驱动发动机也以类的形式实现,但这些类从BatteryPoweredMotor而不是Motor派生。这些改动加入到2.0版软件之后,销售部门很满意。随着业务的不断发展,不久之后光驱动的发动机出现了。销售部门要求光驱动发动机需要一定光能才能运转,光能以流明(Lumen)度量。这个信息对客户很重要,因为下雨或多云的天气里,某些光驱动发动机可能无法运转。销售部门要求为软件增加对光驱动发动机的支持,所以要定义一个新的行为。

      行为3:查询光驱动发动机能够正常运转所需要的最小流明数,发动机返回一个整数。

      再定义一个抽象类并把行为3转换成Java方法,代码如下:

    public abstract SolarPoweredMotor extends Motor{
                
    abstract public int getLumensToOperate();
                }

      如图1所示,SolarPoweredMotor和BatteryPoweredMotor都从Motor抽象类派生。在整个软件中,90%以上的代码以相同的方式对待所有的发动机。偶尔需要检查一下发动机是光驱动还是电驱动,使用instanceof实现,代码如下:

    if (instanceof SolarPoweredMotor){}
                
    if (instanceof BatteryPoweredMotor){}

    无论是哪种发动机,马力这个参数都很重要,所以在所有派生的抽象类(SolarPoweredMotor和BatteryPoweredMotor)中,getHorsepower()方法都有效。

      现在销售部门又有了一种新的发动机,它是一种既有电驱动又有光驱动的双重驱动发动机。光驱动和电驱动的行为本身没有变化,但新的发动机同时支持两种行为。在考虑如何定义新型的光电驱动发动机时,接口和抽象类的差别开始显示出来了。新的目标是在增加新型发动机的前提下尽量少改动代码。因为与光驱动发动机、电驱动发动机有关的代码已经过全面的测试,不存在已知的Bug。为了增加光电驱动发动机,要定义一个新的SolarBatteryPowered抽象类。如果让SolarBatteryPowered从Motor抽象类派生,SolarBatteryPowered将不支持针对光驱动发动机和电驱动发动机的instanceof操作。也就是说,如果查询一个光电驱动的发动机是光驱动的,还是电驱动的,得到的答案是:都不是。

      如果让SolarBatteryPowered从SolarPoweredMotor(或BatteryPoweredMotor)抽象类派生,类似的问题也会出现,SolarBatteryPowered将不支持针对BatteryPoweredMotor(或SolarPoweredMotor)的instanceof操作。从行为上看,光电驱动的发动机必须同时从两个抽象类派生,但Java语言不允许多重继承。之所以会出现这个问题,根本的原因在于使用抽象类不仅意味着定义特定的行为,而且意味着定义实现的模式。也就是说,应该定义一个发动机如何获得行为的模型,而不仅仅是声明发动机具有某一个行为。

    通过接口建立行为模型.如果用接口来建立行为模型,就可以避免隐含地规定实现模式。例如,前面的几个行为改用接口定义如下。
    行为1:

    public interface Motor(){
                
    public int getHorsepower();
                }

    行为2:

    public interface BatteryPoweredMotor extends Motor(){
                
    public int getTimeToRecharge();
                }

    行为3:

    public interface SolarPoweredMotor extends Motor{
                
    abstract public int getLumensToOperate();
                }

    现在光电驱动的发动机可以描述为:

    public DualPoweredMotor implements SolarPoweredMotor, BatteryPoweredMotor{}

     
    DualPoweredMotor只继承行为定义,而不是行为的实现模式,如图2所示。

      在使用接口的同时仍旧可以使用抽象类,不过这时抽象类的作用是实现行为,而不是定义行为。只要实现行为的类遵从接口定义,即使它改变了父抽象类,也不用改变其它代码与之交互的方式。特别是对于公用的实现代码,抽象类有它的优点。抽象类能够保证实现的层次关系,避免代码重复。然而,即使在使用抽象类的场合,也不要忽视通过接口定义行为模型的原则。从实践的角度来看,如果依赖于抽象类来定义行为,往往导致过于复杂的继承关系,而通过接口定义行为能够更有效地分离行为与实现,为代码的维护和修改带来方便。

    抽象类和接口的优缺点
    1.一个子类如果implements一个接口,就必须实现接口中的所有方法(不管是否需要);如果是继承一个抽象类,只需要实现需要的方法即可,这是抽象类的一个优点
    2. 如果一个接口中定义的方法名改变了,那么所有实现此接口的子类显然将无法通过编译,因为它们所实现的方法名已经不存在了,这是接口的一个缺点;而抽象类就不存在这个问题,只是为子类添加了一个新的方法(接口中旧的方法)
    3.抽象类的缺点:就是一个子类只能有一个父类。A extends B . 这样A就拥有了B的所有方法和功能,但当A还想拥有C的功能的时候。就不能通过 A extends C 来实现,   而需要走一些弯路。目前系统架构的趋势就是由针对抽象(借口,抽象类)而不是具体编程,并且将功能尽可能的细分。   这就需要通过实现多个接口的方式来实现,显然,抽象类无法提供这样的功能。从系统重构的角度来说,一个具体类抽象出接口是十分方便的。只需要写一个接口,里面定义具体类的所有方法,然后在为这个具体类implement这个接口就可以了。而抽象类就要复杂的多,比如说 B extends A , C extends B 如果想要为c抽象出一个抽象类D的话,就需要找到它的最顶层A来从头做起,因为无法做到C extends D

    引用:http://www.cnblogs.com/happlyonline/archive/2007/07/12/814412.html

  • 相关阅读:
    自动化设计自动化测试介绍
    自动化设计框架介绍 TestReport
    自动化设计自动化测试环境搭建<二>
    自动化设计自动化测试环境搭建<三>
    浅谈敏捷模型
    自动化设计框架介绍 TestLog
    自动化设计框架介绍 TestScript
    自动化设计框架介绍
    LoadRunner脚本录制常见问题整理<转>
    自动化设计框架介绍 TestSnap
  • 原文地址:https://www.cnblogs.com/cxd4321/p/910454.html
Copyright © 2020-2023  润新知