• Non ci sono risultati.

Schiacciatine in Java

0.4 Java vs. Go

0.4.2 Schiacciatine in Java

Una traduzione “letterale” della versione in Go è la seguente:

package schiacciatine;

import java.util.Scanner;

public class Schiacciatine {

public static char[] schiaccia(char[] s) { char[] result = new char[s.length];

int i = 0;

for (char c : s) { if (c == '*') {

result[i] = c;

i++;

} }

for (char c : s) {

if (Character.isLetter(c)) { result[i] = c;

} i++;

}return result;

}

public static char[][] schiacciaTutte(char[][] s) { char[][] result = new char[s.length][s[0].length];

int i = 0;

for (char[] line : s) { result[i] = schiaccia(line);

} i++;

return result;

}

public static char[][] trasponi(char[][] s) { char[][] result = new char[s[0].length][s.length];

for (int i = 0; i < result.length; i++) { for (int j = 0; j < result[i].length; j++) {

result[i][j] = s[j][i];

} }

return result;

0.4 Java vs. Go

}

public static void main(String[] args) { Scanner scanner = new Scanner(System.in);

int r, c;

System.out.print("Dammi il numero di righe: ");

r = scanner.nextInt();

System.out.print("Dammi il numero di colonne: ");

c = scanner.nextInt();

char[][] matrice = new char[r][c];

for (int i = 0; i < r; i++) {

System.out.println("Dammi la riga " + i);

for (int j = 0; j < c; j++) {

matrice[i][j] = scanner.next().charAt(0);

} }

char[][] result = trasponi(schiacciaTutte(trasponi(matrice)));

for (int i = 0; i < result.length; i++) { for (int j = 0; j < result[0].length; j++) {

System.out.print(result[i][j] + " ");

}

System.out.println();

} } }

Traduzione Object-oriented

Invece, con uno stile molto più orientato agli oggetti:

package schiacciatine;

import java.util.Scanner;

public class SchiacciatineOO { private char[][] matrice;

public SchiacciatineOO(char[][] matrice) { this.matrice = matrice;

}

public static void main(String[] args) { Scanner scanner = new Scanner(System.in);

int r, c;

System.out.print("Dammi il numero di righe: ");

r = scanner.nextInt();

System.out.print("Dammi il numero di colonne: ");

c = scanner.nextInt();

char[][] matriceTarget = new char[r][c];

for (int i = 0; i < r; i++) {

System.out.print("Dammi la riga " + i);

for (int j = 0; j < c; j++) {

matriceTarget[i][j] = scanner.next().charAt(0);

} }

SchiacciatineOO obj = new SchiacciatineOO(matriceTarget);

obj.trasponi();

obj.schiacciaTutte();

obj.trasponi();

System.out.println("\n" + obj);

}

private char[] schiaccia(int line) {

char[] result = new char[matrice[line].length];

int i = 0;

for (char c : matrice[line]) { if (c == '*') {

result[i] = c;

i++;

} }

for (char c : matrice[line]) { if (Character.isLetter(c)) {

result[i] = c;

} i++;

}return result;

}

public void schiacciaTutte() {

char[][] result = new char[matrice.length][matrice[0].length];

for (int i = 0; i < matrice.length; i++) { result[i] = schiaccia(i);

}

matrice = result;

}

public void trasponi() {

char[][] result = new char[matrice[0].length][matrice.length];

0.4 Java vs. Go

for (int i = 0; i < result.length; i++) { for (int j = 0; j < result[i].length; j++) {

result[i][j] = matrice[j][i];

}

}matrice = result;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder();

for (int i = 0; i < matrice.length; i++) { for (int j = 0; j < matrice[i].length; j++) {

sb.append(matrice[i][j]);

}

sb.append('\n');

}return sb.toString();

} }

Questa versione permette già di fare alcune osservazioni di programmazione ‘in grande’.

Consideriamo il “componente” SchiacciatineOO (in programmazione ‘in grande’ siamo interessati ai “pezzi” che compongono il nostro sistema e a come interagiscono, cosa nascondono e cosa espongono): la sua responsabilità è quella di “schiacciare” una matrice di caratteri; si noti che tale matrice è un altro componente, esterno agli oggetti Schiac

ciatineOO

5

, infatti il main deve creare (e riempire) una matrice (matriceTarget) prima di passarla a un oggetto SchiacciatineOO. La matrice, quindi, non è affatto incapsulata nell’oggetto SchiacciatineOO, nonostante sia un attributo

private

di Schiacciatine

OO : con

private

si regola la visibilità dell’identificatore (matrice) con cui ci si riferisce alla matrice, ma non necessariamente la sua accessibilità nell’ambito del sistema. Di fatto un programmatore disattento potrebbe scrivere (nel main):

SchiacciatineOO obj = new SchiacciatineOO(matriceTarget);

obj.trasponi();

obj.schiacciaTutte();

matriceTarget[0][0] = '?';

obj.trasponi();

Una simile operazione violerebbe i principi progettuali del componente, ma risulta del tutto lecita dal punto di vista sintattico. Vale la pena osservare che la classe Hello vista nell’esempio

0.4.1, che pure è simile, non ha questo problema perché gli oggetti stringa

sono immutabili.

5A rigore i componenti del sistema sono sempre oggetti; quando ne parliamo, però, generalmente facciamo riferimento alle loro caratteristiche comuni, cioè alla classe di cui sono esemplari. Del resto per parlare di un esercito, diremmo probabilmente che i soldati fanno questo e quest’altro, anche se naturalmente le azioni sono effettivamente svolte da Caio e Sempronio (soldati arruolati nell’esercito in questione).

Per incapsulare la matrice negli oggetti SchiacciatineOO una possibile soluzione è spostarne la creazione nel costruttore (un’altra possibilità, piuttosto inefficiente in questo caso, potrebbe essere quella di farne una copia).

public classSchiacciatineOO{ privatechar[][]matrice;

publicSchiacciatineOO() {

Scanner scanner=newScanner(System.in);

intr,c;

System.out.print("Dammi il numero di righe: ");

r=scanner.nextInt();

System.out.print("Dammi il numero di colonne: ");

c=scanner.nextInt();

matrice=newchar[r][c];

for(inti= 0;i<r;i++) {

System.out.print("Dammi la riga "+i);

for(intj= 0;j<c;j++) {

matrice[i][j] =scanner.next().charAt(0);

} } }

public staticvoidmain(String[]args) {

SchiacciatineOO obj=newSchiacciatineOO();

obj.trasponi();

obj.schiacciaTutte();

obj.trasponi();

System.out.println("\n"+obj);

}

// Questi metodi non cambiano:

// private char[] schiaccia(int line) // public void schiacciaTutte() // public void trasponi() // public String toString() }

// Versione precedente public classSchiacciatineOO{

private char[][]matrice;

publicSchiacciatineOO(char[][]matrice) { this.matrice=matrice;

}

public staticvoidmain(String[]args) { Scanner scanner=newScanner(System.in);

intr,c;

System.out.print("Dammi il numero di righe: ");

r=scanner.nextInt();

System.out.print("Dammi il numero di colonne: ");

c=scanner.nextInt();

char[][]matriceTarget=newchar[r][c];

for(inti= 0;i<r;i++) {

System.out.print("Dammi la riga "+i);

for(intj= 0;j<c;j++) {

matriceTarget[i][j] =scanner.next().charAt(0);

} }

SchiacciatineOO obj=new SchiacciatineOO(matriceTarget);

Sempre ragionando sulle responsabilità della classe SchiacciatineOO, cioè sul perché l’abbiamo progettata viene da chiedersi perché esponga un metodo trasponi: si tratta infatti di un servizio interno, utile all’implementazione (come schiaccia), ma non im-mediatamente comprensibile come necessario allo scopo di SchiacciatineOO. In effetti, dovendo spiegare o documentare il componente SchiacciatineOO, dovremmo probabil-mente chiarire a un potenziale utilizzatore che dovrà ricordarsi di chiamare (due volte!) trasponi.

Per evitare anche questo problema possiamo spostare le trasposizioni alla fine del co-struttore e alla fine di schiacciaTutte, e, conseguentemente rendere

private

il metodo trasponi .

public SchiacciatineOO() {

Scanner scanner = new Scanner(System.in);

int r, c;

System.out.print("Dammi il numero di righe: ");

r = scanner.nextInt();

System.out.print("Dammi il numero di colonne: ");

c = scanner.nextInt();

matrice = new char[r][c];

0.4 Java vs. Go

for (int i = 0; i < r; i++) {

System.out.print("Dammi la riga " + i);

for (int j = 0; j < c; j++) {

matrice[i][j] = scanner.next().charAt(0);

} }

trasponi();

}

public static void main(String[] args) { SchiacciatineOO obj = new SchiacciatineOO();

obj.schiacciaTutte();

System.out.println("\n" + obj);

}

public void schiacciaTutte() {

char[][] result = new char[matrice.length][matrice[0].length];

for (int i = 0; i < matrice.length; i++) { result[i] = schiaccia(i);

}

matrice = result;

trasponi();

}

private void trasponi() { // come prima

}

// Questi metodi non cambiano:

// private char[] schiaccia(int line) // public String toString()

}

A questo punto, però, salta all’occhio l’inefficienza della doppia trasposizione! Il nostro componente conserva la matrice in forma trasposta rispetto a ciò che provie-ne dall’input e ri-traspoprovie-ne provie-nella sua operazioprovie-ne principale: possiamo evitare almeno la prima trasposizione costruendo la matrice già in forma trasposta.

public SchiacciatineOO() {

Scanner scanner = new Scanner(System.in);

int r, c;

System.out.print("Dammi il numero di righe: ");

r = scanner.nextInt();

System.out.print("Dammi il numero di colonne: ");

c = scanner.nextInt();

matrice = new char[c][r]; // trasposta!

for (int i = 0; i < r; i++) {

System.out.print("Dammi la riga " + i);

for (int j = 0; j < c; j++) {

matrice[j][i] = scanner.next().charAt(0);

} } }

// Questi metodi non cambiano:

// public static void main(String[] args) // public void schiacciaTutte()

// private char[] schiaccia(int line) // private void trasponi()

// public String toString() }

Attenzione però: ora diventa importante non chiamare schiacciaTutte più di una volta, altrimenti la trasposizione introduce un problema. Per non avere questo vincolo, meglio spostare la trasposizione nel metodo che serializza lo stato dell’oggetto come Str

ing (cioè il metodo toString), invece di cambiare lo stato con il metodo trasponi (che a questo punto diventa inutile e si può rimuovere).

public class SchiacciatineOO { private char[][] matrice;

public void schiacciaTutte() {

char[][] result = new char[matrice.length][matrice[0].length];

for (int i = 0; i < matrice.length; i++) { result[i] = schiaccia(i);

}

matrice = result;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder();

for (int col = 0; col < matrice[0].length; col++) { for (int i = 0; i < matrice.length; i++) {

sb.append(matrice[i][col]);

sb.append(" ");

}

sb.append('\n');

0.4 Java vs. Go

}return sb.toString();

}

// Questi metodi non cambiano:

// public SchiacciatineOO()

// public static void main(String[] args) // private char[] schiaccia(int line) }

C’è un ultimo miglioramento interessante, miglioramento che probabilmente un pro-grammatore ‘in grande’ esperto avrebbe introdotto fin dalle fasi iniziali. Il componente SchiacciatineOO attualmente è difficile da verificare con test di unità non interattivi:

senza tecniche particolari possiamo solo “provarlo” con un file di input (serve qualcosa tipo gradle run < input.txt, si veda l’esempio in

1.2) e vedere se il risultato è quello

atteso.

Possiamo migliorare molto la situazione esponendo la dipendenza con l’input: il fatto che gli oggetti vengano creati prendendo i dati dallo standard input non dovrebbe essere cablato nel costruttore, ma dipendere da un parametro col quale si sceglie la sorgente dei dati. L’esposizione permette di alterare l’oggetto che se ne occupa in fase di test (si noti la simmetria con il caso discusso sopra in cui abbiamo invece perfezionato l’incapsulamento per evitare alterazioni esterne, che qui invece sono desiderabili).

public SchiacciatineOO(InputStream sorgente) { Scanner scanner = new Scanner(sorgente);

int r, c;

System.out.print("Dammi il numero di righe: ");

r = scanner.nextInt();

System.out.print("Dammi il numero di colonne: ");

c = scanner.nextInt();

matrice = new char[c][r];

for (int i = 0; i < r; i++) {

System.out.print("Dammi la riga " + i);

for (int j = 0; j < c; j++) {

matrice[j][i] = scanner.next().charAt(0);

} } }

public static void main(String[] args) {

SchiacciatineOO obj = new SchiacciatineOO(System.in);

obj.schiacciaTutte();

System.out.println("\n" + obj);

}

// Questi metodi non cambiano:

// public void schiacciaTutte() // private char[] schiaccia(int line) // public String toString()

Ora possiamo scrivere anche la classe di test (con la libreria Junit “jupiter”).

package schiacciatine;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.ByteArrayInputStream;

import java.io.InputStream;

import java.nio.charset.StandardCharsets;

import org.junit.jupiter.api.Test;

public class SchiacciatineOOTest {

@Test

void testInput(){

String sb = "3\n"

+ "2\n"

+ "Z Z\n"

+ "* *\n"

+ "A A\n";

InputStream in = new ByteArrayInputStream(sb.getBytes(StandardCharsets.UTF_8));

String expected = "Z Z \n* * \nA A \n";

SchiacciatineOO soo = new SchiacciatineOO(in);

assertEquals(expected, soo.toString());

}

@Test

void testSchiacciaTutte() { String sb = "5\n"

+ "4\n"

+ "V * K S\n"

+ "* * * S\n"

+ "S B * *\n"

+ "* * * *\n"

+ "S B S B\n";

InputStream in = new ByteArrayInputStream(sb.getBytes(StandardCharsets.UTF_8));

SchiacciatineOO soo = new SchiacciatineOO(in);

String expected = "* * * * \n* * * * \nV * * S \nS B K S \nS B S B \n";

soo.schiacciaTutte();

assertEquals(expected, soo.toString());

} }

1 Lab01: Esperimenti iniziali

1.1 Campo minato

Il punto di partenza di questo laboratorio è disponibile all’indirizzo:

https://gitlab.

com/programmazione2/lab01.

Scritto da Martin P. Robillard e adattato per il corso di “Programmazione II” @ Unimi.

Figura 1.1: Screenshot dell’applicazione Minesweeper

Documenti correlati