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.