fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
4 minut

Constructor chaining


21.12.2009

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.

0 0 votes
Article Rating
5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Gutek
14 years ago

a ja jestem przeciwny takiemu tworzeniu obiektów.

czyli zamiast pustego konstrutkora i tych innych IMO powinno byc:
var user = new UserBuilder()
.WithEmptyId()
.WithName("Jacek")
.WithDefaultRoles();

// to tylko przyklad

Ogolnie jezeli klasa juz potrzebuje wiecej niz 2 konstrutkorow to ja bym tworzyl Builder. Daje Ci to mozliwosc zarzadzania tym jakby sie potem moglo wszystko rozrosnac. Poza tym, dzieki builderowi tez nie powtarzasz kodu.

Dodatkowo w przykladie uzyles obiektu User -> zadko sie zdarza ze jest to obiekt, ktorego mozna dowolnie "zmieniac". Jego dane sa immutable a IMO kazda zmiania powinna byc gdzies odnotowana – przynajmniej ja staram sie zawsze takie podejscie zastosowac :)

Dodatkowo MS juz zapowiedzial a na grupach dyskusyjnych toczyla sie dyskusja na temat wlasnie zastapienia "paru" konstruktorow za pomoca Optional/Named Parameters. Ogolnie nie po to powstala ta technologia i wykorzystywanie tego nagminnie moze prowadzic do niezlego zamieszania w kodzie. Tutaj akurat wszyscy byli zgodni. Jak chcesz to podesle Ci spakowana taka dyskusje (IMO domyslasz sie gdzie sie toczyla ;)).

Gutek

procent
14 years ago

@Gutek:
Co do buildera to zgadzam sie ze jest fajny, ale jest tez bardziej czasochlonny niz po prostu konstruktor (a szczegolnie jego wersja ‘fluent’).
User z kolei to tylko przyklad, akurat mialem w VS taka klase otwarta z jakiegos poprzedniego posta:).
Dyskusja: domyślam się gdzie byla, ale nawet jak podeslesz to pewnie i tak nie bedzie mi sie chcialo czytac z tego samego wzgledu z ktorego nie czytam ‘na zywo’:). Jestem pewny ze przesadzanie z default params moze prowadzic do balaganu, ale dopiero w praktyce okaze sie jak to naprawde jest. Poki co mozna jedynie gdybac – tak jak mialo to miejsce w przypadku ‘var’ z C# 3.0.

Generalnie chodzi o to zeby nie powtarzac kodu, a zawarcie wszystkiego w jednym konstruktorze jest na to sposobem – oczywiscie nie jedynym.

squash
squash
14 years ago

kurcze, uzywam takich rozwiazan od dawna i nie wiedzialem, ze one są "chained" :)
podobne rozwiazania uzywam w normalnych funkcjach przeladowanych

gutek – na pewno masz racje z builderami ale to oczywiscie wszystko zalezy od kontekstu

squash

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również