Difference between revisions of "Object Oriented Programming with Free Pascal and Lazarus/hu"

From Free Pascal wiki
Jump to navigationJump to search
m (→‎Bevezetés: kisebb helyesbítés)
(az eredeti cikk komplett átirata (oka: a nehezen értelmezhető, valóságtól elrugaszkodott példák))
Line 1: Line 1:
 
{{Object_Oriented_Programming_with_FreePascal_and_Lazarus}}
 
{{Object_Oriented_Programming_with_FreePascal_and_Lazarus}}
 +
 +
 +
<font size="6">Objektum-orientált programozás Free Pascal-lal és Lazarus-szal</font>
 +
 +
 +
__TOC__
 +
  
 
== Bevezetés ==
 
== Bevezetés ==
Line 20: Line 27:
  
 
Annak ellenére, hogy minden komplex grafikus felhasználói felülettel rendelkező rendszer megírható Pascal-ban, vagy bármely más nyelven, sokkal egyszerűbb objektum-orientált rendszert használni, ahol minden grafikus objektumnak a képernyőn lehetnek saját tulajdonságai és függvényei, így együtt egy összefüggő szerkezetet alkotva.
 
Annak ellenére, hogy minden komplex grafikus felhasználói felülettel rendelkező rendszer megírható Pascal-ban, vagy bármely más nyelven, sokkal egyszerűbb objektum-orientált rendszert használni, ahol minden grafikus objektumnak a képernyőn lehetnek saját tulajdonságai és függvényei, így együtt egy összefüggő szerkezetet alkotva.
 +
 +
Már a tárgyalás elején le kell szögeznünk az osztály és az objektum közötti fontos logikai különbséget: az osztály egy leírás, amely alapján a fordító példányosítás segítségével létrehozza az objektumot.
 +
 +
Sajnos Pascal-ban ez az egyszerű logika csorbát szenved, mert object (objektum) és class (osztály) is deklarálható, illetve példányosítható. A kettő között a különbséget a futási időben elfoglalt memóriaterület helye jelenti: az osztály (class) egy objektumra mutató mutató (pointer). Ez azt jelenti, hogy az osztály a program HEAP területén helyezkedik el, míg az objektum a stack-en.
 +
 +
Ennek ellenére - mivel az object típust ritkán alkalmazzuk - nyugodtan használhatjuk az első logikai gondolatmenetet.
  
 
== Objektumok - a való világ analógiája ==  
 
== Objektumok - a való világ analógiája ==  
  
Vegyük például a vérminta anagiáját, amit egy kórházban vagy az orvosi rendelőben gyűjtenek be.
+
Vegyük például a vérminta analógiáját, amit egy kórházban vagy az orvosi rendelőben gyűjtenek be.
  
 
=== Vérminta ===
 
=== Vérminta ===
Line 69: Line 82:
 
=== Egy másik példa - az automobil ===
 
=== Egy másik példa - az automobil ===
  
Fordítás folyamatban...
+
Ha nem szereted a vért, hasonló okfejtés használható egy autóra is. Ez a példa talán valamivel közérthetőbb. Az autónak a fizikai megjelenésén kívül, egyéb technikai paraméterei is vannak - ezek lesznek az autó objektumunk mezői. Az autónak működnie is kell valamilyen előre meghatározott leírás szerint. A működés több részfolyamatból áll, ezeket a részfolyamatokat nevezzük metódusoknak.
<!---
 
If you don't like blood, the same sort of reasoning could be applied to a car taken to a garage for repair.
 
It might consist of:
 
*the physical vehicle
 
*documents held by the owner: registration or license (including license plates), insurance, receipts for purchase, parts, repairs etc
 
*the fuel consumption history
 
*the drivers allowed to use the vehicle, with their license particulars
 
*service records held by the garage
 
*methods or procedures to be followed for routine checking and maintenance
 
*methods to be followed for non-routine repairs etc
 
*billing information for the customer
 
  
== Programming Example ==
+
Összefoglalva:
  
Enough of this pre-occupation with Real-World examples! Let us proceed to the main purpose: programming in Object Pascal.
+
Az autó mezői:
 +
*szín
 +
*maximális sebesség
 +
*sebességfokozatok száma
 +
*ajtók száma
 +
*utasok maximális száma
 +
*az éppen bent ülő utasok száma
 +
*aktuális sebesség
  
Let us consider the creation of a simple Form with a few controls for an application in Free Pascal/Lazarus.
+
Az autó metódusai:
 +
*motor indítás
 +
*gyorsítás
 +
*lassítás
 +
*sebességváltás
  
<div class="floatleft">[[Image:ObjectInspector-TForm.png]]</div>
+
== Programozási példa ==
<div class="floatright">[[Image:BlankForm.png]]</div>
 
  
On invoking the Lazarus IDE, the programmer is presented with a blank template Form design, on which he is encouraged to place various controls or objects.
+
Az elméleti fejtegetés után térjünk rá arra a kérdésre, hogy hogy néz ki programozási szempontból egy objektum! Ez a szekció ugyan fel van osztva részekre, de érdemesebb egyben olvasni a teljes kép megalkotásához.
  
Note that the pre-made blank Form is already an Object, with its own properties such as position (Top and Left), size (Height and Width), colour, default font for adding text etc.
+
=== Deklaráció ===
  
 +
Ehhez vegyük alapul az automobilunkat; először is leírást kell készítenünk a mezőkről és a metódusokról:
  
 +
<delphi>
 +
type
 +
  TAutoMobil = class
  
----
+
    { Mezők: }
  
If a Button control is placed on the Form (type TButton), it will have its own series of properties, which can be examined in the Object Inspector window.  
+
    FSzin: String;                  { Az autó színe. }
 +
    FMaxSebesseg: Real;            { Maximális sebesség. }
 +
    FMaxUtasok: Integer;            { Utasok maximális száma. }
 +
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
 +
    FAktualisUtasok: Integer;      { A bent ülő utasok maximális száma. }
  
Several of the properties have names similar to those for the Form; this is because many properties are '''Inherited''' from some common Ancestor class, which lays out how properties are to be defined and handled by the descendant classes.
+
    { Metódusok: }
  
As well as properties, the Object Inspector offers a tab called '''Events''', which gives access to Event Handlers which are methods instructing the application how to deal with things such as a mouse click on a button (OnClick) or some change in the position, size or other properties (OnChange).
+
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer);
 +
    procedure Gyorsit;
 +
    procedure Lassit;
 +
  end;
 +
</delphi>
  
The physical image of the Button on the Form, together with all its properties and Event Handler methods, should be regarded as a single entity or '''Object''' in Pascal.
+
=== Implementáció ===
  
<div class="floatleft">[[Image:ObjectInspector-TButton.png]]</div>
+
Az előző példában jól látható a rekordhoz való hasonlóság, illetve az eltérés is abban, hogy itt már metódusok is lettek definiálva. A metódusokat és mezőket szintén a pont operátorral érhetjük el.
<div class="floatright">[[Image:FormWithButton.png]]</div>
+
Azonban ez a deklaráció még nem teljes! A metódusokat - akár a unit-oknál - ki kell fejtenünk a főprogram szekciójának megkezdése előtt:
<div class="floatright">[[Image:Source_FormWithButton1.png‎ ]]</div>
 
  
== Object-Oriented Extensions to standard Pascal ==
+
<delphi>
 +
{ ...deklarációs rész... }
  
The Pascal record structure is extended by defining an
+
constructor TAutoMobil.Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer);;
=== Object ===
+
begin
 +
  Self.FSzin := aSzin;
 +
  Self.FMaxSebesseg := aMaxSebesseg;
 +
  Self.FMaxUtasok:= aMaxUtasok;
 +
end;
  
An Object is a special kind of record. The record contains all the fields that
+
procedure TAutoMobil.Gyorsit;
are declared in the object's definition (just like a conventional record), but now procedures and functions can be declared as if they were part of the record and are held as pointers to the methods associated with the object's type. 
+
begin
 +
end;
  
For example, an object could contain an array of real values, together with a Method for calculating the average.
+
procedure TAutoMobil.Lassit;
 +
begin
 +
end;
  
Type
+
{ ...további programrészek... }
  Average = Object
+
</delphi>
    NumVal: Integer;
 
    Values: Array [1..200] of Real;
 
    Function Mean: Real; { calculates the average value of the array }
 
  End;
 
  
Objects can ”inherit” fields and methods from ”parent” objects. This means that these fields and methods can be used as if they were included in the objects declared as a ”child” object.
+
Vegyük észre a ''Self'' saját magára mutató objektumot. Ennek a haszna az, hogy az aktuális objektum egy mezőjére hivatkozhatunk vele. A létjogosultsága abban rejlik, hogy a metódusokon belül deklarálható létező mezővel megegyező azonosítójú változó, illetve függvény; így azonban megkülönböztethetjük őket.
  
Furthermore, a concept of visibility is introduced: fields, procedures and functions can be declared
+
=== Példányosítás (konstruktor) ===
as public, protected or private. By default, fields and methods are public, and can be exported outside the
 
current unit. Protected fields or methods are available only to objects descended from the current ancestor object. Fields or methods that are declared private are only accessible in the current unit:
 
their scope is limited to the implementation of the current unit.
 
  
=== Class ===
+
Ne felejtsük el, hogy a deklaráció és a kifejtés még csak egy leírás - tervnek is tekinthetjük - az autóról. Ez azt jelenti, hogy még le kell gyártani - példányosítani kell. Erre szolgál az eddig talán ismeretlen ''constructor'' (konstruktor). Mint látható, nincs hozzá definiálva visszatérési érték, mégis értékadásra használjuk. Ennek az az oka, hogy alapértelmezetten egy pointer-rel és csak azzal térhet vissza. A pointer arra az objektumra mutat, amelyet létrehoztunk a konstruktorral. Talán kicsit bonyolultan hangzik elsőre, de lássuk a példát:
Objects are not used very often by themselves in Free Pascal and Lazarus; instead, Classes are used very widely. A Class is defined in almost the same way as an Object, but is a pointer to an Object rather than the Object itself. Technically, this means that the Class is allocated on the Heap of a program, whereas the Object is allocated on the Stack.
+
 +
<delphi>
 +
var
 +
  Auto: TAutoMobil;
  
Here is a simple example of a typical Class declaration:
+
begin
<delphi>{-------------------------------------------}
+
   Auto := TAutoMobil.Create('Fekete', 160, 5);
{ Example of Class declaration from the LCL }
+
end;
{-------------------------------------------}
+
</delphi>
   TPen = class(TFPCustomPen)
 
  private
 
    FColor: TColor;
 
    FPenHandleCached: boolean;
 
    FReference: TWSPenReference;
 
    procedure FreeReference;
 
    function GetHandle: HPEN;
 
    function GetReference: TWSPenReference;
 
    procedure ReferenceNeeded;
 
    procedure SetHandle(const Value: HPEN);
 
  protected
 
    procedure DoAllocateResources; override;
 
    procedure DoDeAllocateResources; override;
 
    procedure DoCopyProps(From: TFPCanvasHelper); override;
 
    procedure SetColor
 
        (const NewColor: TColor; const NewFPColor: TFPColor); virtual;
 
    procedure SetFPColor(const AValue: TFPColor); override;
 
    procedure SetColor(Value: TColor);
 
    procedure SetMode(Value: TPenMode); override;
 
    procedure SetStyle(Value: TPenStyle); override;
 
    procedure SetWidth(value: Integer); override;
 
  public
 
    constructor Create; override;
 
    destructor Destroy; override;
 
    procedure Assign(Source: TPersistent); override;
 
    property Handle: HPEN read GetHandle write SetHandle; deprecated;
 
    property Reference: TWSPenReference read GetReference;
 
  published
 
    property Color: TColor read FColor write SetColor default clBlack;
 
    property Mode default pmCopy;
 
    property Style default psSolid;
 
    property Width default 1;
 
  end;</delphi>
 
  
 +
Ezzel az egy sorral létrehoztuk az autónkat, amely fekete színű, 160 km/h a végsebessége, és 5 utas fér el benne. Ezek után már beindíthatjuk, gyorsíthatunk, lassíthatunk vele, stb.:
  
Note that this class is defined as an instance of another '''parent''' or '''ancestor''' class (''TFPCustomPen'') from which it '''inherits''' all its properties and methods. It has some fields of its own, grouped under
+
<delphi>
*'''private''' - this means that items defined here are only available or visible to other classes or procedures/function defined within the same program unit (this example is from ''Graphics'', so any of the other classes such as ''TBitMap'', ''TPicture'' etc in the same unit can use them). They are essentially local variables (eg ''FColor'', ''FPenHandleCached'') or locally used methods (''GetHandle'', ''SetHandle'') but can be used or referred to in items declared in the '''protected''' or '''public''' sections.
+
var
*'''protected''' - this means that items defined here are only available or visible to classes that are descended from this '''ancestor''' class, and inherit its properties or methods
+
  Auto: TAutoMobil;
*'''public''' - this means that items defined here are available to any programming unit that includes the current unit in its [[Glossary#Uses | Uses]] clause
 
*'''published''' - is the same as a '''public''' section, but the compiler also generates type information that is needed for automatic streaming of these classes. Often the list of published items appear in the Object Inspector of Lazarus; if there is no '''published''' list, all the '''public''' fields usually appear in the Object Inspector.
 
  
=== Methods ===
+
begin
A method is just like a standard procedure or function, but can have some additional '''directives'''.
+
  Auto := TAutoMobil.Create('Fekete', 160, 5);
  
Some of the methods defined above are labelled with the directive '''virtual'''; others are labelled with the '''override''' directive.
+
  Auto.Gyorsitas;
*'''virtual''' means that the type or actual instance of a method is not known at compile-time, but is selected at run-time depending on what sub-program actually calls the method. It could be considered a place-holder in the definition of the class.
+
  Auto.Lassitas;
*'''override''' means that at run-time the locally given definition can take the place of a definition inherited from an ancestor class, particularly if it was '''virtual'''. If you particularly want to use the method defined in the ancestor class, it is sometimes necessary to call it specifically with the '''inherited''' clause.
+
end;
 +
</delphi>
  
Methods with no virtual or override directive are '''static''' methods (the usual kind in Pascal). Those with a virtual or override directive are '''dynamic'''.
+
=== Megsemmisítés (destruktor) ===
  
Special instances of methods are:
+
Mielőtt továbbmennénk, el kell fogadnunk, hogy az autó egy veszélyes hulladék. Ezért a szabályos megsemmisítésről gondoskodnunk kell. Ez a megsemmisítés szintén egy metódus, amely leírja, hogy mi a teendő megsemmisítés esetén. Ezt a metódust hívjuk destruktornak (''destructor''). A destruktort szintén deklarálni kell, majd pedig kifejteni. Ezzel kibővítve a deklarációnkat:
*'''create''' - a '''constructor''' for a class, which takes care of allocating memory, collecting together all the information needed and configuring/initializing the various properties.
 
*'''destroy''' - a '''destructor''' for a class, which removes all the parts of the class from the system in an orderly and logical way, and returns all its resources for the system to re-use.
 
  
=== Properties ===
+
<delphi>
 +
type
 +
  TAutoMobil = class
 +
 
 +
    { Mezők: }
 +
 
 +
    FSzin: String;                  { Az autó színe. }
 +
    FMaxSebesseg: Real;            { Maximális sebesség. }
 +
    FMaxUtasok: Integer;            { Utasok maximális száma. }
 +
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
 +
    FAktualisUtasok: Integer;      { A bent ülő utasok maximális száma. }
 +
 
 +
    { Metódusok: }
 +
 
 +
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer);
 +
    destructor Destroy;
 +
    procedure Gyorsit;
 +
    procedure Lassit;
 +
  end;
 +
 
 +
</delphi>
 +
 
 +
A destruktor programozástechnikai jelentősége abban rejlik, hogy az objektum által lefoglalt memóriaterületet felszabadítja. Ez a
 +
Pascal-nál (és C++-nál is) nagyon fontos, mivel nincs beépített szemétgyűjtő (mint pl. Java esetén), tehát a már nem szükséges objektumokat "kézzel" kell felszabadítanunk. Természetesen óvatosan kell eljárnunk, nehogy valahol még hivatkozzunk rá. A felszabadított objektumra való hivatkozás a program összeomlásával jár (SIGSEGV hiba), mivel nem létező objektumra mutatunk a hivatkozás során.
 +
 
 +
Ha idáig nem megy készség szinten, ne is folytasd az olvasást, inkább próbáld értelmezni az előzőeket! :)
 +
 
 +
=== Tulajdonságok (property-k) ===
  
Properties are just like ordinary fields in a conventional Pascal record, but they can have '''read''' and/or '''write''' specifiers.
+
Ha eddig minden érthető volt, akkor a dolgokat tovább kell bonyolítanunk azzal, hogy az autó, mint objektum, több egyéb objektumból áll - ezek például: motor, sebességváltó. Ha végig gondoljuk mindegyiknek vannak ugyanúgy mezői és metódusai. A motort, és a sebességváltót készen kapják az autógyárak, és beszerelik az autóba. Azonban ahhoz, hogy be tudják őket szerelni, helyet kell nekik biztosítani: mezőket kell létrehozni a deklarációban:
*'''read''' specifier is a field, or a function that returns a result of the correct type for the property. In the example above, the property ''Color'' has a read specifier ''FColor'', which is a local variable containing the value to be used. If a property has a '''read''' but no '''write''' specifier, it is read-only.
 
*'''write''' specifier is a field, or a procedure that will store the value of the property in a specific location. In the example above, ''Color'' has a write specifier ''SetColor'' that is a procedure (defined in the  '''protected''' section) for writing the color value to some specified location. If a property has a '''write''' but no '''read''' specifier, it is write-only.
 
*'''default''' - note that it is possible to set a '''default''' value for a property. For example, ''Color'' here is given the default value ''clBlack'', or black, at the time of creation. It could subsequently be given a different value, by a programming assignment statement, or in the Object Inspector.
 
*'''index''' specifier is an  integer that the '''read''' or '''write''' methods, shared between properties, can use to identify which property is desired. Note that if '''index''' is used the '''read''' or '''write''' specifiers must be a function or a procedure respectively and cannot be a normal field/variable.
 
  
====Examples====
 
 
<delphi>
 
<delphi>
  TFooClass = class
+
type
  private
+
  TAutoMobil = class
    FIntProp: Integer;
+
 
  public
+
    { Mezők: }
    property IntProp: Integer read FIntProp write FIntProp;
+
 
 +
    FSzin: String;                  { Az autó színe. }
 +
    FMaxSebesseg: Real;            { Maximális sebesség. }
 +
    FMaxUtasok: Integer;            { Utasok maximális száma. }
 +
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
 +
    FAktualisUtasok: Integer;       { A bent ülő utasok maximális száma. }
 +
    FMotor: TMotor;                { Az autóba szerelt motor. }
 +
    FSebessegValto: TSebessegValto; { Az autóba szerelt sebességváltó. }
 +
 
 +
    { Metódusok: }
 +
 
 +
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer; aMotor: TMotor; aSebessegValto: TSebessegValto);
 +
    destructor Destroy;
 +
    procedure Gyorsit;
 +
    procedure Lassit;
 +
  end;
 +
 
 +
</delphi>
 +
 
 +
Ez megjelenik a konstruktorban is paraméterként. Így már többféle modell legyártható pl. a motor megváltoztatásával. A sebességváltó vagy a motor cseréje szükséges lehet később is, de amit be szeretnénk szerelni, nem biztos, hogy kompatibilis! Ennek a kiküszöbölésére használhatunk függvényeket is, illetve a Pascal-ban egy elegáns megoldás a tulajdonságok (''property''-k) használata. Deklarálhatunk írható-olvasható, és csak olvasható tulajdonságokat is. Egy autó kapcsán szinte minden cserélhető egymással, de most kivételesen vegyük az alvázszámot nem megváltoztathatónak (mivel tilos megváltoztatni). Ezt a gyárban egyszer beütik, és onnantól a megsemmisítésig ugyanaz az érték marad. Ehhez a property, a read, és a write kulcsszót kell használnunk:
 +
 
 +
<delphi>
 +
type
 +
  TAutoMobil = class
 +
 
 +
    { Mezők: }
 +
 
 +
    FSzin: String;                  { Az autó színe. }
 +
    FMaxSebesseg: Real;            { Maximális sebesség. }
 +
    FMaxUtasok: Integer;            { Utasok maximális száma. }
 +
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
 +
    FAktualisUtasok: Integer;      { A bent ülő utasok maximális száma. }
 +
    FAlvazSzam: String;            { A gyárban beütött alvázszám. }
 +
    FMotor: TMotor;                { Az autóba szerelt motor. }
 +
    FSebessegValto: TSebessegValto; { Az autóba szerelt sebességváltó. }
 +
 
 +
    { Metódusok: }
 +
 
 +
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer; aMotor: TMotor; aSebessegValto: TSebessegValto);
 +
    destructor Destroy;
 +
    procedure Gyorsit;
 +
    procedure Lassit;
 +
    procedure SetMotor(aMotor: TMotor);
 +
    procedure SetSebessegValto(aSebessegValto: TSebessegValto);
 +
 
 +
    { Tulajdonságok: }
 +
 
 +
    property AlvazSzam: String read FAlvazSzam;            { Csak olvasható tulajdonság. }
 +
    property Motor: TMotor read FMotor write SetMotor;      { Olvasható és írható tulajdonság. }
 +
    property SebessegValto: TSebessegValto read FSebessegValto write SetSebessegValto;
 +
  end;
 +
</delphi>
 +
 
 +
Tovább gondolva a dolgokat, nézzük, mit is csinál pl. a SetSebessegValto fuggvény:
 +
 
 +
<delphi>
 +
{ ...deklarációs rész... }
 +
 
 +
procedure TAutoMobil.SetSebessegValto(aSebessegValto: TSebessegValto);
 +
begin
 +
  if (IsCompatibleSebessegValto(aSebessegValto) = True) then
 +
  begin
 +
    Self.FSebessegValto := aSebessegValto;
 
   end;
 
   end;
 +
  else Self.FSebessegValto := null;
 +
end;
 +
 +
procedure TAutoMobil.Lassit;
 +
begin
 +
end;
 +
 +
{ ...további programrészek... }
 
</delphi>
 
</delphi>
 +
 +
A példában használt ''IsCompatibleSebessegValto'' függvény valahol az osztályon kívül definiált. A tartalmával nem fontos a példa szempontjából foglalkozni. A lényeg, hogy megállapítja, hogy a beszerelni kívánt sebességváltó kompatibilis-e az autóval. Ha igen, akkor bekerül, ha nem, akkor nem kerül be sebességváltó (feltételezzük, hogy a régi sebességváltó már ki lett szerelve).
 +
 +
=== Absztrakt metódusok ===
 +
 +
Az automobilról mindenkinek ugyanaz a kép jut eszébe, azonban különböző gyártók különböző modelleket gyártanak, amelyek működésben is eltérhetnek egymástól.
 +
Úgyis fogalmazhatnánk, hogy attól még, hogy mi magunk is tudjuk, hogy nagyjából hogyan néz ki és működik egy autó, legyártani nem tudnánk. Tehát az automobilt egy absztrakt fogalomként kell értelmezni. Erre való az ''abstract'' kulcsszó használata:
  
 
<delphi>
 
<delphi>
  TFooClass = class
+
type
  private
+
  TAutoMobil = class
    function GetListProp(AIndex: Integer): String;
+
 
    property SetListProp(AIndex: Integer; AValue: String);
+
    { Mezők: }
  public
+
 
    // this type of property may not be in the published section of a class
+
    FSzin: String;                  { Az autó színe. }
    property ListProp[AIndex: Integer]: String read GetListProp write SetListProp;  
+
    FMaxSebesseg: Real;            { Maximális sebesség. }
  end;
+
    FMaxUtasok: Integer;            { Utasok maximális száma. }
 +
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
 +
    FAktualisUtasok: Integer;      { A bent ülő utasok maximális száma. }
 +
    FAlvazSzam: String;             { A gyárban beütött alvázszám. }
 +
    FMotor: TMotor;                { Az autóba szerelt motor. }
 +
    FSebessegValto: TSebessegValto; { Az autóba szerelt sebességváltó. }
 +
 
 +
    { Metódusok: }
 +
 
 +
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer; aMotor: TMotor; aSebessegValto: TSebessegValto); abstract;
 +
    destructor Destroy; abstract;
 +
    procedure Gyorsit; abstract;
 +
    procedure Lassit; abstract;
 +
    procedure SetMotor(aMotor: TMotor); abstract;
 +
    procedure SetSebessegValto(aSebessegValto: TSebessegValto); abstract;
 +
 
 +
    { Tulajdonságok: }
 +
 
 +
    property AlvazSzam: String read FAlvazSzam;            { Csak olvasható tulajdonság. }
 +
    property Motor: TMotor read FMotor write SetMotor;      { Olvasható és írható tulajdonság. }
 +
    property SebessegValto: TSebessegValto read FSebessegValto write SetSebessegValto;
 +
  end;
 
</delphi>
 
</delphi>
 +
 +
=== Öröklődés ===
 +
 +
A gyártók azonban a birtokában vannak az absztrakt fogalmak mögött rejlő tudásnak, ezért a saját elképzeléseik és tudásuk hozzáadásával egyedi automobilokat hoznak létre. Ezt nevezzük öröklődésnek (''inheritance''). Ennek megértéséhez be kell vezetnünk a szülő (''parent'') és gyerek (''child'') fogalmakat.
 +
Ez programozási szempontból a következőképpen néz ki, ha pl. a Mercedes gyárról beszélünk:
  
 
<delphi>
 
<delphi>
  TFooClass = class
+
type
  private
+
  TMercedesAutoMobil = class(TAutoMobil)
  function GetValue(const AIndex: Integer): Integer;
+
 
  procedure SetValue(const AIndex: Integer; AValue: Integer);
+
    FLegzsakokSzama: Integer;  
  public
 
    // note that the read and write methods are shared
 
    property Value1: Integer index 1 read GetValue write SetValue;
 
    property Value2: Integer index 2 read GetValue write SetValue;
 
    property Value3: Integer index 3 read GetValue write SetValue;
 
    property Value4: Integer index 4 read GetValue write SetValue;
 
    // index may be a const or a number or you can even use an enumerated type
 
    // for example:
 
    // property Value: Integer index ord(seSomeEnum) read SomeFunction write SomeProcedure;
 
  end;
 
 
</delphi>
 
</delphi>
  
 +
A ''class'' kulcsszó utáni zárójelben adjuk meg a szülő osztály nevét. Tehát a szülő osztály a TAutoMobil, a gyerek pedig a TMercedesAutoMobil. Ebben az esetben a Mercedes gyárnak kell meghatározni (programozási szempontból kifejteni) az ''abstract'' kulcsszóval ellátott metódusokat. Az előzőekben definiált mezőket és tulajdonságokat pedig örökli az autó, legyen az bármilyen gyártmány.
 +
Az absztrakt metódusok kifejtése mellett további mezők is kerülnek a gyerek objektumba, pl. a légzsákok száma - ami ugye nem feltétlenül része egy autónak (gondoljunk a régebbi modellekre).
 +
Ez az eljárás magával hozza azt a kérdést is, hogy mégis milyen tulajdonságokat, metódusokat örököljön majd a gyerek? Hogyan lehetne ezt definiálni? Erre valók a láthatóságot (''visibility'') definiáló kulcsszavak.
 +
 +
 +
Folytatás hamarosan...
 +
<!--
 
== Free Pascal Language Extensions ==
 
== Free Pascal Language Extensions ==
  

Revision as of 19:20, 21 May 2011

Template:Object Oriented Programming with FreePascal and Lazarus


Objektum-orientált programozás Free Pascal-lal és Lazarus-szal



Bevezetés

Rengeteg kitűnő tananyag található a Pascal-ról, de ennek a leírásnak a célja továbbvinni a kezdőket az objektum-orientált programozás világába, ami a szabványos Pascal kiegészítése. Ezt a kiegészítést a Turbo-Pascal, a Delphi és a Free Pascal / Lazarus tartalmazza.

Az Objektum a szabványos Pascal-ban definiált rekord struktúra kibővítése.

A hagyományos szöveges módú Pascal programozás megfelelő olyan programok készítésére - mint például a hagyományos Unix alkalmazások -, amelyek egy dolgot tudnak csinálni, de azt nagyon jól. Az a bizonyos "egy dolog", amit az alkalmazás csinál elég bonyolult lehet, és akár több menüből elérhető lehetőséget kínálhat a felhasználónak, de alapvetően csak parancsok kiadására szorítkozik, amelyeket a felhasználó kiad a billentyűzet segítségével, és a választ a monitoron vagy a nyomtatón kapja meg.

Ahhoz, hogy egy grafikus felhasználói felületet (GUI) hozzunk létre, általában szükséges bevonni a dolgokba valamilyen objektum-orientált programozási metódust (gyakran a C nyelv vagy valamelyik variánsának használatával, Visual Basic-kel, vagy valamelyik OO variánsával a Pascal-nak, úgymint a Free Pascal a Lazarus-szal vagy nélküle).

Egy GUI-ban a felhasználónak rendelkezésére áll egy képernyő, nagy mennyiségű szervezetten elrendezett képpel és ikonnal, amelyek eszközkészletekből, Widget-ekből állnak, amelyekhez különféle események vannak hozzárendelve, úgy mint

  • kiválasztás egy menüből,
  • fájlok megnyitása, vagy mentése,
  • csatlakozás az internethez,
  • numerikus műveletek végrehajtása, stb.

A felhasználó feladata mozgatni az egeret vagy kijelölő eszközt a képernyőn, hogy eseményeket válasszon ki, amelyek végrehajtódnak egérgomb-, vagy billentyű lenyomására.

Annak ellenére, hogy minden komplex grafikus felhasználói felülettel rendelkező rendszer megírható Pascal-ban, vagy bármely más nyelven, sokkal egyszerűbb objektum-orientált rendszert használni, ahol minden grafikus objektumnak a képernyőn lehetnek saját tulajdonságai és függvényei, így együtt egy összefüggő szerkezetet alkotva.

Már a tárgyalás elején le kell szögeznünk az osztály és az objektum közötti fontos logikai különbséget: az osztály egy leírás, amely alapján a fordító példányosítás segítségével létrehozza az objektumot.

Sajnos Pascal-ban ez az egyszerű logika csorbát szenved, mert object (objektum) és class (osztály) is deklarálható, illetve példányosítható. A kettő között a különbséget a futási időben elfoglalt memóriaterület helye jelenti: az osztály (class) egy objektumra mutató mutató (pointer). Ez azt jelenti, hogy az osztály a program HEAP területén helyezkedik el, míg az objektum a stack-en.

Ennek ellenére - mivel az object típust ritkán alkalmazzuk - nyugodtan használhatjuk az első logikai gondolatmenetet.

Objektumok - a való világ analógiája

Vegyük például a vérminta analógiáját, amit egy kórházban vagy az orvosi rendelőben gyűjtenek be.

Vérminta

A minta fizikálisan természetesen egy objektum; létezik - vele összefüggésben - rengeteg információ, dokumentum, és egyéb fizikai objektum.

  • Kémcső, olyan típusú, amelyet a teszt előír.
  • Helyi szabályozás (vagy metódus, szabványos művelet) amely a nővér vagy technikus munkáját irányítja
    • milyen kémcsövet használjon,
    • hogyan dolgozza fel a mintát
    • hogyan tárolja a laborba való szállításig.
  • Címke a kémcsövön a részletekkel:
    • minta azonosító
    • a páciens neve és születési dátuma
    • a mintavétel időpontja
    • a szükséges tesztek.
  • Igénylőlap, amivel a mintát a laborba küldik:
    • minta azonosító
    • az igénylő azonosítója
    • az igényelt vizsgálatok
    • bővebb információk a páciensről
    • a lehetséges diagnózis, amelynek a megerősítését kérik.

Az igénylőlap másolatát elhelyezik a páciens kartonjában, hogy emlékeztesse az orvost arra, hogy adott időn belül eredményeket fog kapni.

  • A laborban - helyi metódusok, amelyek meghatározzák:
    • hogyan kell a mintát analizálni,
    • milyen eszközöket kell használni,
    • hogyan kell az eszközöket kalibrálni, és működtetni,
    • milyen formában kell tárolni az eredményeket, és
    • hogyan kell visszajelenteni az orvosnak.

A tényleges eredmény egy rekord, amelyet az orvos a diagnózis felállításához használ, továbbá egy másolat kerül elhelyezésre a páciens kartonjában.

A fizikai minta megmaradhat referenciának, igazolásnak vagy további tesztekhez, vagy megsemmisítik valamilyen módon; ehhez egy további metódus szükséges, ami ennek a módját meghatározza.

Nincs szükség orvosra, hogy kitalálja az összes részletet és utasítást minden alkalommal, amikor egy mintát begyűjtenek; sőt, lehet, hogy alig van tudomása arról, hogy hogyan értékelnek ki egy mintát a laborban. A különféle eljárások öröklődnek az előző mintavételekből és analízisekből - így lesz egy általános terv az egész folyamatra - és így együtt, az összes dokumentummal, adattal, és a hozzájuk tartozó metódusokkal a vérmintát, egy komplex objektumként tekinthetjük.

Az orvos képzeletében, a vérminta többnyire ugyanannak az entitásnak jelenik meg, mint az eredményei. A nővéreknek és technikusoknak pedig a minta, a kémcső, a címke, és a tárolási feltételek szintén egy entitást jelentenek.

Egy másik példa - az automobil

Ha nem szereted a vért, hasonló okfejtés használható egy autóra is. Ez a példa talán valamivel közérthetőbb. Az autónak a fizikai megjelenésén kívül, egyéb technikai paraméterei is vannak - ezek lesznek az autó objektumunk mezői. Az autónak működnie is kell valamilyen előre meghatározott leírás szerint. A működés több részfolyamatból áll, ezeket a részfolyamatokat nevezzük metódusoknak.

Összefoglalva:

Az autó mezői:

  • szín
  • maximális sebesség
  • sebességfokozatok száma
  • ajtók száma
  • utasok maximális száma
  • az éppen bent ülő utasok száma
  • aktuális sebesség

Az autó metódusai:

  • motor indítás
  • gyorsítás
  • lassítás
  • sebességváltás

Programozási példa

Az elméleti fejtegetés után térjünk rá arra a kérdésre, hogy hogy néz ki programozási szempontból egy objektum! Ez a szekció ugyan fel van osztva részekre, de érdemesebb egyben olvasni a teljes kép megalkotásához.

Deklaráció

Ehhez vegyük alapul az automobilunkat; először is leírást kell készítenünk a mezőkről és a metódusokról:

<delphi>

type
  TAutoMobil = class
    { Mezők: }
    FSzin: String;                  { Az autó színe. }
    FMaxSebesseg: Real;             { Maximális sebesség. }
    FMaxUtasok: Integer;            { Utasok maximális száma. }
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
    FAktualisUtasok: Integer;       { A bent ülő utasok maximális száma. }
    { Metódusok: }
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer);
    procedure Gyorsit;
    procedure Lassit;
  end;

</delphi>

Implementáció

Az előző példában jól látható a rekordhoz való hasonlóság, illetve az eltérés is abban, hogy itt már metódusok is lettek definiálva. A metódusokat és mezőket szintén a pont operátorral érhetjük el. Azonban ez a deklaráció még nem teljes! A metódusokat - akár a unit-oknál - ki kell fejtenünk a főprogram szekciójának megkezdése előtt:

<delphi> { ...deklarációs rész... }

constructor TAutoMobil.Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer);; begin

 Self.FSzin := aSzin;
 Self.FMaxSebesseg := aMaxSebesseg;
 Self.FMaxUtasok:= aMaxUtasok;

end;

procedure TAutoMobil.Gyorsit; begin end;

procedure TAutoMobil.Lassit; begin end;

{ ...további programrészek... } </delphi>

Vegyük észre a Self saját magára mutató objektumot. Ennek a haszna az, hogy az aktuális objektum egy mezőjére hivatkozhatunk vele. A létjogosultsága abban rejlik, hogy a metódusokon belül deklarálható létező mezővel megegyező azonosítójú változó, illetve függvény; így azonban megkülönböztethetjük őket.

Példányosítás (konstruktor)

Ne felejtsük el, hogy a deklaráció és a kifejtés még csak egy leírás - tervnek is tekinthetjük - az autóról. Ez azt jelenti, hogy még le kell gyártani - példányosítani kell. Erre szolgál az eddig talán ismeretlen constructor (konstruktor). Mint látható, nincs hozzá definiálva visszatérési érték, mégis értékadásra használjuk. Ennek az az oka, hogy alapértelmezetten egy pointer-rel és csak azzal térhet vissza. A pointer arra az objektumra mutat, amelyet létrehoztunk a konstruktorral. Talán kicsit bonyolultan hangzik elsőre, de lássuk a példát:

<delphi> var

 Auto: TAutoMobil;

begin

 Auto := TAutoMobil.Create('Fekete', 160, 5);

end; </delphi>

Ezzel az egy sorral létrehoztuk az autónkat, amely fekete színű, 160 km/h a végsebessége, és 5 utas fér el benne. Ezek után már beindíthatjuk, gyorsíthatunk, lassíthatunk vele, stb.:

<delphi> var

 Auto: TAutoMobil;

begin

 Auto := TAutoMobil.Create('Fekete', 160, 5);
 Auto.Gyorsitas;
 Auto.Lassitas;

end; </delphi>

Megsemmisítés (destruktor)

Mielőtt továbbmennénk, el kell fogadnunk, hogy az autó egy veszélyes hulladék. Ezért a szabályos megsemmisítésről gondoskodnunk kell. Ez a megsemmisítés szintén egy metódus, amely leírja, hogy mi a teendő megsemmisítés esetén. Ezt a metódust hívjuk destruktornak (destructor). A destruktort szintén deklarálni kell, majd pedig kifejteni. Ezzel kibővítve a deklarációnkat:

<delphi>

type
  TAutoMobil = class
    { Mezők: }
    FSzin: String;                  { Az autó színe. }
    FMaxSebesseg: Real;             { Maximális sebesség. }
    FMaxUtasok: Integer;            { Utasok maximális száma. }
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
    FAktualisUtasok: Integer;       { A bent ülő utasok maximális száma. }
    { Metódusok: }
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer);
    destructor Destroy;
    procedure Gyorsit;
    procedure Lassit;
  end;

</delphi>

A destruktor programozástechnikai jelentősége abban rejlik, hogy az objektum által lefoglalt memóriaterületet felszabadítja. Ez a Pascal-nál (és C++-nál is) nagyon fontos, mivel nincs beépített szemétgyűjtő (mint pl. Java esetén), tehát a már nem szükséges objektumokat "kézzel" kell felszabadítanunk. Természetesen óvatosan kell eljárnunk, nehogy valahol még hivatkozzunk rá. A felszabadított objektumra való hivatkozás a program összeomlásával jár (SIGSEGV hiba), mivel nem létező objektumra mutatunk a hivatkozás során.

Ha idáig nem megy készség szinten, ne is folytasd az olvasást, inkább próbáld értelmezni az előzőeket! :)

Tulajdonságok (property-k)

Ha eddig minden érthető volt, akkor a dolgokat tovább kell bonyolítanunk azzal, hogy az autó, mint objektum, több egyéb objektumból áll - ezek például: motor, sebességváltó. Ha végig gondoljuk mindegyiknek vannak ugyanúgy mezői és metódusai. A motort, és a sebességváltót készen kapják az autógyárak, és beszerelik az autóba. Azonban ahhoz, hogy be tudják őket szerelni, helyet kell nekik biztosítani: mezőket kell létrehozni a deklarációban:

<delphi>

type
  TAutoMobil = class
    { Mezők: }
    FSzin: String;                  { Az autó színe. }
    FMaxSebesseg: Real;             { Maximális sebesség. }
    FMaxUtasok: Integer;            { Utasok maximális száma. }
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
    FAktualisUtasok: Integer;       { A bent ülő utasok maximális száma. }
    FMotor: TMotor;                 { Az autóba szerelt motor. }
    FSebessegValto: TSebessegValto; { Az autóba szerelt sebességváltó. }
    { Metódusok: }
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer; aMotor: TMotor; aSebessegValto: TSebessegValto);
    destructor Destroy;
    procedure Gyorsit;
    procedure Lassit;
  end;

</delphi>

Ez megjelenik a konstruktorban is paraméterként. Így már többféle modell legyártható pl. a motor megváltoztatásával. A sebességváltó vagy a motor cseréje szükséges lehet később is, de amit be szeretnénk szerelni, nem biztos, hogy kompatibilis! Ennek a kiküszöbölésére használhatunk függvényeket is, illetve a Pascal-ban egy elegáns megoldás a tulajdonságok (property-k) használata. Deklarálhatunk írható-olvasható, és csak olvasható tulajdonságokat is. Egy autó kapcsán szinte minden cserélhető egymással, de most kivételesen vegyük az alvázszámot nem megváltoztathatónak (mivel tilos megváltoztatni). Ezt a gyárban egyszer beütik, és onnantól a megsemmisítésig ugyanaz az érték marad. Ehhez a property, a read, és a write kulcsszót kell használnunk:

<delphi>

type
  TAutoMobil = class
    { Mezők: }
    FSzin: String;                  { Az autó színe. }
    FMaxSebesseg: Real;             { Maximális sebesség. }
    FMaxUtasok: Integer;            { Utasok maximális száma. }
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
    FAktualisUtasok: Integer;       { A bent ülő utasok maximális száma. }
    FAlvazSzam: String;             { A gyárban beütött alvázszám. }
    FMotor: TMotor;                 { Az autóba szerelt motor. }
    FSebessegValto: TSebessegValto; { Az autóba szerelt sebességváltó. }
    { Metódusok: }
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer; aMotor: TMotor; aSebessegValto: TSebessegValto);
    destructor Destroy;
    procedure Gyorsit;
    procedure Lassit;
    procedure SetMotor(aMotor: TMotor);
    procedure SetSebessegValto(aSebessegValto: TSebessegValto);
    { Tulajdonságok: }
    property AlvazSzam: String read FAlvazSzam;             { Csak olvasható tulajdonság. }
    property Motor: TMotor read FMotor write SetMotor;      { Olvasható és írható tulajdonság. }
    property SebessegValto: TSebessegValto read FSebessegValto write SetSebessegValto;
  end;

</delphi>

Tovább gondolva a dolgokat, nézzük, mit is csinál pl. a SetSebessegValto fuggvény:

<delphi> { ...deklarációs rész... }

procedure TAutoMobil.SetSebessegValto(aSebessegValto: TSebessegValto); begin

 if (IsCompatibleSebessegValto(aSebessegValto) = True) then
 begin
   Self.FSebessegValto := aSebessegValto;
 end;
 else Self.FSebessegValto := null;

end;

procedure TAutoMobil.Lassit; begin end;

{ ...további programrészek... } </delphi>

A példában használt IsCompatibleSebessegValto függvény valahol az osztályon kívül definiált. A tartalmával nem fontos a példa szempontjából foglalkozni. A lényeg, hogy megállapítja, hogy a beszerelni kívánt sebességváltó kompatibilis-e az autóval. Ha igen, akkor bekerül, ha nem, akkor nem kerül be sebességváltó (feltételezzük, hogy a régi sebességváltó már ki lett szerelve).

Absztrakt metódusok

Az automobilról mindenkinek ugyanaz a kép jut eszébe, azonban különböző gyártók különböző modelleket gyártanak, amelyek működésben is eltérhetnek egymástól. Úgyis fogalmazhatnánk, hogy attól még, hogy mi magunk is tudjuk, hogy nagyjából hogyan néz ki és működik egy autó, legyártani nem tudnánk. Tehát az automobilt egy absztrakt fogalomként kell értelmezni. Erre való az abstract kulcsszó használata:

<delphi>

type
  TAutoMobil = class
    { Mezők: }
    FSzin: String;                  { Az autó színe. }
    FMaxSebesseg: Real;             { Maximális sebesség. }
    FMaxUtasok: Integer;            { Utasok maximális száma. }
    FAktualisSebesseg: Real;        { Az aktuális sebesség. }
    FAktualisUtasok: Integer;       { A bent ülő utasok maximális száma. }
    FAlvazSzam: String;             { A gyárban beütött alvázszám. }
    FMotor: TMotor;                 { Az autóba szerelt motor. }
    FSebessegValto: TSebessegValto; { Az autóba szerelt sebességváltó. }
    { Metódusok: }
    constructor Create(aSzin: String; aMaxSebesseg: Real; aMaxUtasok: Integer; aMotor: TMotor; aSebessegValto: TSebessegValto); abstract;
    destructor Destroy; abstract;
    procedure Gyorsit; abstract;
    procedure Lassit; abstract;
    procedure SetMotor(aMotor: TMotor); abstract;
    procedure SetSebessegValto(aSebessegValto: TSebessegValto); abstract;
    { Tulajdonságok: }
    property AlvazSzam: String read FAlvazSzam;             { Csak olvasható tulajdonság. }
    property Motor: TMotor read FMotor write SetMotor;      { Olvasható és írható tulajdonság. }
    property SebessegValto: TSebessegValto read FSebessegValto write SetSebessegValto;
  end;

</delphi>

Öröklődés

A gyártók azonban a birtokában vannak az absztrakt fogalmak mögött rejlő tudásnak, ezért a saját elképzeléseik és tudásuk hozzáadásával egyedi automobilokat hoznak létre. Ezt nevezzük öröklődésnek (inheritance). Ennek megértéséhez be kell vezetnünk a szülő (parent) és gyerek (child) fogalmakat. Ez programozási szempontból a következőképpen néz ki, ha pl. a Mercedes gyárról beszélünk:

<delphi>

type
  TMercedesAutoMobil = class(TAutoMobil)
    FLegzsakokSzama: Integer; 

</delphi>

A class kulcsszó utáni zárójelben adjuk meg a szülő osztály nevét. Tehát a szülő osztály a TAutoMobil, a gyerek pedig a TMercedesAutoMobil. Ebben az esetben a Mercedes gyárnak kell meghatározni (programozási szempontból kifejteni) az abstract kulcsszóval ellátott metódusokat. Az előzőekben definiált mezőket és tulajdonságokat pedig örökli az autó, legyen az bármilyen gyártmány. Az absztrakt metódusok kifejtése mellett további mezők is kerülnek a gyerek objektumba, pl. a légzsákok száma - ami ugye nem feltétlenül része egy autónak (gondoljunk a régebbi modellekre). Ez az eljárás magával hozza azt a kérdést is, hogy mégis milyen tulajdonságokat, metódusokat örököljön majd a gyerek? Hogyan lehetne ezt definiálni? Erre valók a láthatóságot (visibility) definiáló kulcsszavak.


Folytatás hamarosan...