Разработка конструктора
Конструкторы — это особые методы, используемые для инициализации типов и создания экземпляров типов. Конструктор типа используется для инициализации статических данных в типе. Конструктор типа вызывается средой CLR перед созданием экземпляров типа. Конструкторы типа являются static (Shared в Visual Basic) и не могут принимать параметры. Конструктор экземпляра используется для создания новых экземпляров типа. Конструкторы экземпляров могут принимать параметры, но это не является обязательным. Конструктор экземпляра без параметров называется конструктором по умолчанию.
В приведенных ниже рекомендациях излагаются оптимальные методы создания конструкторов.
Рекомендуется предоставлять простые конструкторы, в идеальном случае конструкторы по умолчанию. Простой конструктор имеет очень малое количество параметров, и все параметры являются примитивными типами или перечислениями.
Рекомендуется использовать статический заводской метод вместо конструктора, если семантика требуемой операции не отображается непосредственно на конструкцию нового экземпляра или рекомендации по разработке конструктора кажутся неприменимыми.
Используйте параметры конструкторов как ярлыки для установки значений основных свойств.
Установка значений свойств путем использования конструктора должна быть идентичной непосредственной установке значений свойств. В следующем примере кода показан класс EmployeeRecord, который может быть инициализирован либо путем вызова конструктора, либо посредством непосредственной установки значений свойств. Класс EmployeeManagerConstructor является примером инициализации объекта EmployeeRecord с использованием конструктора. Класс EmployeeManagerProperties является примером инициализации объекта EmployeeRecord с использованием свойств. Класс Tester является примером того, что независимо от используемого способа объекты имеют одинаковое состояние.
Imports System
Imports System.Collections.ObjectModel
namespace Examples.DesignGuidelines.Constructors
' This Class can get its data either by setting
' properties or by passing the data to its constructor.
Public Class EmployeeRecord
private employeeIdValue as Integer
private departmentValue as Integer
Public Sub New()
End Sub
Public Sub New(id as Integer, department as Integer)
Me.employeeIdValue = id
Me.departmentValue = department
End Sub
Public Property Department as Integer
Get
Return departmentValue
End Get
Set
departmentValue = value
End Set
End Property
Public Property EmployeeId as Integer
Get
Return employeeIdValue
End Get
Set
employeeIdValue = value
End Set
End Property
Public Sub DisplayData()
Console.WriteLine("{0} {1}", EmployeeId, Department)
End Sub
End Class
' This Class creates Employee records by passing
' argumemnts to the constructor.
Public Class EmployeeManagerConstructor
Dim employees as Collection(Of EmployeeRecord) = _
new Collection(Of EmployeeRecord)()
Public Sub AddEmployee(employeeId as Integer, department as Integer)
Dim record as EmployeeRecord = new EmployeeRecord(employeeId, department)
employees.Add(record)
record.DisplayData()
End Sub
End Class
' This Class creates Employee records by setting properties.
Public Class EmployeeManagerProperties
Dim employees as Collection(Of EmployeeRecord)= _
new Collection(Of EmployeeRecord)()
Public Sub AddEmployee(employeeId as Integer, department as Integer)
Dim record as EmployeeRecord = new EmployeeRecord()
record.EmployeeId = employeeId
record.Department = department
employees.Add(record)
record.DisplayData()
End Sub
End Class
Public Class Tester
' The following method creates objects with the same state
' using the two different approaches.
Public Shared Sub Main()
Dim byConstructor as EmployeeManagerConstructor = _
new EmployeeManagerConstructor()
byConstructor.AddEmployee(102, 102)
Dim byProperties as EmployeeManagerProperties = _
new EmployeeManagerProperties()
byProperties.AddEmployee(102, 102)
End Sub
End Class
End Namespace
using System;
using System.Collections.ObjectModel;
namespace Examples.DesignGuidelines.Constructors
{
// This class can get its data either by setting
// properties or by passing the data to its constructor.
public class EmployeeRecord
{
private int employeeId;
private int department;
public EmployeeRecord()
{
}
public EmployeeRecord(int id, int department)
{
this.employeeId = id;
this.department = department;
}
public int Department
{
get {return department;}
set {department = value;}
}
public int EmployeeId
{
get {return employeeId;}
set {employeeId = value;}
}
public void DisplayData()
{
Console.WriteLine("{0} {1}", EmployeeId, Department);
}
}
// This class creates Employee records by passing
// argumemnts to the constructor.
public class EmployeeManagerConstructor
{
Collection<EmployeeRecord > employees = new Collection<EmployeeRecord>();
public void AddEmployee(int employeeId, int department)
{
EmployeeRecord record = new EmployeeRecord(employeeId, department);
employees.Add(record);
record.DisplayData();
}
}
// This class creates Employee records by setting properties.
public class EmployeeManagerProperties
{
Collection<EmployeeRecord > employees = new Collection<EmployeeRecord>();
public void AddEmployee(int employeeId, int department)
{
EmployeeRecord record = new EmployeeRecord();
record.EmployeeId = employeeId;
record.Department = department;
employees.Add(record);
record.DisplayData();
}
}
public class Tester
{
// The following method creates objects with the same state
// using the two different approaches.
public static void Main()
{
EmployeeManagerConstructor byConstructor =
new EmployeeManagerConstructor();
byConstructor.AddEmployee(102, 102);
EmployeeManagerProperties byProperties =
new EmployeeManagerProperties();
byProperties.AddEmployee(102, 102);
}
}
}
using namespace System;
using namespace System::Collections::ObjectModel;
namespace Examples { namespace DesignGuidelines { namespace Constructors
{
// This class can get its data either by setting
// properties or by passing the data to its constructor.
public ref class EmployeeRecord
{
private:
int employeeId;
int department;
public:
EmployeeRecord()
{
}
EmployeeRecord(int id, int department)
{
this->employeeId = id;
this->department = department;
}
property int Department
{
int get() {return department;}
void set(int value) {department = value;}
}
property int EmployeeId
{
int get() {return employeeId;}
void set(int value) {employeeId = value;}
}
void DisplayData()
{
Console::WriteLine("{0} {1}", EmployeeId, Department);
}
};
// This class creates Employee records by passing
// argumemnts to the constructor.
public ref class EmployeeManagerConstructor
{
private:
Collection<EmployeeRecord^>^ employees;
public:
EmployeeManagerConstructor()
{
employees = gcnew Collection<EmployeeRecord^>();
}
void AddEmployee(int employeeId, int department)
{
EmployeeRecord^ record = gcnew EmployeeRecord(employeeId, department);
employees->Add(record);
record->DisplayData();
}
};
// This class creates Employee records by setting properties.
public ref class EmployeeManagerProperties
{
private:
Collection<EmployeeRecord^>^ employees;
public:
EmployeeManagerProperties()
{
employees = gcnew Collection<EmployeeRecord^>();
}
void AddEmployee(int employeeId, int department)
{
EmployeeRecord^ record = gcnew EmployeeRecord();
record->EmployeeId = employeeId;
record->Department = department;
employees->Add(record);
record->DisplayData();
}
};
public ref class Tester
{
// The following method creates objects with the same state
// using the two different approaches.
public:
static void Main()
{
EmployeeManagerConstructor^ byConstructor =
gcnew EmployeeManagerConstructor();
byConstructor->AddEmployee(102, 102);
EmployeeManagerProperties^ byProperties =
gcnew EmployeeManagerProperties();
byProperties->AddEmployee(102, 102);
}
};
}}}
Обратите внимание, что в этих примерах, как и в любой хорошо разработанной библиотеке, оба способа приводят к созданию объектов с одинаковым состоянием. Неважно, какой способ предпочитает использовать разработчик.
Если параметры конструктора используются только для установки значений свойства, используйте одинаковые имена для параметров конструктора и свойства. Единственное, чем должны различаться имена параметров и свойств, это использование прописных и строчных букв.
Эта рекомендация проиллюстрирована в предыдущем примере.
Предусмотрите для конструктора как можно меньше работы. Конструктор не должен делать ничего, кроме получения параметров. Всю остальную обработку следует вводить только по мере необходимости.
Вызывайте исключения из конструкторов экземпляров (если это возможно).
Конструкторы должны вызывать и обрабатывать исключения, как любой другой метод. В частности, конструктор не должен перехватывать и скрывать исключения, которые он не может обработать. Дополнительные сведения об исключениях см. в разделе Правила разработки исключений.
Если требуется открытый конструктор по умолчанию, объявляйте его явно в классах.
Наилучшей рекомендацией будет явное определение конструктора по умолчанию, если класс его поддерживает. Несмотря на то что некоторые компиляторы автоматически добавляют к классу конструктор по умолчанию, явное его добавление упрощает поддержку кода. Это гарантирует то, что конструктор по умолчанию останется определенным, даже если компилятор не будет его создавать из-за добавления конструктора, принимающего параметры.
Избегайте предоставления конструкторов по умолчанию для структур.
Многие компиляторы, в том числе и компилятор C#, не поддерживают конструкторы без параметров для структур.
Не вызывайте виртуальные члены объекта внутри его конструкторов.
Вызов виртуального члена всегда приведет к вызову перегрузки самого удаленного члена в иерархии наследования независимо от того, был вызван конструктор типа, который определяет перегрузку самого удаленного члена, или нет. Это продемонстрировано в следующем примере. При выполнении конструктора базового класса осуществляется вызов члена производного класса, даже если конструктор производного класса не вызывался. В данном примере печатается BadBaseClass, чтобы показать, что поле состояния не обновлялось конструктором DerivedFromBad.
Imports System
Namespace Examples.DesignGuidelines.MemberDesign
Public Class BadBaseClass
Protected state as String
Public Sub New()
state = "BadBaseClass"
SetState()
End Sub
Public Overridable Sub SetState()
End Sub
End Class
Public Class DerivedFromBad
Inherits BadBaseClass
Public Sub New()
state = "DerivedFromBad "
End Sub
Public Overrides Sub SetState()
Console.WriteLine(state)
End Sub
End Class
Public Class tester
Public Shared Sub Main()
Dim b as DerivedFromBad = new DerivedFromBad()
End Sub
End Class
End Namespace
using System;
namespace Examples.DesignGuidelines.MemberDesign
{
public class BadBaseClass
{
protected string state;
public BadBaseClass()
{
state = "BadBaseClass";
SetState();
}
public virtual void SetState()
{
}
}
public class DerivedFromBad : BadBaseClass
{
public DerivedFromBad()
{
state = "DerivedFromBad ";
}
public override void SetState()
{
Console.WriteLine(state);
}
}
public class tester
{
public static void Main()
{
DerivedFromBad b = new DerivedFromBad();
}
}
}
using namespace System;
namespace Examples { namespace DesignGuidelines { namespace MemberDesign
{
public ref class BadBaseClass
{
protected:
String^ state;
public:
BadBaseClass()
{
state = "BadBaseClass";
SetState();
}
virtual void SetState()
{
}
};
public ref class DerivedFromBad : public BadBaseClass
{
public:
DerivedFromBad()
{
state = "DerivedFromBad ";
}
virtual void SetState() override
{
Console::WriteLine(state);
}
};
public ref class tester
{
public:
static void Main()
{
DerivedFromBad^ b = gcnew DerivedFromBad();
}
};
}}}
Фрагменты — © Корпорация Майкрософт (Microsoft Corp.), 2005. Все права защищены.
Фрагменты — © Addison-Wesley Corporation. Все права защищены.
Для дополнительной информации о разработке руководящих принципов, смотрите "руководства по разработке рамок: Конвенций, идиомы и шаблоны для повторного использования.NET библиотек"книга, Кшиштоф Cwalina и Брэд Абрамс, опубликованных Addison-Wesley, 2005 года.