Akcesory to coś więcej niż pola. Inni już wskazali kilka ważnych różnic, a ja dodam jeszcze jedną.
Właściwości biorą udział w klasach interfejsów. Na przykład:
interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
}
Ten interfejs można spełnić na kilka sposobów. Na przykład:
class Person: IPerson
{
private string _name;
public string FirstName
{
get
{
return _name ?? string.Empty;
}
set
{
if (value == null)
throw new System.ArgumentNullException("value");
_name = value;
}
}
...
}
W tej implementacji chronimy zarówno Person
klasę przed wejściem w niepoprawny stan, jak i obiekt wywołujący przed uzyskaniem wartości null z nieprzypisanej właściwości.
Ale możemy jeszcze bardziej rozwinąć projekt. Na przykład interfejs może nie radzić sobie z ustawiaczem. Można powiedzieć, że konsumenci IPerson
interfejsu są zainteresowani tylko uzyskaniem własności, a nie jej ustawieniem:
interface IPerson
{
string FirstName { get; }
string LastName { get; }
}
Poprzednia implementacja Person
klasy spełnia wymagania tego interfejsu. Fakt, że pozwala dzwoniącemu również ustawić właściwości, jest bez znaczenia z punktu widzenia konsumentów (konsumujących IPerson
). Dodatkową funkcjonalność konkretnej implementacji bierze pod uwagę np. Budowniczy:
class PersonBuilder: IPersonBuilder
{
IPerson BuildPerson(IContext context)
{
Person person = new Person();
person.FirstName = context.GetFirstName();
person.LastName = context.GetLastName();
return person;
}
}
...
void Consumer(IPersonBuilder builder, IContext context)
{
IPerson person = builder.BuildPerson(context);
Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}
W tym kodzie konsument nie wie o ustawieniach właściwości - wiedza o tym nie jest jego interesem. Konsument potrzebuje tylko getterów, a dostaje gettery z interfejsu, czyli z umowy.
Inną całkowicie poprawną implementacją IPerson
byłaby niezmienna klasa osób i odpowiednia fabryka osób:
class Person: IPerson
{
public Person(string firstName, string lastName)
{
if (string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName))
throw new System.ArgumentException();
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
}
...
class PersonFactory: IPersonFactory
{
public IPerson CreatePerson(string firstName, string lastName)
{
return new Person(firstName, lastName);
}
}
...
void Consumer(IPersonFactory factory)
{
IPerson person = factory.CreatePerson("John", "Doe");
Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}
W tym przykładowym kodzie konsument po raz kolejny nie ma wiedzy o wypełnianiu właściwości. Konsument zajmuje się tylko pobieraniem i konkretną implementacją (oraz logiką biznesową stojącą za tym, jak testowanie, jeśli nazwa jest pusta) jest pozostawiona wyspecjalizowanym klasom - budowniczym i fabrykom. Wszystkie te operacje są całkowicie niemożliwe w przypadku pól.