【问题标题】:C# constructor designC#构造函数设计
【发布时间】:2018-09-27 01:17:52
【问题描述】:

我有一个类,你在一个文件夹中传递它,然后它会关闭并处理指定文件夹中的大量数据。

例如:

MyClass myClass = new MyClass(@"C:\temp");

现在它会启动并读取几千个文件并用数据填充类。

我是否应该将这些数据从构造函数中移出并将其作为单独的方法,例如以下?

MyClass myClass = new MyClass();
myClass.LoadFromDirectory(@"C:\temp");

【问题讨论】:

    标签: c# constructor


    【解决方案1】:

    也许您应该使用返回对象实例的静态方法尝试这种方式。

    var myClass = MyClass.LoadFromDirectory(@"C:\temp");
    

    这会将初始化代码保留在您的构造函数之外,并为您提供您正在寻找的“单行”声明。


    从海报下方继续评论,通过添加 State 实现可能是这样的:

    public class MyClass
    {
    
    #region Constructors 
    
        public MyClass(string directory)
        {
            this.Directory = directory;
        }
    
    #endregion
    
    #region Properties
    
        public MyClassState State {get;private set;}
    
        private string _directory;
    
        public string Directory 
        {
            get { return _directory;} 
            private set 
            {
                _directory = value; 
                if (string.IsNullOrEmpty(value)) 
                    this.State = MyClassState.Unknown; 
                else 
                    this.State = MyClassState.Initialized;
            }
        }
    
    #endregion
    
    
    
        public void LoadFromDirectory()
        {
            if (this.State != MyClassState.Initialized || this.State != MyClassState.Loaded)
                throw new InvalidStateException();
    
            // Do loading
    
            this.State = MyClassState.Loaded;
        }
    
    }
    
    public class InvalidStateException : Exception {}
    
    
    public enum MyClassState
    {
        Unknown,
        Initialized,
        Loaded
    }
    

    【讨论】:

    • 好主意,类的初始化和使用通常是完全不同的。这很好地将它们分开。为了进一步分离,您可以将初始化逻辑移动到工厂或构建器类中。
    【解决方案2】:

    这取决于。您应该评估课程的基本目的。它执行什么功能?

    我通常更喜欢让类构造函数执行类运行所需的初始化。然后我调用类上的方法,这些方法可以安全地假设必要的初始化已经完成。

    通常,初始化阶段不应过于密集。执行上述操作的另一种方法可能是:

    // Instantiate the class and get ready to load data from files.
    MyClass myClass = new MyClass(@"C:\temp");
    
    // Parse the file collection and load necessary data.
    myClass.PopulateData();
    

    【讨论】:

    • 这实际上是一个最佳实践,大多数重构工具会告诉你不要调用构造函数中的方法,除非它们是初始化方法,例如创建控件或设置属性。
    • 这导致该类具有两种非常不同的状态-一种具有文件名,另一种具有真实数据。填充后,听起来它根本不需要文件名,但它仍然需要一个变量。这样的事情总是让我紧张。我更喜欢静态方法方法,其结果是一个“准备好行动”的对象。
    • @Jon Skeet,太棒了,不是最好的地方,但你的 C# in Depth 书对我帮助很大。谢谢! - 我担心这个类在处理数据时可能需要几分钟才能创建,并且我希望避免出现双重状态。
    • 非常正确,Jon,所述的对象创建处理起来很痛苦,但是实现这种功能的另一种非常常见的方式,比如 SqlConnection 类,你仍然必须“打开”连接,即使您通过构造函数传递了 ConnectionString。
    • 再一次,这是一个有目的的声明对象,具有暴露的状态
    【解决方案3】:

    我同意 Ari 和其他人的观点 - 将他们分开。

    构造函数确实应该做最少的工作(只需初始化准备好使用的对象并保留它)。通过使用单独的方法来完成工作:

    • 调用者更清楚工作函数可能需要很长时间。
    • 很容易提供多个构造函数来初始化具有不同信息的对象(例如,您可以传入可以提供路径名的自己的类(而不是字符串)。或者您可以传入一个额外的参数指定要匹配的通配符文件名,或指定搜索是否应递归到子文件夹的标志)。
    • 您可以避免构造函数出现任何问题。在构造函数中,对象没有完全形成,所以做工作可能很危险 - 例如。在构造函数中调用虚函数是一个非常糟糕的主意。您在构造函数中放入的代码越少,您意外做出“坏事”的可能性就越小。
    • 将不同的行为/功能分成不同的方法是一种更简洁的编码风格。保持初始化和工作分开
    • 拆分类将来会更容易维护和重构。

    【讨论】:

      【解决方案4】:

      这就是你的班级所做的一切吗?如果是这样,那么我会说这并不重要。但很可能你的班级实际上比你所展示的要多。例如,它是否有任何错误处理?

      构造函数的目的是构造一个对象。方法的目的是执行一个动作。所以我的投票是这种形式:

      MyClass myClass = new MyClass();
      myClass.LoadFromDirectory(@"C:\temp");
      

      【讨论】:

      • 这门课还会有更多内容,我正在设计它。虽然类的核心要求通过传递给类的数据来填充它。
      【解决方案5】:

      我认为您应该根据您是否计划重用同一个对象以对不同的输入执行相同的操作来决定上述两种方法(“首先初始化,然后执行”与“空初始化,使用参数执行”) .
      如果该类仅用于在固定参数上运行任务,我将在构造函数中对其进行初始化(使其成为只读),然后以不同的方法执行任务。
      如果你想继续在不同的参数上执行任务,我会把它放在任务方法本身中。

      如果该类所做的只是这个任务,我也会考虑将其全部更改为静态类/方法 - 它不需要保持其内部状态。

      无论如何,我永远不会将任务本身放在构造函数中。正如 Cerebrus 所说,初始化应该很快。

      【讨论】:

        【解决方案6】:

        除非您的类的主要目的是执行 I/O,否则您可能不应该在构造函数中执行 I/O(可能引发 IOException)。

        考虑将班级一分为二:

        interface IMyDataSource
        {
           // ...
        }
        
        class FileDataSource: IMyDataSource
        {
            public FileDataSource(string path)
            {
                // ...
            }
        }
        
        class MyClass
        {
            public MyClass(IMyDataSource source)
            {
                 // ...
            }
        }
        
        IMyDataSource myDS = new FileDataSource(@"C:\temp");
        MyClass myClass = new MyClass(myDS);
        

        这样主类可以专注于管理自己的状态,而数据源则根据文件内容构建初始状态。

        【讨论】:

        • 但是现在 FileDataSource 有可能在其构造函数中抛出异常,因此只是将错误传递给单独的对象。
        • @Tom Anderson,就是这样。 FileDataSource 旨在执行 I/O,因此构造函数中的 I/O 异常是预期的。 MyClass 不是为做 I/O 而设计的,所以避免了构造函数中的 I/O 异常。
        【解决方案7】:

        如果这是该类使用的唯一资源,最好将路径传递给构造函数。否则,它将成为您班级成员的参数。

        【讨论】:

          【解决方案8】:

          我个人的偏好是使用 C# 3.0 初始化程序。

          class MyClass {
              public string directory;
              public void Load() {
                // Load files from the current directory
                }
            }
          
          MyClass myClass = new MyClass{ directory = @"C:\temp" };
          myClass.Load();
          

          这有几个优点:

          • 对象实例化不会有 自动文件系统副作用。
          • 所有参数均已命名。
          • 所有参数都是可选的(但是, 当然,可以抛出一个 Load() 中的异常(如果未定义)
          • 您可以初始化尽可能多的属性 想要在实例化调用中 无需超载 构造函数。例如,选项 是否递归目录, 或用于文件规范搜索的通配符 为。
          • 你仍然可以有一些逻辑 在要执行的目录的设置器中 一些东西,但同样,副作用 通常不是一件好事。
          • 通过在一个 单独的过程调用,你避免 不能的问题 引用您的 myClass 实例 异常处理程序。

          【讨论】:

          • 虽然仍然是一个不错的方法,但它确实有一个状态、设置或未设置的字段。
          【解决方案9】:

          我将在这里回应“将他们分开”的人们。如果有帮助,试试这个:

          1. 问问自己,“这个方法/属性/字段做什么?”
          2. 让它这样做;不多也不少。

          在这里应用它,你会得到这个:

          1. 构造函数应该创建对象。
          2. 您的方法应该从文件系统加载其数据。

          在我看来,这比“构造函数应该创建对象从文件系统加载其数据。

          【讨论】:

          • 同意。如果您想阻止并且仅在您的对象返回时返回,请使用具有静态方法的工厂。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-10-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-22
          相关资源
          最近更新 更多