Les accesseurs sont plus que des champs. D'autres ont déjà signalé plusieurs différences importantes, et je vais en ajouter une de plus.
Les propriétés participent aux classes d'interface. Par exemple:
interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
}
Cette interface peut être satisfaite de plusieurs manières. Par exemple:
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;
}
}
...
}
Dans cette implémentation, nous protégeons à la fois la Person
classe contre un état non valide, ainsi que l'appelant contre la suppression de null de la propriété non attribuée.
Mais nous pourrions pousser le design encore plus loin. Par exemple, l'interface peut ne pas traiter le setter. Il est tout à fait légitime de dire que les consommateurs d' IPerson
interface ne sont intéressés qu'à obtenir la propriété, pas à la définir:
interface IPerson
{
string FirstName { get; }
string LastName { get; }
}
L'implémentation précédente de la Person
classe satisfait cette interface. Le fait qu'il laisse l'appelant définir également les propriétés n'a pas de sens du point de vue des consommateurs (qui consomment IPerson
). Une fonctionnalité supplémentaire de l'implémentation concrète est prise en compte par, par exemple, le constructeur:
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);
}
Dans ce code, le consommateur ne connaît pas les poseurs de propriété - ce n'est pas son affaire de le savoir. Le consommateur n'a besoin que de getters, et il obtient des getters de l'interface, c'est-à-dire du contrat.
Une autre implémentation complètement valide de IPerson
serait une classe de personne immuable et une fabrique de personnes correspondante:
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);
}
Dans cet exemple de code, le consommateur n'a à nouveau aucune connaissance du remplissage des propriétés. Le consommateur ne s'occupe que des getters et de l'implémentation concrète (et la logique métier derrière elle, comme tester si le nom est vide) est laissée aux classes spécialisées - constructeurs et usines. Toutes ces opérations sont absolument impossibles avec les champs.