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.