Properties sind Funktionen, die so tun als seien sie Variable.
Dieser Text soll zeigen, dass Properties Lesbarkeit und Änderungfreundlichkeit in Programmen fördern.
So ziemlich jede Programmiersprache ermöglicht es, Sachen in Variablen zu speichern. Also etwa:
string MeineVariable = "Hello World";
Später kann man diesen Wert wieder abfragen, indem man einfach den Variablennamen hinschreibt:
Ausgabe(MeineVariable);
Im Falle von Sprachen mit einem objektorientierten Touch kann man solche Variablen noch an ein Objekt knüpfen. Ein Beispiel (in C#):
class MeinObject
{
public string MeineVariable;
}
MeinObjekt einObjekt = new MeinObjekt();
einObjekt.MeineVariable = "Hello World";
Ausgabe(einObjekt.MeineVariable);
Variablen sind schön einfach zu setzen und zu lesen - das sieht alles sehr natürlich aus. Aber: sie haben ein Problem. Man kann sie schlecht kontrollieren. Das folgende Beispiel soll das illustrieren. Es ist zu beachten, dass es eine starke Vereinfachung der Situationen in real vorhandener Software darstellt. Insbesondere sind in "richtigen" Systemen die Zugriffe auf die Variablen oft weit verstreut.
Hier hätte man gerne, dass in Summe immer automatisch die Summe der vier anderen Variablen steht.
Das ginge, indem diese immer geändert wird, wenn sich eine der andern Variablen ändert:
Ein anderes gewichtiges Problem ist, dass der Kode nicht sehr änderungfreundlich ist. Entscheidet man sich zum Beispiel später, die Variablen RechteTasche und LinkeTasche in eine extra Klasse auszulagern, so muss man auch alle Benutzungen der beiden Variablen ändern. Das ist dann zumeist so riskant, dass man es lieber läßt. Und so entsteht Spookyware:
Deshalb gibt es die Konvention, Variable möglichst im Objekt zu verstecken und den Zugriff nur über Funktionen zu ermöglichen.
class Finanzen
{
public double RechteTasche;
public double LinkeTasche;
public double Brustbeutel;
public double Kopfkissen;
public double Summe;
}
Finanzen meineFinanzen = new Finanzen();
meineFinanzen.RechteTasche = 7.34;
meineFinanzen.LinkeTasche = 0.0;
meineFinanzen.Brustbeutel = 130.0;
meineFinanzen.Kopfkissen = 1000000.0;
Ausgabe(meineFinanzen.Summe);
Das ginge, indem diese immer geändert wird, wenn sich eine der andern Variablen ändert:
class Finanzen
{
public double RechteTasche;
public double LinkeTasche;
public double Brustbeutel;
public double Kopfkissen;
public double Summe;
}
Finanzen meineFinanzen = new Finanzen();
meineFinanzen.Summe = 0;
meineFinanzen.RechteTasche = 7.34;
meineFinanzen.Summe = meineFinanzen.RechteTasche
+ meineFinanzen.LinkeTasche
+ meineFinanzen.Brustbeutel
+ meineFinanzen.Kopfkissen
;
meineFinanzen.LinkeTasche = 0.0;
meineFinanzen.Summe = meineFinanzen.RechteTasche
+ meineFinanzen.LinkeTasche
+ meineFinanzen.Brustbeutel
+ meineFinanzen.Kopfkissen
;
meineFinanzen.Brustbeutel = 130.0;
meineFinanzen.Summe = meineFinanzen.RechteTasche
+ meineFinanzen.LinkeTasche
+ meineFinanzen.Brustbeutel
+ meineFinanzen.Kopfkissen
;
meineFinanzen.Kopfkissen = 1000000.0;
meineFinanzen.Summe = meineFinanzen.RechteTasche
+ meineFinanzen.LinkeTasche
+ meineFinanzen.Brustbeutel
+ meineFinanzen.Kopfkissen
;
Ausgabe(meineFinanzen.Summe);
oder - einfacher - indem bei der Abfrage der Wert jedesmal berechnet wird.
Man kann sich auch noch coolere Varianten vorstellen. Aber alle haben eins gemeinsam: Sie sind fehlerträchtig. Es wird nicht erzwungen, die Summe zu ändern, wenn sich einer der anderen Werte ändert. Man kann mit Variablen sehr leicht Inkonsistenzen erzeugen. Ein anderes gewichtiges Problem ist, dass der Kode nicht sehr änderungfreundlich ist. Entscheidet man sich zum Beispiel später, die Variablen RechteTasche und LinkeTasche in eine extra Klasse auszulagern, so muss man auch alle Benutzungen der beiden Variablen ändern. Das ist dann zumeist so riskant, dass man es lieber läßt. Und so entsteht Spookyware:
class MantelTaschen
{
public double Rechts;
public double Links;
}
class Finanzen
{
public MantelTaschen Taschen;
public double Brustbeutel;
public double Kopfkissen;
public double Summe;
}
Finanzen meineFinanzen = new Finanzen();
meineFinanzen.Taschen.Rechts = 7.34;
meineFinanzen.Taschen.Links = 0.0;
meineFinanzen.Brustbeutel = 130.0;
meineFinanzen.Kopfkissen = 1000000.0;
Ausgabe(meineFinanzen.Summe);
Deshalb gibt es die Konvention, Variable möglichst im Objekt zu verstecken und den Zugriff nur über Funktionen zu ermöglichen.
class Finanzen
{
private double RechteTasche;
private double LinkeTasche;
private double Brustbeutel;
private double Kopfkissen;
public double Summe;
public double GetRechteTasche()
{
return RechteTasche;
}
public void SetRechteTasche(double value)
{
RechteTasche = value;
Summe
= RechteTasche
+ LinkeTasche
+ Brustbeutel
+ Kopfkissen;
}
public void SetLinkeTasche(double value)
{
LinkeTasche = value;
Summe
= RechteTasche
+ LinkeTasche
+ Brustbeutel
+ Kopfkissen;
}
...
}
Finanzen meineFinanzen = new Finanzen();
meineFinanzen.SetRechteTasche(7.34);
meineFinanzen.SetLinkeTasche(0.0);
meineFinanzen.SetBrustbeutel(130.0);
meineFinanzen.SetKopfkissen(1000000.0);
Ausgabe(meineFinanzen.Summe);
class Finanzen
{
private double RechteTasche;
private double LinkeTasche;
private double Brustbeutel;
private double Kopfkissen;
public double GetRechteTasche()
{
return RechteTasche;
}
public void SetRechteTasche(double value)
{
RechteTasche = value;
}
public double GetSume()
{
return RechteTasche
+ LinkeTasche
+ Brustbeutel
+ Kopfkissen;
}
}
Finanzen meineFinanzen = new Finanzen();
meineFinanzen.SetRechteTasche(7.34);
meineFinanzen.SetLinkeTasche(0.0);
meineFinanzen.SetBrustbeutel(130.0);
meineFinanzen.SetKopfkissen(1000000.0);
Ausgabe(meineFinanzen.GetSumme());
Hier mal ein Beispiel dass etwas komplizierte ist, im Vergleich dazu die Schreibweise, wie es mit Variablen aussehen würde:
ich.GetBesterFreund().GetFinanzen().SetRechteTasche
(ich.GetFinanzen().GetBrustbeutel());
versus:
ich.BesterFreund.Finanzen.RechteTasche
= ich.Finanzen.Brustbeutel;
Das zweite sieht - schlicht und ergreifend - einfacher aus, ist viel einfacher zu lesen und schließlich auch viel näher an dem was passiert.
Aber die oben beschriebenen Probleme gebieten es, dass öffentlich benutzbare Variablen ganz schlecht sind und komplett vermieden werden sollten. In der Tat gibt es in vielen Programmiersprachen auch tatsächlich derartige Konventionen.
Das ist schade, da die Syntax ja doch elegant und mit einer hohen Konsistenz zwischen Inhalt und Form daher kommt.
Properties lösen diese Probleme.
Darüberhinaus erlaubt das Vorhandensein von Properties in einer Programmiersprache, öffentliche Variablen zu verwenden wenn dies geboten ist. So gibt es im obigen Beispiel zunächst nichts, was dagegen spricht, RechteTasche, LinkeTasche, Brustbeutel und Kopfkissen als öffentlich zugängliche Variablen zu definieren. Sowohl das Setzen als auch das Auslesen des Wertes ist unproblematisch. Summe muß freilich von Anfang an als Property definiert werden. Also:
Properties lösen diese Probleme.
Darüberhinaus erlaubt das Vorhandensein von Properties in einer Programmiersprache, öffentliche Variablen zu verwenden wenn dies geboten ist. So gibt es im obigen Beispiel zunächst nichts, was dagegen spricht, RechteTasche, LinkeTasche, Brustbeutel und Kopfkissen als öffentlich zugängliche Variablen zu definieren. Sowohl das Setzen als auch das Auslesen des Wertes ist unproblematisch. Summe muß freilich von Anfang an als Property definiert werden. Also:
class Finanzen
{
public double RechteTasche;
public double LinkeTasche;
public double Brustbeutel;
public double Kopfkissen;
public double Summe
{
get
{
return RechteTasche
+ LinkeTasche
+ Brustbeutel
+ Kopfkissen;
}
}
}
Finanzen meineFinanzen = new Finanzen();
meineFinanzen.RechteTasche = 7.34;
meineFinanzen.LinkeTasche = 0.0;
meineFinanzen.Brustbeutel = 130.0;
meineFinanzen.Kopfkissen = 1000000.0;
Ausgabe(meineFinanzen.Summe);
Wenn dann zu einem späteren Zeitpunkt der Lebenzeit dieser Software die Anforderung entsteht, eine Klasse Manteltaschen einzuführen, ist das kein Problem für die Zugriffe. Man versteckt die Änderung einfach in der Klasse Finanzen:
Fazit:
Die Variablenschreibweise erhöht die Lesbarkeit von Programmen erheblich. Sie ist der Funktionalen Schreibweise unter Verwendung von Getter- und Setterfunktionen überlegen. Auserdem wird durch die Existenz von Properties erst die (nahezu) problemlose Benutzung von Variablen ausshalb einer Klasse ermöglicht, da eine nachträgliche Umwandlung in eine Property ohne Codeänderung bei den Zugriffen erfolgen kann.
Aufgrund dieser Möglichkeit sind Properties auch mehr als nur "syntaktischer Zucker".
Bemerkung 1: Hin und wieder hört man das folgende Gegenargument für Properties: Man sieht ihnen nicht an dass da viel mehr passiert als nur das Setzen oder Auslesen einer Variablen.
Natürlich sieht man das nicht. Diese Eigenschaft von Software nennt man Kapselung. Und Kapselung ist ausdrücklich erwünscht. Dass man da draußen mit Situationen konfrontiert ist, wo eine Funktion oder Variable oder Property "RechtTasche" heißt und die dann irgendetwas komplett anderes macht, setzt oder liefert, ist bekannt, hat aber nichts mit dem hier beschriebenen Sachverhalt zu tun.
Niemand behauptet hier, dass Properties (oder irgendein anderes Feature irgendeiner Programmiersprache) verhindern kann, dass schlechte Programmierx schlechte Software schreiben.
Es wird lediglich behauptet, dass Properties geeignet sind, besserere Software zu schreiben.
Bemerkung 2:
Für C++ kenne ich keine Möglichkeit Properties zu implementieren. Das Beispiel, welches in der Wikipedia angegeben ist, funktioniert nicht. Man möge mal so etwas wie in einem der obigen Beispiele versuchen:
class MantelTaschen
{
public double Rechts;
public double Links;
}
class Finanzen
{
public MantelTaschen Taschen;
public double RechteTasche
{
get{return Taschen.Rechts;}
set{Taschen.Rechts = value;}
}
public double LinkeTasche
{
get{return Taschen.Links;}
set{Taschen.Links = value;}
}
public double Brustbeutel;
public double Kopfkissen;
public double Summe
{
get
{
return RechteTasche
+ LinkeTasche
+ Brustbeutel
+ Kopfkissen;
}
}
}
Finanzen meineFinanzen = new Finanzen();
meineFinanzen.RechteTasche = 7.34;
meineFinanzen.LinkeTasche = 0.0;
meineFinanzen.Brustbeutel = 130.0;
meineFinanzen.Kopfkissen = 1000000.0;
Ausgabe(meineFinanzen.Summe);
Die Variablenschreibweise erhöht die Lesbarkeit von Programmen erheblich. Sie ist der Funktionalen Schreibweise unter Verwendung von Getter- und Setterfunktionen überlegen. Auserdem wird durch die Existenz von Properties erst die (nahezu) problemlose Benutzung von Variablen ausshalb einer Klasse ermöglicht, da eine nachträgliche Umwandlung in eine Property ohne Codeänderung bei den Zugriffen erfolgen kann.
Aufgrund dieser Möglichkeit sind Properties auch mehr als nur "syntaktischer Zucker".
Bemerkung 1: Hin und wieder hört man das folgende Gegenargument für Properties: Man sieht ihnen nicht an dass da viel mehr passiert als nur das Setzen oder Auslesen einer Variablen.
Natürlich sieht man das nicht. Diese Eigenschaft von Software nennt man Kapselung. Und Kapselung ist ausdrücklich erwünscht. Dass man da draußen mit Situationen konfrontiert ist, wo eine Funktion oder Variable oder Property "RechtTasche" heißt und die dann irgendetwas komplett anderes macht, setzt oder liefert, ist bekannt, hat aber nichts mit dem hier beschriebenen Sachverhalt zu tun.
Niemand behauptet hier, dass Properties (oder irgendein anderes Feature irgendeiner Programmiersprache) verhindern kann, dass schlechte Programmierx schlechte Software schreiben.
Es wird lediglich behauptet, dass Properties geeignet sind, besserere Software zu schreiben.
Bemerkung 2:
Für C++ kenne ich keine Möglichkeit Properties zu implementieren. Das Beispiel, welches in der Wikipedia angegeben ist, funktioniert nicht. Man möge mal so etwas wie in einem der obigen Beispiele versuchen:
ich.BesterFreund.Finanzen.RechteTasche
= ich.Finanzen.Brustbeutel;
Der Aufruf des Setters funktioniert zwar. Das Konstrukt für den Getter scheitert aber, da der Compiler vor den Punkt-Operator keine Veranlassung sieht, den Konvertierungsoperator aufzurufen. Man müßte die Zugriffe anders schreiben. Aber dann ist ist die Äquivalenz zur Variablenschreibweise weg und die ist essentiell.
Die einzige Lösung ist hier vermutlich, die Sprache zu erweitern.
Mein Vorschlag wäre, eine Dekoration für den Konvertierer einzuführen, die besagt, dass er immer benutzt werden soll, wenn das Property-Objekt als R-Value benutzt wird. Das ist zwar etwas, was die den Konvertierer enthaltende Klasse in ihrer Benutzung ziemlich einschränkt, weil alle anderen Member nur noch R-Value-Situationen aufrufbar sind. Aber dafür hat man dann halt Properties.
Die Microsoft-spezifische Lösung ist (fast) perfekt, aber eben compilerabhängig. Sieht auch etwas spooky aus.
Keine Kommentare:
Kommentar veröffentlichen