Gyakorlati alapok III.
Az osztály alapszintű működése
Ebben a fejezetben a valójában egyszerű körszámításon keresztül fogjuk megismerni az osztályok alapszintű, egymáshoz hangolt működését.
Ebből a szempontból a jelen fejezet rendkívül tanulságos és kiemelten hasznos lesz, bár jelzem, hogy a megértés kedvéért alapvető, mostanában már kötelezően elvárt objektumorientált módszerek kifejtését még nem tartalmazza, ezek:
Először ismételjük meg a már publikált példakódot (A kör kerülete és területe egyszerű adatbekéréssel című fejezetből):
import java.math.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
final double PI = 3.1415926535;
Scanner in = new Scanner(System.in);
System.out.println("Kérem, hogy adja meg a sugárhosszt
(r)!");
String sugar = in.nextLine();
double r = Double.parseDouble(sugar);
double kerulet = 2 * r * PI;
double terulet = Math.pow(r, 2) * PI;
System.out.println(r + " egységnyi sugarú kör
kerülete: " + kerulet);
System.out.println(r + " egységnyi sugarú kör
területe: " + terulet);
}
}
Végeredmény:
Kérem, hogy adja meg a sugárhosszt (r)!
5.8
A 5.8 egységnyi sugarú kör kerülete: 36.4424747806
A 5.8 egységnyi sugarú kör területe: 105.68317686374
Ismétlésképpen nézzük át a fenti kód legfontosabb tulajdonságait:
-
minden a Main osztályon és a main() főprogramon belül helyezkedik el,
-
a kódok tehát funkciójuk szerint nem szegmentáltak, egymástól nem elkülönítettek,
-
ha nincsenek fordítási és egyéb hibák, minden azonnal futtatható.
Nyilvánvalóan a kód egyszerűsége miatt a fenti tulajdonságok nem okoznak számunkra hátrányokat. Sőt a kódegyszerűség most számunkra határozottan előnyös, hiszen emiatt könnyebben fogjuk átlátni a módosított kódokat. Ám az objektumorientált programozás magasabb szintű szempontok figyelembevételét követeli meg.
Itt tehát már felmerülnek előzetes tervezési szempontok is (Osztály-, és projekttervezési minták című fejezet), amely tervezési irányok különbözők is lehetnek, azaz minden programozó, projektvezető más és más szempontokat emelhet ki. Ha azonban a tervezési irány rossz, akkor komplett projektek futhatnak zátonyra, amely jelentős pénzügyi és egyéb veszteségeket képesek okozni.
Kezdjük az osztály immáron klasszikussá vált meghatározásával: az osztály, mint legkisebb önállóan működő egység a valamilyen logikai szempont alapján összetartozó adatokat és a rajta elvégezhető műveleteket jelenti.
Legelőször ezt az alapelvet implementáljuk a fenti körszámító algoritmuson. Ennek során a következőket változtatjuk meg:
-
mindent kiemelünk a main() metódusból: a 2 db bemeneti adatot és a 2 db számítási metódust.
-
A 2 db bemeneti adat osztályszintű helyre kerül, így az osztályon belül korlátozás nélkül mindenki látja (A hozzáférés-módosítók című fejezet).
-
A PI értéke konstans, nem változtatható, ezért final módosítót használunk deklarálásakor (Ami a változót (és a többieket) változatlanná teszi: a final című fejezet).
-
A 2 db számítási metódust szintén különálló metódusegységbe zárjuk (korKeruletSzamitas() és korTeruletSzamitas()).
-
Az r bemeneti ellenőrzését külön metódusokba tesszük (sugarBekeres() és ellenorzesSzamString()).
-
Mivel az osztálynévnek és kódtároló .java kiterjesztésű fájlnak azonos nevűnek kell lennie, a Main osztálynevet nem tudjuk egyszerűen megváltoztatni. Ha az alaposztály nevének mást akarunk megadni, akkor refaktorálnunk kell (itt: Korszamitas).
import java.math.*;
import java.util.Scanner;
public class Korszamitas{
final static double PI = 3.1415926535;
static double sugar;
static double sugarBekeres() {
Scanner in = new Scanner (System.in);
String szamString = new String();
boolean rosszAdat = false;
System.out.println ("Kérem, hogy adja meg a körsugarat (r >
0)!");
do{
szamString = in.nextLine();
rosszAdat =
ellenorzesSzamString(szamString);
}while (rosszAdat == true);
rosszAdat = false;
sugar = Double.parseDouble(szamString);
return sugar;
}
static boolean ellenorzesSzamString(String szamString){
boolean rosszAdat = false;
char karakter = szamString.charAt(0);
if(karakter == '0'){
rosszAdat = true;
}
for(int i = 0; i < szamString.length(); i++){
karakter = szamString.charAt(i);
if(karakter != '0'
&& karakter
!= '1'
&& karakter
!= '2'
&& karakter
!= '3'
&& karakter
!= '4'
&& karakter
!= '5'
&& karakter
!= '6'
&& karakter
!= '7'
&& karakter
!= '8'
&& karakter
!= '9'){
rosszAdat =
true;
break;
}
}
if(rosszAdat == true){
System.out.println("Kérem, hogy csak
pozitív egész számot adjon meg!");
}
return rosszAdat;
}
static double korKeruletSzamitas(double sugar, double PI){
return 2 * sugar * PI;
}
static double korTeruletSzamitas(double sugar, double PI){
return Math.pow(sugar, 2) * PI;
}
static void megjelenites(){
sugar = sugarBekeres();
System.out.println(sugar + " egységnyi sugarú kör kerülete: "
+ korKeruletSzamitas(sugar, PI));
System.out.println(sugar + " egységnyi sugarú kör területe: "
+ korTeruletSzamitas(sugar, PI));
}
public static void main(String[] args) {
megjelenites();
}
}
Végeredmény:
Kérem, hogy
adja meg a körsugarat (r > 0)!
0
Kérem, hogy csak pozitív egész számot adjon meg!
d
Kérem, hogy csak pozitív egész számot adjon meg!
12
12.0 egységnyi sugarú kör kerülete: 75.398223684
12.0 egységnyi sugarú kör területe: 452.389342104
Ha az adattagokat és metódusokat a static jelzővel illetjük, minden azonnal futtatható lesz, másként viszont fordítási hibaüzenet kapunk. Az alábbi kódban a megjelenites() metódustól vettük el a static módosítást:
A megoldás: a tanultak szerint példányosítanunk kell az egész Korszamitas osztályt.
Ezután a keletkezett Korszamitas objektum referenciájával (itt: korszamitas), egy ponttal (.) és a metódusnévvel hivatkozhatunk az elérendő metódusra: korszamitas.megjelenites().
public static
void main(String[] args) {
Korszamitas korszamitas = new Korszamitas();
korszamitas.megjelenites();
}
}
Vegyünk egy másik osztályrendezési lehetőséget!
A funkcionális kódszegmentálás érdekében külön-külön osztályokba rendezzük az adatokat, a műveleteket, a megjelenítést és a végső, futtató Main osztályt. Azaz voltaképpen logikai ízekre szedjük a kódot. Ekkor a projekten belül következő különálló osztályokat fogjuk létrehozni:
-
AdatOsztaly{}
-
MuveletOsztaly{}
-
MegjelenitesOsztaly{}
-
Main{}
Az AdatOsztaly{} 2 db bemeneti adatot tartalmaz:
-
körsugár (r) - double sugar
-
PI - final double PI = 3.1415926535
public class
AdatOsztaly {
final double PI = 3.1415926535;
double sugar;
}
Emlékezzünk vissza: a PI értéke konstans, nem változtatható, ezért final módosítót használunk deklarálásakor (Ami a változót (és a többieket) változatlanná teszi: a final című fejezet).
Ebben a pillanatban azonban felmerül az a probléma, hogy a körsugár deklarációja önmagában nem életképes, hanem mint egy különálló metódus bemeneti adatát a felhasználótól várjuk. A körsugár tehát egyrészt bemeneti adat, ám egy további, ellenőrző metódussal kell elkérnünk a felhasználótól. Most akkor hová tegyük a sugarBekeres() metódust? A rendezési elvek figyelembevételével természetesen a MuveletOsztaly{} osztályba kell tennünk.
A MuveletOsztaly{} így 4 db metódust tartalmaz:
-
sugarBekeres();
-
ellenorzesSzamString();
-
korKeruletSzamitas();
-
korTeruletSzamitas();
Ahhoz viszont, hogy elérjük az Adatosztaly{} adattagjait, ebben az osztályban példányosítani kell: AdatOsztaly adat = new AdatOsztaly();.
Ezután a keletkezett Adatosztaly objektum referenciájával (itt: adat), egy ponttal (.) és a változónévvel hivatkozhatunk az elérendő változóra: adat.sugar. Példányosítás nélkül a MuveletOsztaly{} nem látja a változót, ezért fordítási hibát kapunk.
Mind a 4 metódus függvény, azaz van visszatérési értékük (return). Ilyen módon működésük, funkcionalitásuk jobban behatárolható, specifikálható.
import java.math.*;
import java.util.Scanner;
public class MuveletOsztaly {
AdatOsztaly adat = new AdatOsztaly();
public double sugarBekeres() {
Scanner in = new Scanner (System.in);
String szamString = new String();
boolean rosszAdat = false;
System.out.println ("Kérem, hogy adja meg a körsugarat (r >
0)!");
do{
szamString = in.nextLine();
rosszAdat =
ellenorzesSzamString(szamString);
}while (rosszAdat == true);
rosszAdat = false;
adat.sugar = Double.parseDouble(szamString);
return adat.sugar;
}
static boolean ellenorzesSzamString(String szamString){
boolean rosszAdat = false;
char karakter = szamString.charAt(0);
if(karakter == '0'){
rosszAdat = true;
}
for(int i = 0; i < szamString.length(); i++){
karakter = szamString.charAt(i);
if(karakter
!= '0'
&& karakter != '1'
&& karakter != '2'
&& karakter != '3'
&& karakter != '4'
&& karakter != '5'
&& karakter != '6'
&& karakter != '7'
&& karakter != '8'
&& karakter != '9'){
rosszAdat = true;
break;
}
}
if(rosszAdat == true){
System.out.println("Kérem, hogy csak pozitív egész számot
adjon meg!");
}
return rosszAdat;
}
public double korKeruletSzamitas(double sugar, double PI){
return 2 * sugar * PI;
}
public double korTeruletSzamitas(double sugar, double PI){
return Math.pow(sugar, 2) * PI;
}
}
A MegjelenitesOsztaly{} 1 db metódust tartalmaz:
-
megjelenites();
Egyetlen feladata a kimeneti adatok megjelenítése. Az osztályban példányosítani kell az összes kapcsolódó osztályt...
MuveletOsztaly muvelet = new MuveletOsztaly();
AdatOsztaly adat = new AdatOsztaly();
...mert el kell érnünk adattagjait és/vagy metódusait. A korKeruletSzamitas() és korTeruletSzamitas() metódusokhoz szükségünk van a sugarBekeres() függvényre, ezért hívását szintén itt tesszük meg (adat.sugar = muvelet.sugarBekeres();).
public class MegjelenitesOsztaly {
MuveletOsztaly muvelet = new MuveletOsztaly();
AdatOsztaly adat = new AdatOsztaly();
public void megjelenites (){
adat.sugar = muvelet.sugarBekeres();
System.out.println(adat.sugar + " egységnyi sugarú kör
kerülete: "
+ muvelet.korKeruletSzamitas(adat.sugar, adat.PI));
System.out.println(adat.sugar + " egységnyi sugarú kör
területe: "
+ muvelet.korTeruletSzamitas(adat.sugar, adat.PI));
}
}
A Main{} osztály main() főprogramja csak a MegjelenitesOsztaly{} osztályt példányosítja és hívja meg:
public class
Main {
public static void main(String[] args) {
MegjelenitesOsztaly megjelenites = new MegjelenitesOsztaly();
megjelenites.megjelenites();
}
}
Végeredmény:
Kérem, hogy
adja meg a körsugarat (r > 0)!
0
Kérem, hogy csak pozitív egész számot adjon meg!
d
Kérem, hogy csak pozitív egész számot adjon meg!
10
10.0 egységnyi sugarú kör kerülete: 62.83185307
10.0 egységnyi sugarú kör területe: 314.15926535
Ilyen módon az önálló Java-projekt neve Korszamitas és 4 db, a projektben szintén külön-külön létrehozandó osztályból fog állni:
Pillanatképek a Korszamitas projektről
Ellenőrizzük le a megoldást teljes egészében:
Main.java
public class
Main {
public static void main(String[] args) {
MegjelenitesOsztaly megjelenites = new MegjelenitesOsztaly();
megjelenites.megjelenites();
}
}
MegjelenitesOsztaly.java
public class MegjelenitesOsztaly {
MuveletOsztaly muvelet = new MuveletOsztaly();
AdatOsztaly adat = new AdatOsztaly();
public void megjelenites (){
adat.sugar = muvelet.sugarBekeres();
System.out.println(adat.sugar + " egységnyi sugarú kör
kerülete: "
+ muvelet.korKeruletSzamitas(adat.sugar, adat.PI));
System.out.println(adat.sugar + " egységnyi sugarú kör
területe: "
+ muvelet.korTeruletSzamitas(adat.sugar, adat.PI));
}
}
MuveletOsztaly.java
import java.math.*;
import java.util.Scanner;
public class MuveletOsztaly {
AdatOsztaly adat = new AdatOsztaly();
public double sugarBekeres() {
Scanner in = new Scanner (System.in);
String szamString = new String();
boolean rosszAdat = false;
System.out.println ("Kérem, hogy adja meg a körsugarat (r >
0)!");
do{
szamString = in.nextLine();
rosszAdat =
ellenorzesSzamString(szamString);
}while (rosszAdat == true);
rosszAdat = false;
adat.sugar = Double.parseDouble(szamString);
return adat.sugar;
}
static boolean ellenorzesSzamString(String szamString){
boolean rosszAdat = false;
char karakter = szamString.charAt(0);
if(karakter == '0'){
rosszAdat = true;
}
for(int i = 0; i < szamString.length(); i++){
karakter = szamString.charAt(i);
if(karakter
!= '0'
&& karakter != '1'
&& karakter != '2'
&& karakter != '3'
&& karakter != '4'
&& karakter != '5'
&& karakter != '6'
&& karakter != '7'
&& karakter != '8'
&& karakter != '9'){
rosszAdat = true;
break;
}
}
if(rosszAdat == true){
System.out.println("Kérem, hogy csak pozitív egész számot
adjon meg!");
}
return rosszAdat;
}
public double korKeruletSzamitas(double sugar, double PI){
return 2 * sugar * PI;
}
public double korTeruletSzamitas(double sugar, double PI){
return Math.pow(sugar, 2) * PI;
}
}
AdatOsztaly.java
public class
AdatOsztaly {
final double PI = 3.1415926535;
double sugar;
}
Végeredmény:
Kérem, hogy
adja meg a körsugarat (r > 0)!
0
Kérem, hogy csak pozitív egész számot adjon meg!
d
Kérem, hogy csak pozitív egész számot adjon meg!
10
10.0 egységnyi sugarú kör kerülete: 62.83185307
10.0 egységnyi sugarú kör területe: 314.15926535
Itt jegyezzük meg, hogy a fentebb konstruált Adatosztaly csak elvi, ebben az esetben oktatási megközelítés.
Már megtanultuk: fontos szempont az objektumorientált elvek alkalmazása során, hogy az osztály logikailag nézve adatok ÉS metódusok zárt egységét alkotja, ezért adatokat külön osztályba rendezni bár megtehetjük, de inkább kövessük a "klasszikusnak" mondható objektumorientált alapelveket, tehát egymástól őket lehetőség szerint ne különítsük el.
Az már egészen más kérdés, ha az adatok az MVC-modell alapján logikailag és fizikailag más helyre kerülnek.