El desenvolupament de programari basat en components és una branca de l'enginyeria de programari que emfatitza la separació d'interessos (separation of concerns o SoC). Es tracta de separar un programari en seccions que adrecen un interès delimitat. Com millor és la separació, més modular és el programari.
Per aconseguir la modularitat es poden utilitzar diferents tècniques. Una és l'encapsulació. Consisteix a restringir l'accés directe als components d'un objecte. Es diu que l'herència va en contra de l'encapsulació, ja que exposa els detalls de la implementació a les subclasses.
També hi ha tècniques addicionals associades al bon disseny de components, com són:
Per tant, tenim tècniques de disseny que faciliten el desenvolupament de components.
Els llenguatges també faciliten el disseny per components. A Java, el principal recurs és la utilització d'interfícies (interfaces).
La signatura d'un mètode inclou tant el nom del mètode com els paràmetres: el seu nombre i tipus. Una interface defineix la signatura dels mètodes que haurà de tenir un objecte, i per tant com es comunicarà amb altres objectes. La idea d'una interface és separar la funcionalitat (que es veu a les signatures) de la implementació. Això permet canviar la implementació respectant la signatura.
Un JAR és un format d'arxiu amb codi Java executable (compilat). És la forma habitual de compartir funcionalitats ja implementades als nostres programaris. Quan executem la nostra aplicació, només cal afegir els JAR corresponents al classpath (camí de classes) de la nostra màquina virtual, i llavors totes les classes d'aquest arxiu estaran disponibles.
Els entorns de desenvolupament com NetBeans permeten generar arxius JAR a partir del nostre codi, i llavors compartir-los amb projectes que les puguin necessitar.
Una forma habitual també de generar arxius és amb eines com ANT, una eina de línia de comanda que permet executar processos de construcció de fitxers tenint en compte les seves dependències. Per exemple, podria permetre compilar i construir un JAR.
La classe ServiceLoader és una utilitat de Java que permet obtenir un o diversos objectes que implementen cert servei, sense saber com es diuen aquests objectes. Això permet crear solucions modulars i extensibles.
Aquí hi ha un tutorial sobre una implementació d'un servei de diccionari (Dictionary).
ServiceLoader<Dictionary> loader = ServiceLoader.load(Dictionary.class);
Iterator<Dictionary> dictionaries = loader.iterator();
La llista de diccionaris obtinguda a la segona setència dependrà de quines implementacions es trobin als JARs afegits al classpath. Cada JAR pot indicar quines implementacions d'aquesta classe ha fet afegint un arxiu:
META-INF/services/dictionary.spi.Dictionary
El nom de l'arxiu inclou el seu package (dictionary.spi) i el nom del servei (un interface en aquest cas), i el contingut ha de tenir una línia amb el package i la classe concreta que implementa el interface, i que ha de tenir un constructor amb zero arguments.
Amb aquest esquema, podem afegir funcionalitat a una solució sense necessitat de recompilar-la ni la necessitat de conèixer la implementació.
El model de programació d'esdeveniments permet que un objecte sigui notificat quan succeeix un esdeveniment. Això permet que no calgui anar comprovant periòdicament si ha passat alguna cosa (polling), sinó que ens informen. Simplifica la programació, evita executar codi addicional i arriba just al moment.
En aquest model hi ha involucrades dues parts:
Per tal que l'oient pugui avisar a l'origen, cal utilitzar un model de registre d'oients. L'origen té habitualment un mètode que permet registrar a l'oient, i quan hi ha un esdeveniment mira quins oients hi ha registrats, i els notifica.
El registre en logs (logging) permet organitzar d'una forma més óptima els missatges generats en temps d'execució per un programari, i és una alternativa als System.out.println
. Té els següents avantatges:
Java conté a la seva llibreria base el paquet java.util.logging
. Aquest paquet pot ser utilitzat de forma bàsica de la següent forma:
private static final Logger LOGGER = Logger.getLogger(LaMevaClasse.class.getName());
Aquesta sentència es pot afegir al començament d'una classe, per tal de fer-hi referència posteriorment. Per exemple:
LOGGER.log(Level.INFO, "un missatge");
El primer paràmetre és el nivell de la notificació, i el segon el missatge. El nivell pot variar entre SEVERE (valor més alt), WARNING, INFO, CONFIG, FINE, FINER i FINEST (valor més baix).
A més, aquest mètode permet utilitzar paràmetres per posició amb el codi {0}, {1}... o passant una excepció.
LOGGER.log(Level.WARNING, "un missatge amb paràmetre {0}", "1");
LOGGER.log(Level.WARNING, "un missatge amb paràmetres {0} i {1}", new Object[]{"1", 2});
LOGGER.log(Level.WARNING, "un missatge sense paràmetre i amb excepció", new RuntimeException("una excepció"));
Si tenim un arxiu loggingConfigFile
(tipus File
) amb l'arxiu de configuració, hem de configurar el registre al començament de l'execució del programari mitjançant la classe LogManager:
LogManager.getLogManager().readConfiguration(new FileInputStream(loggingConfigFile));
Aquesta podria ser una configuració mínima amb nivell màxim de FINE:
handlers= java.util.logging.ConsoleHandler
.level= FINE
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
El paradigma origen/oient