Mere OOP

Tags:    delphi
Skrevet af Bruger #58 @ 16.06.2001
OOP - hvordan og hvorfor, del 2

I denne opfølger til "OOP - hvordan og hvorfor" skal vi se på avancerede objektrelationer og samspil mellem flere objekter. Jeg vil også dække hvordan du kan lave din egen event. Jeg forventer at du har læst første del af denne artikel, da jeg vil bygge videre på eksemplet fra den.

TLandkort og hvad dertil hører

Som beskrevet i første del af artiklen skal TLastvogn have en metode der hedder KørTil. KørTil skal have en parameter: Destination. Destination kan være af typen TDestination, fordi der er flere slags destinationer med forskellige egenskaber. Her er TDestination:
type
  TDestination = class(TCollectionItem)
  private
    FVejVæk: TRuter;
  public
    property VejeVæk: TRuter
     read  FVejVæk
     write FVejVæk;
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  end;
Som man kan se, så nedarver TDestination fra TCollectionItem. Et landkort kan man opfatte som en samling destinationer, derfor er deklarationen af TLandkort sådan her:
type
  TLandkort = class(TCollection);
TDestination har også fået nye constructor og destructor metoder. De skal se sådan ud:
constructor TDestination.Create(Collection: TCollection);
begin
  inherited;
  FVejeVæk := TRuter.Create(TRute);
end;

destructor TDestination.Destroy;
begin
  FVejeVæk.Free;
  inherited;
end;
Nu er der TRute og TRuter tilbage. De skal se sådan ud:
type
  TRuter = class(TCollection);
  TRute = class(TCollectionItem)
  private
    FDestinationer: array[1..2] of TDestination;
    function GetLængde: extended;
  public
    property Destinaitoner[Index: integer]: TDestination
     read  FDestinationer
     write FDestinationer;
    property Længde: extended
     read  GetLængde;
  end;
Læg mærke til at Længde er read-only og bruger en funktion til at hente sin værdi. Dvs. at vi må have data om hvor hver destination ligger. Et nyt property til TDestination: (Her har jeg ikke taget de gamle dele med.)
type
  TDestination = class(TCollectionItem)
  private
    FPosition: TPoint;
  public
    property Position: TPoint
     read  FPosition
     write FPosition;
  end;
Nu kan vi skrive en GetLængde metode til TRute. Den udregner længden mellem de to destinationer i fugleflugtslinie:
function TRute.GetLængde: extended;
var
  P1, P2: TPoint;
begin
  P1 := Destinationer[1].Position;
  P2 := Destinationer[2].Position;
  Result := Sqrt(Sqr(Abs(P1.x - P2.x)) + Sqr(Abs(P1.y - P2.y)));
end;
Nu skulle det være muligt at få TLastvogn til at køre.

KørTil

For at gøre det simpelt: KørTil tager en destination og ser om den kan komme dertil fra hvor den er nu. Den returnerer hvor lang tid det tager at køre ruten i minutter. Der er en chance for at der sker et trafikuheld hver gang der køres. Man kunne eventuelt kode metoden sådan, at chancen for trafikuheld er større når det er sent på aftenen, men det er der ingen grund til at komme ind på her. Her er metoden med et par kommentarer i:
function TLastvogn.KørTil(ADestination: TDestination): cardinal;
var
  x: integer;
  Rute: TRute;
begin
  // For at tilfredsstille compileren
  Result := 0;
  // Først, tjek om vi allerede er der hvor vi skal hen
  // Position er et nyt property i TLastvogn. Det indeholder lastvognens
  // aktuelle position
  if ADestination = Position then
  begin
    // Det taget nul minutter at komme fra her til samme sted
    Result := 0;
    Exit;
  end;
  // Se om vi kan komme fra aktuelle position til destinaiton
  // Gøres ved at loope gennem alle ruter fra aktuelle position
  for x := 0 to Position.VejeVæk.Count-1 do
  begin
    Rute := TRute(Position.VejeVæk.Items[x]);
    if (ADestination = Rute.Destination[1]) or
      (ADestination = Rute.Destination[2]) then
      Break;
  end;
  // Ekstra sikkerhedstjek
  if ((ADestination = Rute.Destination[1]) and
    (Position = Rute.Destination[2])) or
    ((Position = Rute.Destination[1]) and
    (ADestination = Rute.Destination[2])) then
  begin
    // Vi kører med konstant fart: 80 km/t
    // Forudsætter at alle mål er i km
    // Udregn den tid det tager at køre i minutter
    Result := Round(Rute.Længde * 60 / 80);
    // Chancen for trafikuheld er 2%
    if Random <= 0.02 then
      if Assigned(FOnTrafikuheld) then
      begin
        FOnTrafikuheld(Self, Rute);
      end
    else
      // Vi når kun vores nye destination hvis vi ikke kommer ud for et uheld
      FPosition = ADestination;
  end;
end;

Den ny TLastvogn og anden abstrakt snak

Der kom lige 2 nye ting til TLastvogn klassen. Et property, Position, og en event, OnTrafikuheld. Position er bare af typen TDestination. OnTrafikuheld er lidt mere tricky at lave.
type
  // En event skal altid deklareres som en type
  TTrafikheldEvent = procedure(Sender: TObject; ARute: TRute) of object;
  TTransportmiddel = class(TObject);
  public
    procedure BevægTil(ADestination: TDestination); virtual; abstract;
  end;
  TLastvogn = class(TTransportmiddel)
  private
    FAntalKasser: cardinal;
    FKasseVægt: cardinal;
    FPosition: TDestination;
    FOnTrafikuheld: TTrafikuheldEvent;
    function GetVægt: cardinal;
  public
    property AntalKasser: cardinal
     read  FAntalKasser
     write FAntalKasser;
    property KasseVægt: cardinal
     read  FKasseVægt
     write FKasseVægt;
    property Vægt: cardinal
     read  GetVægt;
    property Position: TDestination
     read  FPosition;
    property OnTrafikuheld: TTrafikuheldEvent
     read  FOnTrafikuheld
     write FOnTrafikuheld;
    procedure BevægTil(ADestination: TDestination); override;
  end;

Her er det vigtigt at lægge mærke til TTransportmiddel klassen og den metode den indeholder. Kan du se det lille ord "abstract" til sidst på linien? Det betyder at vi ikke behøver at implementere metoden BevægTil for TTransportmiddel klassen. Faktisk må vi ikke. Men det betyder at alle transportmidler kan behandles ens. (Alle kan bedes om at bevæge sig til et andet sted, uden at man er klar over hvilken type der er tale om.) Jeg har også omdøbt KørTil af logiske grunde. Det er sjældent at man ser en jumbojet køre fra by til by.
Sidste destination

TDestination er en meget god klasse, men den kan godt forbedres. Hvad men nogen mere specifikke destinationer? Som TStorby og TVarelager? Intet problem, bare lav en ny klasse:
type
  TStorby = class(TDestination)
  private
    FIndbyggere: cardinal;
  public
    property Indbyggere: cardinal
     read  FIndbyggere
     write FIndbyggere;
  end;
  TVarelager = class(TDestination)
  private
    FArbejdere: cardinal;
    FKasser: cardinal;
  public
    property Arbejdere: cardinal
     read  FArbejdere
     write FArbejdere;
    property Kasser: cardinal
     read  FKasser
     write FKasser;
  end;
Man kunne også give TStorby en metode, ModtagVarer, hvor der bliver losset så mange kasser fra en TLastvogn som byen nu kan bruge, udregnet fra indbyggertal. Og sådan kunne man fortsætte. Disse klasser kunne måske bruges til et spil ala Transport Tycoon, eller måske et system til en transport-virksomhed.
Det var så slut for denne gang. Hvis der er noget du ikke lige forstår, så kan du slå op i hjælpen, og hvis det stadig ikke hjælper, så er der sikkert en anden der vil hjælpe dig. Spørg i Delphi forummet.


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (0)

Du skal være logget ind for at skrive en kommentar.
t