Enhavo

Objektema programado per Ĝavo


3 Ekzemplo pri objektema programado

La antaŭa leciono klarigis la realigon de tri gravaj objektemaj principoj en Ĝavo:

La avantaĝoj de enkapsuligo estas relative simplaj kaj bone kompreneblaj, sed heredado kaj dinamika bindado estas kompleksaj, interrilataj konceptoj. Nia simpla ekzemplo de la klaso Membro ne sufiĉas por klarigi ilian uzon kaj iliajn avantaĝojn. Tial ni reiru al nia komenca ekzemplo de geometriaj figuroj kaj iom pli detale esploru ĝin.

3.1 Desegnado de geometriaj figuroj

Ni komencu per du simplaj, sendependaj klasoj, kies objektoj reprezentas triangulojn resp. cirklojn. Tiuj objektoj kapablas desegni sin mem helpe de la klaso java.awt.Graphics, kiun disponigas la norma rul-medio de Ĝavo. La necesaj konstruiloj ne estas prezentataj.

     public class Triangulo {
         /** la koordinatoj de la tri verticoj */
         private int x1;
         private int y1;
         private int x2;
         private int y2;
         private int x3;
         private int y3;
         /** metodo por desegni sin */
         public void desegnu(Graphics g) { 
             g.drawLine(x1, y1, x2, y2);
             g.drawLine(x2, y2, x3, y3);
             g.drawLine(x3, y3, x1, y1);
         }
     }
    
     public class Cirklo {
         /** la koordinatoj de la centro */
         private int x;
         private int y;
         /** la radiuso */
         private int r;
         /** metodo por desegni sin */
         public void desegnu(Graphics g) { 
             g.drawOval(x1 - r, y1 - r, 2 * r, 2 * r);
         }
     }
    

Nun ni supozu klason Bildo, kiu havas liston de geometriaj figuroj kaj volas desegni ilin. La fakto, ke la figur-objektoj scias desegni sin mem, faciligas la aferon. La listo estu unudimensia tabelo. Ĉar ĝi enhavas kaj cirklojn kaj triangulojn, necesas deklari ĝin per la klaso Object, kiu estas superklaso de ĉiuj klasoj en Ĝavo.

public class Bildo {
     private Object[] figuroj = …
}
    

Ni nun neglektas la programparton, kiu plenigas la liston; povus esti la agoj de iu uzanto, kiu sur la ekrano desegnas figurojn. Ni konsideru la programparton, kiu desegnas ĉiujn figurojn en la listo; ĝi estu metodo, kiu same nomiĝas desegnu. (Ne estas problemo, ke metodoj de diversaj klasoj havas la saman nomon; tio eĉ povas plibonigi la klarecon de programo.)

3.1.1 La ne-objektema aliro

Se Ĝavo ne estus objektema lingvo, ni devus procedi proksimume jene:

     public void desegnu(Graphics g) {
         // trairu la liston:
         for (int i = 0; i < figuroj.length; i++) {
             Object figuro = figuroj[i]; // la i-a figuro
             if (figuro instanceof Triangulo) {
                 Triangulo triangulo = (Triangulo) figuro;
                 triangulo.desegnu(g);
             }
             if (figuro instanceof Cirklo) {
                 Cirklo cirklo = (Cirklo) figuro;
                 cirklo.desegnu(g);
             }
         }
     }
    

En tiu ĉi programpeco ni trairas la liston pere de indico i, kiu iras de 0 ĝis sub la longo de la listo (tabelaj indicoj en Ĝavo komenciĝas ĉe 0, ne ĉe 1). Ene de la ciklo ni ne scias, ĉu la koncerna objekto estas triangulo aŭ cirklo; do ni devas esplori ĝin. Tion ebligas la ĝava operaciilo instanceof ("ekzemplero de"); la esprimo o instanceof K estas vera, se kaj nur se objekto o estas ekzemplero de klaso K.

Se la koncerna figuro estas ekzemplero de klaso Triangulo, ni povas "devigi" ĝin al tiu klaso; tio okazas per antaŭmeto de la klasnomo en rondaj krampoj: (Triangulo) figuro. La rezulto estas uzebla kiel ekzemplero de la klaso Triangulo kaj do scias desegni sin. Analoge pri la klaso Cirklo.

La esploro per instanceof necesas, ĉar erara devigo rezultas en rul-eraro, kiu haltigas la programon.

3.1.2 La objektema aliro

Ĉar Ĝavo estas objektema lingvo, ĝi havas heredadon kaj abstraktajn metodojn. Tiuj ecoj permesas al ni eviti la esploradon de la objekt-tipoj per instanceof. (Pretere, ne-objektemaj lingvoj normale ne posedas tian mekanismon por esplori daten-tipojn; do necesus, ke la programisto mem realigu ĝin.)

Unue, necesas havi superklason por geometriaj figuroj; ni nomu ĝin Figuro. Due, tiu klaso havu abstraktan metodon por desegni sin; ĝi do mem devas esti abstrakta. Trie, la klasoj Triangulo kaj Cirklo estu ĝiaj subklasoj:

    public abstract class Figuro {
        public abstract void desegnu(Graphics g);
    }
    
     public class Triangulo extends figuro {
         /** la koordinatoj de la tri verticoj */
         private int x1;
         private int y1;
         private int x2;
         private int y2;
         private int x3;
         private int y3;
         /** metodo por desegni sin */
         public void desegnu(Graphics g) { 
             g.drawLine(x1, y1, x2, y2);
             g.drawLine(x2, y2, x3, y3);
             g.drawLine(x3, y3, x1, y1);
         }
     }
    
     public class Cirklo extends figuro {
         /** la koordinatoj de la centro */
         private int x;
         private int y;
         /** la radiuso */
         private int r;
         /** metodo por desegni sin */
         public void desegnu(Graphics g) { 
             g.drawOval(x1 - r, y1 - r, 2 * r, 2 * r);
         }
     }
    

Ni rimarkas, ke la klasoj Triangulo kaj Cirklo ne ŝanĝiĝis, escepte de la aldono extends Figuro. Tiu aldono signifas, ke ili estas subklasoj de Figuro.

Nun eblas pli simple formuli la klason Bildo:

 public class Bildo {
     private Figuro[] figuroj = …
     public void desegnu(Graphics g) {
         // trairu la liston:
         for (int i = 0; i < figuroj.length; i++) {
             Figuro figuro = figuroj[i]; // la i-a figuro
             figuro.desegnu(g);
         }
     }
 }
    

Kompare al la ne-objektema aliro notindas jenaj diferencoj:

Ĉu tiuj ĉi avantaĝoj valoras la enkondukon de la objektema "programada paradigmo"? Fakte, la vera avantaĝo de la objektema aliro evidentiĝas, kiam oni iom plivastigas la ekzemplon. Ni enkonduku novan specon de figuroj, kvadraton:

     public class Kvadrato extends figuro {
         /** la koordinatoj de du kontrauaj verticoj */
         private int xCentra;
         private int yCentra;
         private int x1;
         private int y1;
         /** metodo por desegni sin */
         public void desegnu(Graphics g) { 
             int a = x1 − xCentra;
             int b = y1 − yCentra;
             int x2 = xCentra − a;
             int y2 = yCentra − b;
             int x3 = xCentra + b;
             int y3 = yCentra − a;
             int x4 = xCentra − b;
             int y4 = yCentra + a;
             g.drawLine(x1, y1, x2, y2);
             g.drawLine(x2, y2, x3, y3);
             g.drawLine(x3, y3, x4, y4);
             g.drawLine(x4, y4, x1, y1);
         }
    } 

Ni vidas, ke por enkonduki la novan specon de geometriaj figuroj sufiĉis verki novan klason. Ne necesis modifi jam ekzistantajn klasojn. En la ne-objektema aliro ni devus modifi la metodon desegnu de klaso Bildo, kaj eble multajn similajn lokojn en la programo.

Laŭ la objektema aliro la informo, ke ekzistas klaso Kvadrato, ideale ekzistas nur en tiu klaso mem. Tial estas simple enkonduki novajn klasojn. Tamen tiu principo ne estas absolute sekvebla, ĉar por produkti ekzemplerojn de iu klaso ja necesas voki ties konstruilon, do scii pri ĝi. Por tamen laŭeble konservi la avantaĝojn de objektemo oni ofte restriktas la produktadon de ekzempleroj al specialaj klasoj, nomataj "fabrikoj". Multaj klasoj estas konataj nur al siaj fabrikoj kaj nekonataj ekstere. Ni poste vidos ekzemplon.

3.2 Hierarkioj de objektoj

La operaciilo instanceof, kiu diras al ni, ĉu iu objekto estas de iu klaso, ne plu estas necesa en la objektema versio de nia programo. Ĝenerale la uzo de instanceof estas indikilo, ke programo ne estas vere objektema. Ofte eblas anstataŭi tiun operaciilon per dinamika bindado; tiam estas konsilinde fari tion.

Kiel instanceof rilatas al sub- kaj superklasoj? Ni konsideru etan program-pecon kaj ties rezulton (eligon):

     Cirklo c = new Cirklo(23, 31, 7); // tia konstruilo estas utila
     Triangulo t = new Triangulo(0, 4, 3, 4, 3, 0); // ankau tia

     if (t instanceof Triangulo) {
         System.out.println("t estas triangulo.");
     }
     if (t instanceof Cirklo) {
         System.out.println("t estas cirklo.");
     }
     if (t instanceof Figuro) {
         System.out.println("t estas figuro.");
     } 
t estas triangulo.
t estas figuro.

Estas ja klare, ke k estas Triangulo kaj ke k ne estas Cirklo. Sed laŭ la opinio de Ĝavo k estas ankaŭ Figuro! Same kiel en geometrio ĉiuj trianguloj estas figuroj, kvankam ne ĉiuj figuroj estas trianguloj. Kaj same kiel en la reala vivo objektoj formas hierarkion aŭ hierarkiojn. Ili povas havi multajn nivelojn; ekzemple Triangulo, mem subklaso de Figuro, povas esti superklaso de OrtaTriangulo kaj similaj klasoj. Sed ne ĉiuj tiaj klas-rilatoj estas utilaj.

Kial ni diris "hierarkion aŭ hierarkiojn"? Efektive ne en ĉiuj objektemaj lingvoj ekzistas universala superklaso, kiu ampleksas ĉiujn objektojn. Tie ekzistas pluraj sendependaj hierarkioj, ekzemple de Figuroj kaj de Membroj, kiuj ne havas komunan superklason.

En Ĝavo ekzistas universala superklaso: Ĝi nomiĝas Object (objekto), kaj ĉiu objekto estas ekzemplero de ĝi. Tial en Ĝavo ekzistas nur unu hierarkio de objektoj.

La klaso Object ne estas tre kompleksa. Jen la plej gravaj el ĝiaj metodoj:


sekva