【问题标题】:Constructor with unknown argument具有未知参数的构造函数
【发布时间】:2015-09-30 17:07:43
【问题描述】:

PHP 度过了我一生的大部分时间之后,我最近开始在C# 做一些编程。

PHP 我可以这样做:

class User
{
    public __construct($UserId)
    {
        // Do stuff
    }
}

class Employee extends User
{
    public __construct($EmployeeId)
    {
        // Look up the UserId connected to $EmployeeId
        $UserId = hypothetical_get_user_id_func($EmployeeId);
        parent::__construct($UserId);
    }
}

但是,在C# 中我似乎没有这种可能性,因为似乎我必须在$UserId 进入第一个构造函数之前知道它。

public class User
{
    public User(int UserId)
    {
        // Do stuff
    }
}

public class Employee : User
{
    public Employee(int EmployeeId) : base(***) // I don't know this value yet?
    {
        // This is where I would find the User Id, and would like to pass
        // it to the User class constructor.
    }
}

有什么方法可以实现我在 C# 中尝试做的事情吗?

基本上将一个值传递给主对象的构造函数,它在自己的主体中计算出要传递给基类的值。

【问题讨论】:

  • 您必须将 userId 作为另一个参数。
  • 那是不可能的。但是你可以调用一个静态方法并传递它的结果。
  • 正如 SLaks 所说,这是不可能的。但有充分的理由。您的构造函数不应包含业务逻辑。您所说的听起来更像是 DAL/Factory。在 C# 中,我们鼓励 SOLID 设计原则,而您正在尝试做的事情打破了 SOLID 的关注点分离部分。

标签: c# class oop inheritance constructor


【解决方案1】:

您可以在构造函数中调用静态方法。虽然很不标准。

public class User
{
    public User(int userId)
    {
        // Do stuff
    }
}

public class Employee : User
{
    public Employee(int employeeId) : base(GetUserId(employeeId))
    {

    }

    public static int GetUserId(int employeeId)
    {           
        return employeeId - 5;
    }
}

【讨论】:

  • 抱歉 Loofer,我认为您不了解 PHP 程序员的心理。不幸的是...>_
【解决方案2】:

关注点分离

其他答案未能理解的是 PHP 和 C# 具有非常不同的编码标准。

PHP 中的一个常见模式是

class User
{
    public __construct($UserId)
    {
        // Do stuff
    }
}

class Employee extends User
{
    public __construct($EmployeeId)
    {
        // Look up the UserId connected to $EmployeeId
        $UserId = get_userid_from_database($EmployeeId);
        parent::__construct($UserId);
    }
}

但这绝对不会被认为是 C# 中的好设计。

在 .net 中实现这一点的典型方法更接近于

public class User
{
    public User(int userId)
    {
        // Do stuff
    }
}

public class Employee : User
{
    public Employee(int employeeId, int userId) : base(userId) // I don't know this value yet?
    {
        // This is where I would find the User Id, and would like to pass
        // it to the User class constructor.
    }
}

public class EmployeeRepository
{
    public Employee GetEmployee(int employeeId)
    {
        using(var connection = new SqlConnection(..)
        {
            //blah blah blah
            return new Employee(employeeId, userId);
        }
    }
}

关键是 Employee 可以在没有数据库的情况下使用,例如在单元测试中。

【讨论】:

  • 我习惯做的是将Id(我的db表中的主键)传递给对象构造函数,然后在构造函数中从db中获取数据。我觉得这是我可以确定对象完整性和有效性的唯一方法。是收集,这可能不是推荐的做法。您对解释如何做到这一点的文章有什么建议吗?
  • @Ishas 最佳做法是不要这样做。您正在将您的课程与数据库联系起来。最佳实践是公开您的属性并拥有一个不同的 类,该类可以从数据库中的数据创建对象。否则,如果不从数据库中读取数据,就无法测试您的课程。
  • @lshas 你想阅读任何关于 Entity Framework 6 的教程。关键是每个类都应该有尽可能少的代码和尽可能少的公共方法。通过将Employee 绑定到数据库,您不能在没有数据库的情况下使用Employee 对象。这意味着每次测试代码时都需要实例化(并初始化)数据库。
【解决方案3】:

您可以重构为共享函数:

public class User
{

    protected User() {}

    public User(int userId)
    {
        Init(userId);
    }

    protected void Init(int userId)
    {
        // Do stuff
    }

}

public class Employee : User
{
    public Employee(int employeeId)  
    {
        // find user Id here

        Init(userId);
    }
}

请注意,我将参数名称的大小写更改为更标准的小写字母。

我习惯做的是将Id(我的db表中的主键)传递给对象构造函数,然后在构造函数中从db中获取数据。

不要那样做。类不必依赖外部数据源来创建它们自己的实例。创建一个单独的类(存储库、工厂等),从数据库中提取数据并填充空白对象的属性。

将数据库逻辑放在类的构造函数中会阻止独立于数据源的测试。

【讨论】:

  • 这不会编译,因为User 没有无参数构造函数,Init 没有定义返回类型。
【解决方案4】:

使用静态方法来实例化你的类怎么样?

public class User
{
    public User(int UserId)
    {
        // Do stuff
    }
}

public class Employee : User
{
    private Employee(int userID) : base(userID) { }

    public static Employee GetEmployee(int employeeID)
    {
        var userID = GetUserIDFromEmployeeID(employeeID);

        return new Employee(userID);
    }
}

然后在你的调用代码中你会:

var employee = Employee.GetEmployee(someValue);

【讨论】:

    【解决方案5】:

    某些语言允许您从派生构造函数中显式调用基构造函数。

    C# 不是这样的语言。

    在这种情况下,由于 EmployeeId 与 UserId 不同,并且是在派生类构造函数中计算的,因此您必须从 Employee 构造函数中设置它,而不是将其作为参数传递给基类构造函数。

    UserId 的基类中的后备存储需要设置为受保护,或者您需要提供受保护的属性或方法来设置它。

    【讨论】:

      【解决方案6】:

      你想做的事情是不可能的(而且可能不应该——谢谢,PHP),但这样的事情就足够了:

      public class User
      {
          protected User() {
              // Do things here
          }
      
          public User(int UserId)
          {
              Init(UserId);
          }
      
          protected void Init(int UserId) {
              // Do stuff
          }
      }
      
      public class Employee : User
      {
          public Employee(int EmployeeId) : base()
          {
              int UserId;
              // Find UserId
      
              Init(UserId);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-08-02
        • 2017-09-27
        • 1970-01-01
        • 1970-01-01
        • 2020-07-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多