Objektorientierung für Dummis (mich)

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
mr.mc.mauser
Beiträge: 33
Registriert: Di 8. Jun 2010, 19:38

Objektorientierung für Dummis (mich)

Beitrag von mr.mc.mauser »

Hallo,

ich bin zwar nicht ganz neu in Lazarus, aber in Objektorientierung :|
Ich habe mich da immer Durchgewurschtelt und mit Globalen Variablen oder Hilfsfeldern gearbeitet.
Jetzt lese ich Öfters das Globale Variablen nicht das Optimale sind.
Habe mir zum Verständnis ein Formular angelegt mit einen Label und 1 Button:
Dazu dieser Quelltext:

Code: Alles auswählen

unit Unit1; 
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end; 
 
var
  Form1: TForm1;
  X: Integer;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  X:=X+1;
  Label1.Caption:='X ='+IntToStr(x);
end;
 
end.
Wie macht man das jetzt das X in einem Objekt gespeichert wird und das jede andere Funktion darauf zurückgreifen kann ?
Also Quasi wie bei der Globalen Variable

Sorry die vielleicht blöde Frage, aber da finde ich den Einstieg nicht, habe zwar einige Todos gelesen, aber nie so das ich diesen Teil kapiert habe :oops:

Gruß
Robert

MAC
Beiträge: 770
Registriert: Sa 21. Feb 2009, 13:46
OS, Lazarus, FPC: Windows 7 (L 1.3 Built 43666 FPC 2.6.2)
CPU-Target: 32Bit

Re: Objektorientierung für Dummis (mich)

Beitrag von MAC »

das ist der falsche Ansatz. (ist ja auch nur ein einfaches beispiel...)

Eine Zahl in einem Objekt zu speichern ist "sinnlos". Aber soballt du mehrere ,miteinander kombinierte Variablen, Funktionen ,Proceduren speichern willst lohnt es sich ein Objekt zu erstellen

Ein Objekt in deiner Unit ist z.B Das TForm1.
Denn TForm1 ist abgeleitet von TForm

Code: Alles auswählen

TForm1 = class(TForm)
Es enthält dann zusätzlich einen Button1 von der Classe TButton sowie ein Label1 von der Klasse TLabel;
Dem Objekt ist eine Procedure zugeordnet: Button1Click
das wars.
Jetzt könntest du z.B. folgendes machen.
Globale Variable:

Code: Alles auswählen

Form2:TForm1;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2 := TForm1.Create(nil);
end;
und du hättest eine 2te Form. Leider geht das bei Formen nicht so ganz einfach (man sieht die 2te Form zumindest nicht), aber das ist das System.



Wir können aber auch folgendes machen, Ein Edit zur Laufzeit erstellen.

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
 aEdit:TEdit;
begin
aEdit := TEdit.Create(Form1);
aEdit.Parent := Form1;
aEdit.left := random(100);
aEdit.top := random(100);
aedit.Width := 100;
aEdit.Height := 30;
//aEdit.Free;
end;
Und wir haben unser erstes Visuelles Ergebnis.
Man kann also zusammenfallsen Objekte sind gut, da man sie 1 mal schreibt, aber unendliche viele Instanzen öffnen kann. Wie in diesem Beispiel. TEdit wurde nur 1 mal Programmiert, trotzdem hast du am ende 100 Stück auf deinem Formular...
Dieses Beispiel hat auch seine Nachteile - so genannte Speicherlecks.
Jedes mal wenn aEdit.Create(...) aufgerufen wird wird arbeitsspeicher reserviert.
Diesen muss man am ende Wieder freigeben (sonnst verbraucht die anwendung nachher 2 GB speicher...) indem man aEdit.Free am ende von Button1.Click ausführt. Dann wird das Edit aber sofort gelöscht und man sieht nichts mehr;
Dies umgeht man indem mann indem man in einem Globalen Array ( meinarray: array of TEdit )alle Pointer auf aEdit speicher und beim Button2 Click alle mit .Free freigibt
.

Code: Alles auswählen

setlength(meinArray,length(meinarray)+1);
MeinArray[high(meinArray)] := aEdit;

Nur mal so als ausführung - um ehrlich zu sein könnte ich noch 100 andere Sachen dazu erwähnen. Wie die ganzen Proceduren und Variablen die im Objekt miterstellt werden (z.B: TEdit.Text ...)

Um zu deinem Problem zurückzukommen , man kann x auch als Variable von TForm1 schreiben...

Code: Alles auswählen

TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
    x:integer; // hier. Ändern tust du es nun mit Form1.x := 1;
  end;
vielleicht noch:
http://wiki.lazarus.freepascal.org/Obje ... nd_Lazarus" onclick="window.open(this.href);return false;

Code: Alles auswählen

Signatur := nil;

mr.mc.mauser
Beiträge: 33
Registriert: Di 8. Jun 2010, 19:38

Re: Objektorientierung für Dummis (mich)

Beitrag von mr.mc.mauser »

Ich danke Dir Mac, das hat mir schon mal die Augen ein wenig geöffnet.

Ich habe jetzt ein wenig Experimentier und bin zu diese Ergebnis gekommen (ist für eine Einfache Zählfunktion etwas Dick aber mir gehts ja ums Kapieren)

Code: Alles auswählen

unit Unit1; 
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end; 
 
  TTest = class
    private
      Fzahl: Integer;
      Ftext: String;
      procedure Settext;
    public
       procedure SetZahl;
  end;
 
var
  X: TTest;
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.FormShow(Sender: TObject);
begin
     X := TTest.Create;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  X.SetZahl;
  Label1.Caption:='X ='+X.Ftext;
end;
 
procedure TTest.SetZahl;
begin
   self.Fzahl:=self.Fzahl+1;
   Settext;
end;
 
procedure TTest.Settext;
begin
   self.Ftext:=inttostr(self.Fzahl);
end;
 
end.
Jetzt bin ich mir nur nicht ganz sicher wo das X.create nun hinkommt, ich habe es in TForm1.FormShow gemacht weil das eben das ganze Formular aufruft.
Wenn ich es nach TForm1.Button1Click machen würde bleibt mein X immer bei 1 stehen.

Und mir ist noch etwas aufgefallen.
Eine Globale variable setzt man ja einfach mit Var X:Integer;, an der gleichen Stelle sage ich jetzt ja X:TTest; das ist ja die gleiche Baustelle.
Also ist es ja auch eine Globale "sache".
Und der Vorteil gegenüber einer Globalen Variable ist das ich noch Proceduren und Funktionen dazu machen kann, wie ich es in meinen kleinen Skript gemacht habe.

Jetzt wird einiges verständlicher für mich.

Gruß
Robert

MAC
Beiträge: 770
Registriert: Sa 21. Feb 2009, 13:46
OS, Lazarus, FPC: Windows 7 (L 1.3 Built 43666 FPC 2.6.2)
CPU-Target: 32Bit

Re: Objektorientierung für Dummis (mich)

Beitrag von MAC »

genau.
Die eine Globale variable bleibt.
Allerdings nur mal ein beispiel.
Du programmierst ein Fussballspiel ohne Objekte.
1 Spieler
Du hast eine Globale Variable für die Position des Spieler 1.
Du hast ein booleanwert ob er den Ball besitzt,
Eine Integerzahl zu welchem Team er gehört.
eine weitere Zahl wieviele Rote/Gelbe karten er hat.
Torquoten.
EinpaarFunktionen wie zum beispiel:
function ist_Ball_in_nahe(position_vom_ball:TPoint):boolean;


So. Wenn du jetzt 2 spieler haben willst.
Dann kopierst du das alles. Das heißt du hast doppelt so viele Variablen und funktionen und das wird unübersichtlich.

Der Trick: Du machst 1 mal die Klasse TSpieler mit dem ganzen zeug. Un Definierst dann Global nur 22 Spieler. (also 22 Instanzen)

Code: Alles auswählen

Spieler : array [0..21] of TSpieler;
Und das war es.


Zu deiner Frage:
1. Ich sag dir nur schonmal das x := TTest.Create richtig ist. X.Create wird zwar kompiliert, gibt aber einen fehler zur laufzeit...
2.Wenn du es in Button 1 machst dann wird jedes mal eine neue instanz erstellt, deshalb ist der Wert von FZahl = 0, der wird 1 mal erhöht und dann ist er auf 1. Hier haben wir dann wieder das Speicherleck, da immer neuer speicher besetzt wird. Allerdings der Pfad zum alten Speicher verloren geht und dieser schwer bereinigt werden kann...
OnShow ist da besser, trotzdem kann es sein das wenn du die form minimierst oder schließt und dann wieder startest das die Prozedure 2 mal aufgerufen wird.
OnCreate ist hier empfehlenswerter.
(Ich mach das immer mit einem Timer den ich nur 1 mal ausführenlasse mit Timer1.Enabled := False; in OnTimer...)
3.Das self ist unnötig, kannst es aber drinn lassen wenn du willst...

4. Außerdem gibt es noch propertys. Die sind relativ nützlich - für den Benutzer verhält es sich wie eine Variable. Z.B TEdit.Text ist eine Property. Das tolle daran. Wir können sagen das soballt der Benutzer eine Variable veränder eine Prozedure ausgeführt wird...
Mann sollte die F-Variablen übrigenz nur in funktionen von TTest verwenden - Da diese (wie in deinem Fall richtig gemacht wurde) geschützt sind und somit, nur in der Unit von TTest verändert werden können...

Code: Alles auswählen

unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;
 
  { TTest }
 
  TTest = class
    private
      Fzahl: Integer;
      Ftext: String;
      procedure Setzahl(const AValue: integer);
    public
       property Zahl:integer read FZahl write Setzahl; // hier darf man die zahl bestimmen. // wenn man die Zahl auslesen will wird man auf die variable fzahl weitergeleitet. will man sie ändern wird man auf SetZahl weitergeleitet.
       property Text:string read Ftext; // diese Property ist Readonly, man kann Text nicht ändern nur lesen...
  end;
 
var
  X: TTest;
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.FormShow(Sender: TObject);
begin
     X := TTest.Create;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  X.zahl := x.Zahl+1;
  Label1.Caption:='X ='+X.Text; // ausgelesen wird über die Property Text
end;
 
procedure TTest.Setzahl(const AValue: integer);
begin
  FZahl:=AValue;
  Ftext := inttoStr(AValue); // jedes man wenn Zahl geändert wird wird FText mitgeändert...
end;
 
end.

Code: Alles auswählen

Signatur := nil;

mr.mc.mauser
Beiträge: 33
Registriert: Di 8. Jun 2010, 19:38

Re: Objektorientierung für Dummis (mich)

Beitrag von mr.mc.mauser »

Danke dir Mac,

genauso müsste ein ToDo gemacht sein, dann kapiert man es auch.
Habe jetzt ein wenig gespielt und nun kapiere ich auch warum ich manchmal ne Fehlermeldung bekommen habe gerade bei ausgelagerten Datenmodulen...

Ist es auch möglich mit einem Property mehre Werte zu übergeben zb. so:

Code: Alles auswählen

private
    procedure SetClausel(buch:string; wurst:integer);
public
    property Clausel:String read Erg write SetClausel;
wie muss dann der Property aussehen ?

Gruß
Robert

Maik81ftl
Beiträge: 619
Registriert: Mi 9. Mär 2011, 16:34
OS, Lazarus, FPC: Ubuntu10.04 LTS (L 0.9.31.0 FPC 2.4.4)
CPU-Target: 64Bit
Wohnort: seit 01.06.2011 in Wahlstedt

Re: Objektorientierung für Dummis (mich)

Beitrag von Maik81ftl »

mr.mc.mauser hat geschrieben:Danke dir Mac,

genauso müsste ein ToDo gemacht sein, dann kapiert man es auch.
Habe jetzt ein wenig gespielt und nun kapiere ich auch warum ich manchmal ne Fehlermeldung bekommen habe gerade bei ausgelagerten Datenmodulen...

Ist es auch möglich mit einem Property mehre Werte zu übergeben zb. so:

Code: Alles auswählen

private
    procedure SetClausel(buch:string; wurst:integer);
public
    property Clausel:String read Erg write SetClausel;
wie muss dann der Property aussehen ?

Gruß
Robert
Ähmmm Ja und Nein, wobei das Ja eher überwiegt.

Was ich die aber da eher Empfehlen würde um da auf nummer sicher zu gehen auch den werten, die du übergeben möchtest, ein Typ zu machen, Somit hast du am ende nur eine variable.

Gennerell empfele ich da für neulinge das Buch "MSR mit Delphi" von Franzis.

kann man auch unter lazarus verwenden.

Meine klassen und Komponenten habe ich auch alle mit diesem Buch erfolgreich aufgebau.
Ubuntu 10.04 LTS ist meine Heimat. Lazarus ist meine Sprache :D und der Kreis Segeberg meine LIEBE :D

MAC
Beiträge: 770
Registriert: Sa 21. Feb 2009, 13:46
OS, Lazarus, FPC: Windows 7 (L 1.3 Built 43666 FPC 2.6.2)
CPU-Target: 32Bit

Re: Objektorientierung für Dummis (mich)

Beitrag von MAC »

generell kann man, soweit ich weis einer property nur 1 Wert zuordnen.
Man kann aber tricksen.

zum beispiel in dem man über dem objekt ein Record definiert:

Code: Alles auswählen

TClausel = record
wurst:integer;
buch:string;
end;
und dann kann man TClausel " wie eine ganz normale variable, z.b vom typ integer verwenden "

Code: Alles auswählen

private
    procedure SetClausel(avalue:TClausel);
public
    property Clausel:TClausel read Erg write SetClausel;
Der Record speichert mehrere Werte unter einem Namen.
Man kann im record auf die einzelnen werte zugreifen in dem man Clausel.wurst oder Clausel.Buch verwendet.
Man kann Record1 := Record2 setzen. Dann haben beide den gleichen, aber nicht denselben wert (Gegenteil: Wenn man das mit Klassen macht und danach Class1 ändert wird dort automatisch Class2 mitgeändert - weil sich im name nur ein Pointer(Wegweise) auf die eigentlichen Werte vorkommt - bei Records kann man das bedenkenlos machen.)

Code: Alles auswählen

Signatur := nil;

Maik81ftl
Beiträge: 619
Registriert: Mi 9. Mär 2011, 16:34
OS, Lazarus, FPC: Ubuntu10.04 LTS (L 0.9.31.0 FPC 2.4.4)
CPU-Target: 64Bit
Wohnort: seit 01.06.2011 in Wahlstedt

Re: Objektorientierung für Dummis (mich)

Beitrag von Maik81ftl »

MAC hat geschrieben:generell kann man, soweit ich weis einer property nur 1 Wert zuordnen.
Man kann aber tricksen.

zum beispiel in dem man über dem objekt ein Record definiert:

Code: Alles auswählen

TClausel = record
wurst:integer;
buch:string;
end;
und dann kann man TClausel " wie eine ganz normale variable, z.b vom typ integer verwenden "

Code: Alles auswählen

private
    procedure SetClausel(avalue:TClausel);
public
    property Clausel:TClausel read Erg write SetClausel;
Der Record speichert mehrere Werte unter einem Namen.
Man kann im record auf die einzelnen werte zugreifen in dem man Clausel.wurst oder Clausel.Buch verwendet.
Man kann Record1 := Record2 setzen. Dann haben beide den gleichen, aber nicht denselben wert (Gegenteil: Wenn man das mit Klassen macht und danach Class1 ändert wird dort automatisch Class2 mitgeändert - weil sich im name nur ein Pointer(Wegweise) auf die eigentlichen Werte vorkommt - bei Records kann man das bedenkenlos machen.)
Würde sagen, wir haben Beide Recht. nein, weil nur ein wert. Ja, da der Trick17 greift. Ergo Typ erstellen. Dahingehend kann man sagen in dem man einen Typ erstellt, kann man mit einer Variable sehr viele Werte übergeben.
Ubuntu 10.04 LTS ist meine Heimat. Lazarus ist meine Sprache :D und der Kreis Segeberg meine LIEBE :D

carli
Beiträge: 657
Registriert: Sa 9. Jan 2010, 17:32
OS, Lazarus, FPC: Linux 2.6.x, SVN-Lazarus, FPC 2.4.0-2
CPU-Target: 64Bit

Re: Objektorientierung für Dummis (mich)

Beitrag von carli »

MAC hat geschrieben:generell kann man, soweit ich weis einer property nur 1 Wert zuordnen.
Man kann aber tricksen.

zum beispiel in dem man über dem objekt ein Record definiert:

Code: Alles auswählen

TClausel = record
wurst:integer;
buch:string;
end;
und dann kann man TClausel " wie eine ganz normale variable, z.b vom typ integer verwenden "

Code: Alles auswählen

private
    procedure SetClausel(avalue:TClausel);
public
    property Clausel:TClausel read Erg write SetClausel;
Der Record speichert mehrere Werte unter einem Namen.
Man kann im record auf die einzelnen werte zugreifen in dem man Clausel.wurst oder Clausel.Buch verwendet.
Man kann Record1 := Record2 setzen. Dann haben beide den gleichen, aber nicht denselben wert (Gegenteil: Wenn man das mit Klassen macht und danach Class1 ändert wird dort automatisch Class2 mitgeändert - weil sich im name nur ein Pointer(Wegweise) auf die eigentlichen Werte vorkommt - bei Records kann man das bedenkenlos machen.)
Dann würde ich aber schon Klasse statt record nutzen aus mehreren Gründen:
- Kopieren geht schneller (weil Zeiger statt Daten)
- Man kann auch Eigenschaften der Klasse einzeln setzen (record und property mag das nicht)
- im Getter könnte man sogar eine Routine schreiben, die die Klasse erstellt, falls sie noch nicht existiert
- Dadurch sind Startwerte möglich
- und gewisse andere Vorzüge von Klassen (Methoden vor allem)

Antworten