接口和抽象类的使用场景以及多类继承存在的问题(c#)

  我们首先来看下抽象class能发挥优势的使用场景。  

  假设有一个Cars基类,具体型号的Car继承该基类,并实现自己独有的属性或方法。

public class Cars  
    {    
        public string Wheel()  
        {  
            return "I have 4 wheeler";  
        }  
    }  

  有两种具体型号的汽车CarA和CarB均继承自Cars基类。也即它们拥有Cars基类的属性和方法。现在有一个需求,即需要添加一些对CarA和CarB类通用(commen)的但各自的实现不同的方法,比如colors方法,我们如何做到这一点呢?下面罗列了可能想到的解决方法。

  1. 直接在Cars基类中添加方法,让CarA和CarB继承
  2. 新创建一个interface,让CarA和CarB继承Cars类和该interface
  3. 新创建一个抽象class,让CarA和CarB仅继承该抽象class

  下面论证每种方案的可行性。

  1. 对于方案1而言,由于普通的Cars基类中仅能添加有共同实现(common implementation)的普通(normal)方法,故不能满足需求。
  2. 方案2可行,但需要子类继承interface和Cars基类,具体实现如下:
interface IExtra  
   {   
         string colors();  
   }  

  3. 方案3可行,将具有共同实现的方法定义为普通方法,将通用但各自实现不同的方法定义为抽象方法,这两种方法均封装在抽象class中,子类只需继承该抽象class。另外,该抽象基类中还可以添加一些公共fields,这是interface所不能实现的。

public abstract class Cars
    {
        //将公有但实现不同的功能声明为抽象方法
        public abstract string colors();

        //将公有且实现相同的功能声明为普通方法
        public string Wheel()
        {
            return "4 wheeler";
        }
    }

   从以上代码实现可以看到,使用抽象class能封装字段,公有且实现相同的方法和公有但实现不同的方法(抽象方法),其作用相当于一个普通class和interface。故当我们需要实现一个包含公有且实现相同的方法和公有但实现不同的方法的基类(或许还可能包含字段)时,应该首先考虑创建抽象class,然后才是interface(需要和一个普通class配合来达到和抽象class相同的效果)。

  现在,我们来看下抽象class的第二种用途。

  还是拿上面的Cars基类举例,我们让CarA类继承Cars基类,并添加自己的DataRecorder方法。本来用户能通过创建CarA类的实例对象来访问CarA类和Cars基类的属性和方法,但这里的主要问题是允许创建Cars基类的实例对象,但基类对象不能够访问子类独有的属性和方法,这并不满足我们的要求。为了限制这一点,我们应禁止在子类中创建基类的对象,仅允许创建子类对象来同时访问子类和基类的属性和方法。

  接下来我们讨论接口interface的使用场景,在讨论之前,我们先看一个OOP中存在的多继承(Multiple Inheritance)问题。

  在C++中存在多继承(Multiple Inheritance)的概念,但是多继承存在一个严重的问题,即Diamond Problem。

public class print1
    {
        public void print()
        {
            Console.WriteLine("hello");
        }
    }

    public class print2
    {
        public void print()
        {
            Console.WriteLine("world");
        }
    }
    class Program : print1, print2
    {
        static void Main(string[] args)
        {
            
        }
    }

  以上就是多继承(Multiple Inheritance)的例子,该例子不能通过编译,因为在class Program中,如果调用继承的print方法,系统将不能确定到底调用从class print1还是从class print2继承来的print方法,从而引发程序错误。通过使用interface可以解决这个问题。

public interface print1
    {
        void print();
    }
    public interface print2
    {
        void print();
    }
    class Program : print1, print2
    {
        void print1.print()
        {
            Console.WriteLine("hello");
        }
        void print2.print()
        {
            Console.WriteLine("world");
        }
        static void Main(string[] args)
        {
            Program p = new Program();
            ((print2)p).print();
            Console.ReadLine();
        }
    }

   现在,我们看看interface如何在实际开发场景中使用interface。还是拿上面的Cars类例子来讲,现在CarA要添加行车记录仪新功能DataRecorder,但CarB不具备该功能。如何实现这一需求?

  对于这一新需求,我们通常能想到以下4种解决方案:

  1. 创建一个新的普通class,该class定义来DataRecorder方法,然后让CarA类继承该新class
  2. 直接在Cars类中添加DataRecorder方法,并将该方法声明为abstract方法
  3. 创建一个抽象class,该抽象class定义一个抽象DataRecorder方法,然后让CarA类继承该抽象class并实现DataRecorder方法
  4. 直接在CarA类中创建DataRecorder方法并使用它
  5. 利用interface实现

  下面依次验证每个解决方案的可行性。

  1. 对于方案1来讲,由于CarA已经继承了Cars基类,此时不能同时继承新创建的普通class,故此方案不可行。
  2. 对于方案2来讲,当CarB继承Cars类时也必须override该DataRecorder方法,这违背了我们的需求。
  3. 方案3和方案1情形相同。
  4. 方案4似乎是能想到的最直接的方式,简单粗暴。这个方案在当添加的方法较少且我们能保证记住每个要添加的方法情形下工作良好,但是当添加的方法较多时,若我们忘记添加其中一两个方法时,没有一种机制通知我们这种疏忽。若没有语法错误,程序就能编译通过,但执行时不能得到期望输出。
  5. 方案5能克服上述方案的缺点,可行,代码实现见下面:
public class Cars  
    {    
        public string Wheel()  
        {  
            return "I have 4 wheeler";  
        }  
    }  
interface IDataRecorder  
 {  
    void DataRecorder();      
 }  
interface IDataRecorder  
 {  
    void DataRecorder();      
 }  
public class CarA:Cars, IDataRecorder  
    {  
        public void DataRecorder()  
        {  
            Console.WriteLine("DataRecorder supported.");  
        }  

        static void Main(string[] args)  
        {  
            CarA car = new CarA();  
  
            Console.WriteLine(car.Wheel());  
            car. DataRecorder();  
            Console.ReadLine();  
        }  
    }  

   以上方案5代码实现克服了方案1和3多继承(Multiple Inheritance)报错问题,同时克服了方案2中CarB类必须override DataRecorder方法的缺陷,并且在后期维护添加多个方法时由于疏忽导致忘记添加某些方法时,能通过编译错误提醒开发者,从而克服了方案4的问题。

猜你喜欢

转载自www.cnblogs.com/lian--ying/p/9147750.html