Detta dokument förutsätter Netscape 4 eller Internet Explorer 4, om du har tidigare versioner klicka här.

Innehåll, föreläsning 1 (Introduktion till objektorienterad programmering)

Föreläsning 1: Introduktion till objektorienterad programmering

Dynamiska web-sidor: applets

Vid konstruktion av websidor vill man ofta åstadkomma rörliga delar och/eller ge surfaren en chans att påverka sidinnehållet (gäller t.ex. spel). HTML i sig själv ger ingen eller mycket litet möjlighet att skapa dynamiska Web-sidor. Ett sätt att åstadkomma sådana är att skriva s.k. applets, program i programspråket Java som sedan kan exekveras av nätbläddrare (som t.ex. Netscape eller Internet Explorer) som "kan" Java.
import java.applet.*;
import java.awt.*;

public class Welcome extends Applet 
{
    
    public void paint( Graphics g)
    {
	g.drawString("Welcome to the World of Internet Programming", 100, 100);
    }

};
Detta program sparas i filen Welcome.java (observera att filnamnet måste vara identiskt med klassnamnet) som kompileras med kommandot
javac Welcome.java
varvid en fil (med suffixet .class) i s.k. bytekod skapas. Denna fil anropas sedan med hjälp av märkordet (eng. tag) APPLET från en web-sida:
<APPLET CODE="Welcome.class" WIDTH=500 HEIGHT=120>
</APPLET>
Man kan titta på (och debugga) sin applet på två sätt, endera med kommandot
appletviewer 
eller i en nätbläddrare. Detta blir resultatet:

Nu finns det inte mycket dynamik i texten men Java är speciellt skapat för och därför mycket lämpligt för att generera grafik och användargränssnitt. Mer om det senare.

Det som händer när en nätbläddrare hittar ordet APPLET är att Java-programmet (eller snarare den bytekod som genereras av javac) laddas ned till klientmaskinen, d.v.s. den maskin som surfaren sitter vid. Detta kan verka farligt men en applet har mycket begränsade befogenheter och kan inte komma åt klientdatorn (surfarens dator) eller dess disk.

Java är ett objektorienterat programspråk. Vad detta innebär beskrivs i det följande.

Något om objektorienterad analys och design

Att programmera innebär att lösa ett problem, t.ex. att hantera ett biblioteks böcker eller att visa en figur i en applet. Men varför skall man använda ett objektorienterat angreppssätt? Att svara kortfattat på detta är inte lätt men ett försök skulle kunna vara att den traditionella tekniken - funktions- eller algoritmbaserad - leder till svåröverblickbara, svårutbyggda program.

Som vi har sett är Java ett objektorienterat språk. Vad innebär detta för själva processen "att skapa ett program"? Svaret är att eftersom OOP ser program som en samling av objekt (inte som en samling algoritmer) blir det första steget att bestämma dessa objekt. Detta görs genom att analysera problemet, egentligen utan att man tänker på att programmera i meningen "knappa in kod" eller ens vilket programspråk man skall använda, man beskriver problemet, inte hur man skall lösa det. Nästa steg blir att tänka sig hur programmet kommer att se ut i grova drag (design-fasen), sedan kommer implementationen som bör vara relativt enkel och rättfram om analys och design har gjorts bra, varefter testning av programmet tar vid.

Sammanfattningsvis: Objektorientering är att arbeta med modeller och att ställa problemområdet i centrum. I ett objektorienterat synsätt delas programmering in i fyra faser: analys, design, implementation och testning. Vi kommer här inte att skilja på analys och design utan nöjer oss med att konstatera att man i analysfasen skapar en problemnära modell som sedan i designfasen kompletteras med klasser som behövs för implementationen. Man bör hålla isär de två men i praktiken växlar man ofta mellan nivåerna: en analys följs av en design som ger upphov till förändringar i analysen, etc.

Begrepp och notation i analys och design

Syftet med anlysen är att skapa en modell som visar de viktiga sambanden i ett oftast komplext problem. Det finns flera snarlika metoder för att skapa denna modell. Här kommer vi att utnyttja notationen från en av dessa: Object Modeling Technique (OMT) som delvis ligger till grund för den metod som troligen blir standardnotation i framtiden: Unified Modeling Language (UML).

De viktigaste begreppen i OOP har grafiska motsvarigheter i OMT-notationen, t.ex. rektanglar för klasser och små trianglar för arv. En del av analysfasen går ut på att skapa en objektmodell med hjälp av dessa.

Klass

En klass är en beskrivning (en mall) av någonting. När man skapar ett objekt säger man att klassen instansieras, man skapar en instans. Man kan t.ex. tänka sig klassen Blomma med olika instanser tulpan, ros o.s.v. En rosodlare skulle kanske snarare se klassen Ros som instansieras med olika sorters rosor. Vilka klasser man behöver beror helt på vilket problem man skall lösa.

Man kan se en klass som ett record eller en datapost med operationer, d.v.s. funktioner som kan påverka klassen eller besvara frågor om den. Funktionerna kallas metoder och klassens data kallas dess attribut. En viktig del av OOP är att inga andra än klassens metoder kan ändra på data i klassens instanser.

En klass ritas som en rektangel (se figuren till vänster nedan).

Attribut och metoder

Man kan också rita en klass mer fullständigt, med attribut (datafält) och metoder (de operationer klassens instanser kan utföra) (se figuren till höger). Som synes kan attribut och operationer beskrivas mer eller mindre detaljrikt.

Arv

I OOP används ordet arv för att beskriva ett är en-förhållande. Att en klass B ärver en annan klass, A, betyder alltså att B är ett slags A och att allt som A kan, kan också B. Man säger att A är superklass till B som är subklass till A. Exempelvis kan man tänka sig en klass Fordon som superklass till Bil, Cykel, etc. Flera nivåer av arv kan förekomma: Fordon, Motorfordon och Bil t.ex.

I det inledande exempelprogrammet är Applet en klass som finns i Java och Welcome är en sorts Applet.

Arv visas med en triangel med den toppiga sidan uppåt mot superklassen:

Här har vi en superklass (kallas ibland basklass) som innehåller det som är gemensamt för de båda subklasserna, både attribut och metoder.

Det kan finnas klasser som inte är möjliga att instansiera, s.k. abstrakta klasser. En sådan klass tjänar endast som superklass till andra klasser och kan innehålla abstrakta metoder, d.v.s. metoder som inte implementeras i klassen men i de subklasser som kan instansieras (senare i detta avsnitt visar ett exempel på en abstrakt klass).

Association

Ordet association används för att beskriva att två klasser har någon sorts relation. Det kan vara t.ex. lärare-student, bil-bilägare, kurs-tentamen. Relationerna kan vara sådana att varje instans av en klass hör samman med exakt en instans av en annan klass. En instans kan också har en relation till många instanser av en annan klass.

En association skall vara en fast relation, inte tillfällig. Tillfälliga relationer kan ofta bättre beskrivas som operationer (metoder).

En linje utan siffra eller ring betyder att multipliciteten är exakt 1, en fylld ring betyder många (noll eller fler), en ofylld ring betyder en eller ingen och en eventuell siffra anger alla tillåtna multiplicitetsvärden.

I figuren är associationen mellan Klass_1 och Klass_4 sådan att till varje Klass_1 skall en eller flera Klass_4 vara associerade. Varje instans av Klass_5 är associerad till 1,2,3,4 eller 8 instanser av Klass_2. Varje instans av Klass_3 är associerad till en eller ingen Klass_6, medan varje instans av Klass_6 har många Klass_3-objekt knutna till sig. Klass_3 skulle t.ex. kunna vara personer och Klass_6 yrke (om man nu antar att ingen i dessa arbetslöshetstider är ofin nog att ha flera jobb).

I OMT-notationen används olika sorters cirklar för att poängtera hur många instanser av en klass som kan relateras till en klass. Cirklar kan finnas i båda ändar av relationen. Om man, vilket man kan göra, anger multipliciteten med siffror är cirklarna strängt taget onödiga. I UML har man också tagit bort dem och anger 0.. i stället för fylld cirkel, 0-1 i stället för tom cirkel, o.s.v.

Aggregat

En klass som innehåller instanser av andra klasser som attribut brukar kallas ett aggregat av klasser. Aggregat kan ses som ett specialfall av association men brukar ritas med en speciell aggregatmarkör:

Som framgår av figuren finns det alternativa sätt att visa aggregatsrelationer.

Relationer

För att i figurerna visa vilken relation två objekt har till varandra brukar man använda vanligt språk, t.ex. äger, består av, pratar med. Relationerna skrivs vid de linjer som binder ihop klasserna.

Några ord om metodik i analys och design

I de flesta analys- och designmetoder ingår följande steg: Vissa tekniker har speciella notationer för delar av detta, t.ex. finns i OMT något som kallas den dynamiska modellen som beskriver de tillstånd olika objekt kan befinna sig i. I OMT finns också en funktionell modell som beskriver dataflöden mellan objekt. Båda dessa modeller kan ses som hjälpmedel för de två avslutande punkterna ovan.

Steg ett i analysfasen är att leta efter lämpliga objekt t.ex. genom att göra en lista med alla substantiv i problembeskrivningen (den traditionella algoritmbaserade programmeringstekniken utgår från verben). Först kan man tillåta sig att vara okritisk och ta med "allting", men innan man är klar måste man rensa bort ovidkommande saker och kanske göra de enklaste klasserna till attribut. Regeln är ungefär att de presumtiva klasser som inte behövs utanför en annan klass kan ses som attribut till denna andra klass.

Modellen byggs sedan ut genom att man letar efter associationer och arv. Exakt hur man går till väga är svårt att beskriva i ord. Men det är viktigt att man verkligen hittar arvsrelationer (tänk efter om en klass B är en sorts A) och associationer. Eftersom allting i Java är klasser (det finns inga "vanliga" funktioner) blir det ju inget program om man inte lyckas få sina klasser att hänga ihop.

Sista delen i analysfasen är att förfina sin design genom att lägga till operationer på klasserna. Här, liksom tidigare, är scenarier ett hjälpmedel

Att skapa en objektmodell är oftast ett iterativt arbete, d.v.s ett senare steg kan påverka tidigare modellen som då måste anpassas.

Ett litet exempel

Ett litet exempel får avsluta detta avsnitt. Tanken är att visa, dels fördelarna med objektorientering jämfört med funktionsbaserad programmering, dels också hur en liten objektmodell kan te sig grafiskt. Bli inte orolig om du inte förstår syntaxen i exemplet, detaljerna i Java kommer senare.

Programmet implementerar en del av ett ritprogram. För att inte det hela ska bli alltför oöverskådligt har inte själva den interaktiva konstruktionen av figurer tagits med utan appletens init()-metod skapar ett antal figurer som användaren kan flytta omkring, dels en och en (om man klickar med musen nära centrum av en figur och med musknappen nedtryckt sedan flyttar markören), dels också alla samtidigt (om man klickar någon annanstans). Appleten följer här:

I objektmodellen är Shape-klasserna mer detaljerade än de övriga (det finns ju ingen mening i att lägga in Javas hela klasshierarki ovanför Applet t.ex. - vi anser Applet vara superklass):

Notationen {abstract} kan användas för att särskilt markera att operationen draw är abstrakt och alltså måste implementeras i subklasserna.

De två klasserna MouseAdapter och MouseMotionAdapter hanterar händelserna musklick och musförflyttning (mer om hur detta går till senare).

Hur skulle nu Shape och dess subklasser implementeras i ett icke objektorienterat språk? Det troligaste är något i stil med (i ett tänkt Java-liknande språk där klasserna är enkla records som i Pascal eller C):

class Shape 
{
  int typeOfShape;
  int x,y,size;
  boolean fill;
}

Shape shape[MAXNUMOFSHAPES];

for (int i=0; i < numOfShapes; i++)
  draw( shape[i] );

draw ( Shape shape )
{
  switch (shape.typeOfShape)
  {
    case SQUARE:   drawSquare( x, y, size, fill);
    case TRIANGLE: drawTriangle( x, y, size, fill);
    case CIRCLE:   drawCircle( x, y, size, fill);
  }
}

Principen för hur det ser ut i Java:
class Shape 
{
  int x,y,size;
  boolean fill;
  draw();
}

class Triangle extends Shape
{
  draw() { rita triangeln; }
}

class Square extends Shape
{
  draw() { rita kvadraten; }
}

class Circle extends Shape
{
  draw() { rita cirkeln; }
}

Shape shape[MAXNUMOFSHAPES];

for (int i=0; i < numOfShapes; i++)
  shape[i].draw();
Punkt-notationen används för metoder: a.b() innebär alltså att metoden b anropas för instansen a och vi behöver alltså inte skicka med a som argument till b.

De båda kodsnuttarna ovan är inte fullständiga och kan kanske tyckas likvärdiga vid första anblicken. Men anta nu att man får ändrade förutsättningar, t.ex. att man vill kunna rita rektangler eller ellipser. I den funktionsbaserade lösningen krävs nu relativt omfattande ändringar: alla funktioner som utför något - i exemplet endast draw(), men många fler funktioner finns i ett fullfjädrat ritprogram - måste ändras genom att nya case-satser läggs in. Den objektorientade lösningen däremot tillåter att man lägger in kod för de nya figurerna utan någon ändring alls av existerande kod: Man kan lägga till nya figurer (rektanglar, ellipser, månghörningar, etc) utan att någonting i det redan skrivna behöver ändras eller ens kompileras om.

Lägg märke till att alla typer av figurer (som är subklasser till Shape) kan lagras i variabler av typen Shape. Olika figurer kan sedan "bete sig" olika när en metod anropas - detta kallas polymorfi och är en mycket viktig del av objektorienterad programmering. Programmeraren uppnår denna polymorfi genom att ersätta (eng. override) metoder i superklassen: man skriver helt enkelt en ny metod med samma signatur: samma namn och samma parametrar. Abstrakta metoder måste "ersättas" i subklasserna eftersom de inte är implementerade i superklassen.

En annan mycket stor fördel med OOP-lösningen är att man kan ändra den interna representationen av formerna utan att program som använder Shape-klasserna påverkas. Detta kallas inkapsling (eng. encapsulation) av data.

Fullständigt program

För att göra exemplet fullständigt finns programmet att titta på i filerna Bli inte orolig om du inte förstår programmet, detaljerna kommer att klarna så småningom (den applet du ser är inte exakt denna, en teknik som kallas dubbelbuffring används för att reducera flimret).

Implementation

Hur implementeras arv, aggregat och association i Java? Som exemplet visar används extends för arv. Aggregat åstadkoms helt enkelt genom att en klass innehåller variabler som refererar instanser av en annan klass, i en array om multipliciteten är större än 1. Referenser används också för associationer. I koden ser man alltså ingen skillnad mellan associationer och aggregat, mer än att aggregat-delarna instansieras av klassen själv medan associationerna oftast skickas i form av referenser till en metod - att OOPExempel själv skapar Shape-objekten är ju en följd av att själva konstruktörsmetoderna (eller kanske -objekten?) inte finns med.

Sammanfattning