Przejdź do treści

DevStyle - Strona Główna
Constructor chaining
Backend

Tagi:

Powtarzanie kodu w kilku miejscach zwykle jest sygnałem zaniedbania i nie powinno mieć miejsca. Nawet (a może: w szczególności!) gdy kod ten jest prosty, głupi, niewymagający myślenia i będący efektem tzw. clipboard inheritance (ctrl+c, ctrl+v).

Tyczy się to również konstruktorów klas. Tą część kodu łatwo jest przegapić, bo wszelakie ułatwiacze umożliwiają automatyczne ich wygenerowanie. A co jeśli mamy ich kilka? Poniższy przykład obrazuje stan, do którego NIE CHCEMY doprowadzić:

  1:  public class User
  2:  {
  3:  	public int Id { get; set; }
  4:  	public string FirstName { get; set; }
  5:  	public string LastName { get; set; }
  6:  	public IList<string> Roles { get; set; }
  7:  
  8:  	public User()
  9:  	{
 10:  		Id = 0;
 11:  		FirstName = string.Empty;
 12:  		LastName = string.Empty;
 13:  		Roles = new List<string>();
 14:  	}
 15:  
 16:  	public User(int id)
 17:  	{
 18:  		Id = id;
 19:  		FirstName = string.Empty;
 20:  		LastName = string.Empty;
 21:  		Roles = new List<string>();
 22:  	}
 23:  
 24:  	public User(int id, string firstName, string lastName)
 25:  	{
 26:  		Id = id;
 27:  		FirstName = firstName;
 28:  		LastName = lastName;
 29:  		Roles = new List<string>();
 30:  	}
 31:  
 32:  	public User(int id, string firstName, string lastName, IList<string> roles)
 33:  	{
 34:  		Id = id;
 35:  		FirstName = firstName;
 36:  		LastName = lastName;
 37:  		Roles = roles;
 38:  	}
 39:  }

Poradzić sobie z tym syfem można w bardzo prosty sposób: wystarczy wykorzystać (wzorzec? konstrukcję? praktykę?) constructor chaining. Całą "logikę" umieszczamy w konstruktorze posiadającym największą liczbę parametrów, po czym wywołania "uboższych" przeciążeń (:)) delegujemy do tego jedynego, wspieranego ultimate ctor:

  1:  public class User
  2:  {
  3:  	private const int EMPTY_ID = 0;
  4:  	private const string EMPTY_FIRST_NAME = "";
  5:  	private const string EMPTY_LAST_NAME = "";
  6:  
  7:  	public int Id { get; set; }
  8:  	public string FirstName { get; set; }
  9:  	public string LastName { get; set; }
 10:  	public IList<string> Roles { get; set; }
 11:  
 12:  	public User()
 13:  		: this(EMPTY_ID, EMPTY_FIRST_NAME, EMPTY_LAST_NAME, new List<string>())
 14:  	{
 15:  	}
 16:  
 17:  	public User(int id)
 18:  		: this(id, EMPTY_FIRST_NAME, EMPTY_LAST_NAME, new List<string>())
 19:  	{
 20:  	}
 21:  
 22:  	public User(int id, string firstName, string lastName)
 23:  		: this(id, firstName, lastName, new List<string>())
 24:  	{
 25:  	}
 26:  
 27:  	public User(int id, string firstName, string lastName, IList<string> roles)
 28:  	{
 29:  		Id = id;
 30:  		FirstName = firstName;
 31:  		LastName = lastName;
 32:  		Roles = roles;
 33:  	}
 34:  }

W tym przykładzie poszedłem o krok dalej definiując zbiór stałych, ale w większości przypadków można pominąć tą czynność.

Nie wiem dlaczego, ale wielokrotnie spotykałem się z ignorancją programistów w tym zakresie. Przecież tak banalne rozwiązanie może wyeliminować tak wiele zbędnego kodu. A gdy dodamy do tego wywoływanie konstruktorów klas bazowych i nadal będziemy ignorować możliwość wzajemnego wywoływania konstruktorów to mamy na własne życzenie gotowy mega-fe mega-bajzel.

Dodać w temacie można, że dostępne w C# 4.0 parametry opcjonalne pozwolą na jeszcze łatwiejsze pozbycie się tego problemu.

Zobacz również