【问题标题】:why to use abstract class as type? [duplicate]为什么要使用抽象类作为类型? [复制]
【发布时间】:2017-08-15 15:02:07
【问题描述】:

我对接口/抽象类/类有相当的理解,但只是试图理解其他东西。看下面的代码:

namespace AbstractClassExample
{
    class Program
    {
        static void Main(string[] args)
        {
            BaseEmployee fullTimeEmployee = new FullTimeEmployee();

            BaseEmployee contractEmployee = new ContractEmployee();
        }
    }

    public abstract class BaseEmployee
    {
        public string EmployeeID { get; set; }
        public string EmployeeName { get; set; }
        public string EmployeeAddress { get; set; }

        public abstract double CalculateSalary(int hoursWorked);
    }

    public class FullTimeEmployee : BaseEmployee
    {
        public override double CalculateSalary(int hoursWorked)
        {
            //do something
        }
    }

    public class ContractEmployee : BaseEmployee
    {
        public override double CalculateSalary(int hoursWorked)
        {
            //do something
        }
    }
}

但是我没能做到以下几点(第一种方法):

BaseEmployee fullTimeEmployee = new FullTimeEmployee();
BaseEmployee contractEmployee = new ContractEmployee();

为什么不这样写(第二种方法):

FullTimeEmployee fullTimeEmployee = new FullTimeEmployee();

完全可以使用第二种方法,因为它会起作用。工作中的任何开发人员如何知道上述抽象类是否在 DLL 中。当您与您一起编写代码或某种文档时,可能会使用第一种方法。不是吗?

类似的例子也适用于接口声明。喜欢:

interface IPointy {
    void MyMethod();
}

class Pencil : IPointy {
    void MyMethod() {
    }

    void MyOtherMethod() {
    }
}

IPointy itPt = new Pencil();

第一种方法不是让它变得复杂吗?有什么好的做法?第一和第二的任何好的做法和坏的做法?

【问题讨论】:

标签: c# interface abstract-class


【解决方案1】:

抽象类的好处之一是 - 您可以在方法中使用抽象类型作为参数类型。

假设您有一个 Report 类,其方法为 Generate,在生成报告期间需要计算员工工资

public class Report
{
    public SalaryReport Generate(BaseEmployee employee)
    {
        // ...
        var salary = employee.CalculateSalary();
        // ...
    }
}

您不希望在需要计算工资的每个方法中都使用if..else 语句。
所以在Report类中并不关心CalculateSalary是如何实现的,它只关心Employee类有这个方法。

【讨论】:

    【解决方案2】:

    您将FullTimeEmployee 分配给BaseEmployee 的原因之一是您可以将它们放在FullTimeEmployees' andContractEmployees 的集合中:

    List<BaseEmployee> allEmployees = new List<BaseEmployee>()
    {
        new FullTimeEmployee() {...},
        new FullTimeEmployee() {...},
        new ContractEmployee() {...},
        new FullTimeEmployee() {...},
    }
    

    这样做的缺点是您不能(有效)使用ContractEmployees 没有的FullTimeEmployee 功能,但如果您在处理allEmployees 时不需要此功能,则此方法更可取上面创建了两个员工集合。例如,您可以编写一个适用于FullTimeEmployeesContractEmployees 的函数:

    private void PaySalary(List<BaseEmployee> employees)
    {
        foreach (var employee in employees)
        {
            var salary = employee.CalculateSalary()
            Pay(salary, ...);
        }
    }
    

    创建面向对象设计时的指导方针之一是您应该为变化而设计,这意味着您的设计应该使您可以轻松地将类型添加到您的设计中,或者更改您的类的内部结构。

    假设您需要一种新型员工HiredEmployees。因为您是从BaseEmployee 派生出来的,所以您会知道您可以计算他们的薪水。您不必更改函数PaySalary

    如果你给你的FullTimeEmployee 和你的ContractEmployee 一个接口,这也会起作用:

    interface ISalaryReceiver
    {
        double CalculateSalary(int hoursWorked);
    }
    
    class BaseEmployee
    {
        public string EmployeeID { get; set; }
        ...
    }
    
    class FullTimeEmployee : BaseEmployee, ISalaryReceiver
    {
        public override double CalculateSalary(int hoursWorked)
        {
            ...
        }
    }
    
    class ContractEmployee : BaseEmployee, ISalaryReceiver
    {
        public double CalculateSalary(int hoursWorked)
        {
            ...
        }
    }
    
    void PaySalary(List<ISalaryReceiver> employees)
    {
        ...
    }
    

    这种方法可行。它甚至已经准备好改变:你可以发明任何员工,只要它实现了ISalaryReceiver

    但是!

    假设您的 BaseEmploye 有一个需要计算工资的函数:

    class BaseEmployee
    {
        ...
        public void PaySalary()
        {
           double salary = ... // how to calculate the salary?
        }
    }
    

    你不能让BaseEmployee实现ISalaryReceiver,因为BaseEmployee不知道如何计算工资。

    使用抽象方法时,可以告诉BaseEmployeeBaseEmployee 的每个对象都知道CalculateSalary

    abstract class BaseEmployee
    {
        abstract double CalculateSalary(...);
        public void PaySalary()
        {
           double salary = this.CalculateSalary(...);
           ...
        }
    }
    

    因此,如果您的基类需要每个派生类不同的函数,并且没有适当的默认功能,那么您的基类需要一个抽象函数。保证每个派生类都实现了这个函数,因此基类可以调用它。

    因为在这种情况下创建基类的对象没有意义(毕竟基类不知道如何CalculateSalary),因此必须将基类声明为抽象的结果,你可以'不创建基类的对象。只能创建实现CalculateSalary 的派生类的对象。

    【讨论】:

      【解决方案3】:

      使用第一种方法启用多态

      假设你有一个公司类:

      class Company {
      
      }
      

      当然,公司有全职和合同工。让我们将它们添加为属性:

      public FullTimeEmployee[] EmployeesFullTime { get; set; }
      public ContractEmployees[] EmployeesContract { get; set; }
      

      这似乎都很好。

      但是,如果您的公司现在可以拥有另一种计算工资方式不同的员工怎么办?您必须添加另一个属性:

      public FullTimeEmployee[] EmployeesFullTime { get; set; }
      public ContractEmployee[] EmployeesContract { get; set; }
      public AnotherKindOfEmployee[] EmployeesOther { get; set; }
      

      这样不好,是吗?每次添加新员工时,都必须添加另一个属性!

      这就是为什么你使用BaseEmployee,它不关心它拥有什么样的员工,它仍然可以计算工资!

      public BaseEmployee[] AllEmployees { get; set; }
      

      【讨论】:

      • 我曾想过添加一个类似的答案(已经写了一半),但后来意识到这只是关于 基本类型 和继承。如果这就是 OP 所要问的,那好吧,但是为什么在 abstract 类型的问题中如此关注?
      • @Damien_The_Unbeliever 抽象类型只是阻止您创建它的实例。永远不可能有BaseEmployee 的实例,因为每个员工的工资计算方式都不一样。
      • 我知道 - 我的观点是,问题的焦点似乎是抽象类型,而不仅仅是简单的继承。即使基本类型不是抽象的,您的答案也同样适用,所以我很难知道 它是否在处理 OP 试图询问的内容
      • 对于不同类型的员工可能适用不同的费率,因此使用不同的等级。顺便说一句,上面的例子只是我作为参考来理解为什么抽象类用作类型?我只是从某个地方复制的
      • @Sweeper 是的,我明白你不能初始化抽象类型。我们不要拘泥于员工/薪水的例子。问题是关于任何最佳实践。当我可以使用 FullTimeEmployee fullTimeEmployee = new FullTimeEmployee();为什么它使用 BaseEmployee fullTimeEmployee = new FullTimeEmployee();?
      猜你喜欢
      • 1970-01-01
      • 2011-01-23
      • 1970-01-01
      • 1970-01-01
      • 2014-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-04
      相关资源
      最近更新 更多