====== Patrons de disseny ======
{{tag> #FpInfor #Dam #DamMpDual #DamMpPro}}
----
==== Patrons de disseny ====
- Fa referència a solucions de programació usades habitualment al dissenyar software.
- Habitualment els patrons de disseny es consideren bones pràctiques a la hora de programar.
- A vegades es critica alguns patrons de disseny ja que poden ser resultat d’un mal disseny del software, en aquests casos s’anomenen ‘anti-patrons’
==== Paradigmes de programació ====
Els diferents llenguatges de programació ofereixen diferents eines per solucionar els problemes que surten a la hora de dissenyar el software. En la programació imperativa hi ha dos paradigmes:
- **Programació procedural**, que agrupa les instruccions en procediments i les dades en estructures
struct persona0 {
String nom;
String edat;
}
function aniversari () {
persona0.edat += 1;
}
- **Programació orientada en objectes**, en què les instruccions formen part de l’estat de l’objecte en què es declaren
public class Persona {
String nom;
String edat;
public void aniversari () {
edat += 1;
}
}
==== Orientació a objectes ====
És un paradigma en què la programació es fa definint objectes, que contenen dades i codi.
- **Dades**, o propietats de l’objecte
- **Codi**, procediments que operen sobre les dades (funcions)
Cal distingir entre les definicions i els objectes funcionals
- **Classes**, defineixen els objectes (les seves dades i funcions)
public class Poligon {
int num_vertex = 1;
String nom = “linia”
public Poligon (v1, n1) {
num_vertex = v1;
nom = n1; }
}
- **Instàncies**, objectes existents amb un estat (dades) diferents a altres objectes de la mateixa classe
public static void Main (String[] args) {
Poligon pL = new Poligon(2, “linia”);
Poligon pT = new Poligon(3, “triangle”);
Poligon pQ = new Poligon(4, “quadrat”);
}
pL, pT i pQ són instàncies de la classe Poligon
== Java, extends ==
Quan volem fer servir les propietats i mètodes d’una classe pare fem servir ‘**extends**’.
També podem sobre-escriure els mètodes de la classe filla amb ‘**@Override**'
package SuperClass;
public class SuperClass {
public int getNb() {
return 1;
}
public int getNb2() {
return 2;
}
}
package SuperClass;
public class SubClass extends SuperClass {
@Override
public int getNb2() {
return 3;
}
}
package SuperClass;
public class Main {
public static void main(String args[]){
SubClass s = new SubClass();
System.out.println(s.getNb()); //returns 1
System.out.println(s.getNb2()); //returns 3
SuperClass sup = new SuperClass();
System.out.println(sup.getNb()); //returns 1
System.out.println(sup.getNb2()); //returns 2
}
}
== Java, implements ==
Una clase abstracta que agrupa mètodes buits, que s’han d’implementar en altres classes
package Animals;
interface Animal {
public void animalSound();
public void sleep();
}
package Animals;
class Anec implements Animal {
public void animalSound() {
System.out.println("L'anec fa: quac quac");
}
}
package Animals;
class Gos implements Animal {
public void animalSound() {
System.out.println("El gos diu: guau guau");
}
public void sleep() {
System.out.println("Zzz");
}
}
package Animals;
public class Main {
public static void main(String args[]){
Anec a = new Anec();
Gos g = new Gos();
a.animalSound();
g.animalSound();
}
}
== Java, mètodes abstractes ==
Les classes abstractes es declaren sense implementació, no es poden instanciar però se’n poden fer subclasses. Pot implementar algún mètode.
package Animals2;
abstract class Animal {
public abstract void animalSound();
public void sleep() {
System.out.println("Zzz");
}
}
package Animals2;
class Anec extends Animal {
public void animalSound() {
System.out.println("L'anec fa: quac quac");
}
}
package Animals2;
class Gos extends Animal {
public void animalSound() {
System.out.println("El gos diu: guau guau");
}
public void sleep() {
System.out.println("Zzz");
}
}
package Animals2;
public class Main {
public static void main(String args[]){
Anec a = new Anec();
Gos g = new Gos();
a.animalSound();
g.animalSound();
}
}
==== Prototype ====
Prototype és un patró que ens permet crear còpies d’objectes sense que dependre de les seves classes.
Quan volem una còpia exacte d’un objecte tenim algun problema:
- Els objectes poden tenir mètodes privats que no podem veure
- Com que cal conèixer la classe original, pot ser que implementi una altre classe
El patró “prototype” delega el procés de clonar un objecte als propis objectes que estan essent clonats.
Per fer-ho, es crea una “interficie” amb almenys el mètode “clone” que crea un objecte de la classe actual amb tots els valors de l’objecte antic
Quan es fa servir Prototype?
- Quan el teu codi no hagi de dependre de les classes que necessites copiar
- Imagina que tens una clase complexa que és complicada de configurar, prototype permet tenir diferents objectes configurats llestos per clonar i usar
Amb prototype hem de crear el mètode 'clone()' que s'encarrega de fer una instància que és còpia exacte del mateix element. Per fer-ho cal tenir un constructor que accepti la pròpia Classe com a paràmetre.
import java.util.Objects;
public abstract class Poligon {
public int x;
public int y;
public String color;
public Poligon() {}
public Poligon(Poligon target) {
if (target != null) {
this.x = target.x;
this.y = target.y;
this.color = target.color;
}
}
public abstract Poligon clone();
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Poligon)) return false;
Poligon cast2 = (Poligon) object2;
return cast2.x == x && cast2.y == y && cast2.color.equals(color);
}
}
public class Cercle extends Poligon {
public int radius;
public Cercle() {
}
public Cercle(Cercle target) {
super(target);
if (target != null) {
this.radius = target.radius;
}
}
@Override
public Poligon clone() {
return new Cercle(this);
}
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Poligon) || !super.equals(object2)) return false;
Cercle cast2 = (Cercle) object2;
return cast2.radius == radius;
}
}
== Exemple de Prototype ==
{{ ::designpatterns-exemple_prototype.zip |}}
==== Factory ====
Substituir la creació d’objectes a través de ‘new’ per crides a metodes ‘factory’ específics de cada classe.
Els objectes es segueixen creant amb ‘new’, però es fa de manera interna al mètode ‘factory’
El objectes creats a través de ‘factory’ s’anomenen ‘products’
Quina és la motivació de Factory?
- Els clients d’una llibreria no tenen que saber com funciona aquesta llibreria, o fins i tot el tipus d’objecte o constructors que es fan servir.
- Amb factory tens un ‘product’ que declara una interfície comú a tots els objectes i les seves subclasses
- Cal que hi hagi una classe ‘creator’ que té el mètode per fabricar ‘products’
Quan es fa servir factory?
- Quan no sabem amb quins tipus d’objectes i dependencies haurà de treballar el nostre codi
- Quan volem donar maneres d’extendre els components de la nostre llibreria
- Quan volem estalviar recursos del sistema, aprofitant objectes enlloc de refentlos cada vegada
En Java Factory es pot implementar a través de:
- interface i classes que implementen les seves funcions
- amb classes derivades (extends) i sobreescrivint les funcions necessàries
Transportation.java
public interface Transportation {
void deliverPackage();
}
FactoryTransportation.java
public class FactoryTransportation {
public static Transportation getTransportation(String method) {
if("ship".equalsIgnoreCase(method))
return new TransportationShip();
else if("truck".equalsIgnoreCase(method))
return new TransportationTruck();
else if("van".equalsIgnoreCase(method))
return new TransportationVan();
return null;
}
public static void deliver (Transportation transport) {
transport.deliverPackage();
}
}
TransportationShip.java
public class TransportationShip implements Transportation {
@Override
public void deliverPackage() {
System.out.println("Package is traveling across the ocean");
}
}
TransportationTruck.java
public class TransportationTruck implements Transportation {
@Override
public void deliverPackage() {
System.out.println("Highway traveling");
}
}
== Exemple de Factory ==
{{ ::designpatterns-exemple_factory.zip |}}
==== Singleton ====
Singleton assegura que només hi ha una instància d’una classe determinada
Crees un objecte, si al cap d’una estona tones a crear un objecte d’aquella classe reps l'original enlloc del nou
Permet accedir un objecte de manera global en un programa, protegint-lo de sobre-escriptures
Com es fa?
- Posar el constructor de la classe com a ‘private’
- Fer un mètode de creació ‘static’ que funciona com a constructor, però:
* El primer cop crida al constructor privat
*
* La resta de vegades, retorna l’objecte creat originalment
Avantages:
- Assegura que només hi ha una instància de la classe
- Es pot accedir a l’objecte de manera global
- Només s’inicia el primer cop
Crítiques:
- Es considera un ‘anti-patró’ que pot amagar un mal disseny de l’aplicació
- En entorns amb threads cal assegurar que diversos fils no creen un objecte ‘singelton’ diverses vegades
Exemple de Singleton:
public final class ExempleSingleton {
private static ExempleSingleton instance;
public String value;
private ExempleSingleton(String value) {
this.value = value;
}
public static ExempleSingleton getInstance(String value) {
if (instance == null) {
instance = new ExempleSingleton(value);
}
return instance;
}
}
Els Singletons també es poden 'hackejar', aquest és un exemple de com ignorar un Singleton per crear diverses instàncies:
ExempleSingleton instanceOne = ExempleSingleton.getInstance("Hola");
ExempleSingleton instanceTwo = null;
try {
Constructor[] constructors = ExempleSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
//Below code will destroy the singleton pattern
constructor.setAccessible(true);
instanceTwo = (ExempleSingleton) constructor.newInstance("Adeu");
break;
}
} catch (Exception e) { e.printStackTrace(); }
Què passa amb els objectes serialitzables?
Tot i que hi ha maneres d’implementar Singleton en objectes serialitzables, és fàcil trencar l’esquema i crear diverses instàncies serialitzant i des-serialitzant els objectes cada vegada que es vol una instància nova.
== Exemple de Singleton ==
{{ ::designpatterns-exemple_singleton.zip |}}
==== DAO, Data Access Object ====
DAO es fa servir per separar la manera de guardar les dades i la l’estructura lògica amb la que s’hi treballa a nivell de programació.
Una interfície base DAO defineix les funcions que implementen tots els objectes DAO
Cada objecte DAO implementa la transformació entre la seva Classe i on estan emmagatzemades les dades
Una manera habitual de definir quines funcions ha de implementar DAO és CRUD, que significa:
- Create
- Read
- Update
- Delete
I són les operacions més habituals al treballar amb models de dades
public interface Dao {
void add(T t); // Equival a Create
T get(int id); // Equival a Read
ArrayList getAll();
void update(int id, T t);
void delete(int id);
void print();
}
Avantatges de DAO
- Simplifica el treball dels programadors al abstreure l’estructura de dades de l’arquitectura del programa
- Unifica la manera de fer les crides pels diferents tipus d’objectes independentment de com es guarden les seves dades
Inconvenients:
- A vegades es defineixen massa funcions DAO fent que sigui difícil de mantenir o impossible d’aplicar a totes les dades per igual
- Resta flexibilitat, fent que algunes crides/consultes a la base de dades no puguin estar optimitzades
- Separar la lògica de les dades pot implicar necessitar operacions “extra” per guardar les modificacions del model de dades
== Exemple de DAO ==
{{ ::designpatterns-exemple_dao.zip |}}
==== Observer ====
Es crida a un mètode quan una variable cambia de valor.
Tradicionalment un objecte, manté una llista d’observadors que criden a mètodes quan hi ha canvis a l’estat de l’objecte (valors de les variables).
No hi ha una manera específica d’implementar l’observer, alguns llenguatges l’implementen de base, altres tenen llibreries específiques i normalment el programador s’ho implementa com millor li convé.
Usos més habituals d'Observer:
- En interfícies gràfiques quan un usuari canvia un ‘input’ (text, select, checkbox, …) per executar funcions que s'actualitzen les dades cap a una base de dades remota o algun altre element visual de la eina.
- Quan les dades canvien remotament i rebem els nous valors, aquests han d’actualitzar la interfície gràfica automàticament. O executar processos que tractin aquestes noves dades rebudes adeqüadament.
- Quan s’observa una carpeta d’arxius o un arxius i si hi ha canvis s’executa una acció (normalment ho fa un ‘thread’ paral·lel al procés)
En java cal definir l'acció a fer quan hi han canvis, es fa a través de la funció "**propertyChange**":
Observable obsNum = new Observable(0) {
@Override
public void propertyChange(Integer oldValue, Integer newValue) {
System.out.printf("obsNum ha canviat de %s cap a %s\n", oldValue, newValue);
}
};
Observable.java
public abstract class Observable {
private T value;
public Observable(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
T oldValue = this.value;
this.value = value;
this.propertyChange(oldValue, value);
}
public abstract void propertyChange(T oldValue, T newValue);
}
Cada classe que vol observar alguna propietat necessita una llista d'observadors tipus **PropertyChangeSupport**
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
public class CotxeEvents {
private PropertyChangeSupport llistaObservers = new PropertyChangeSupport(this);
private CotxeEstats estat;
private int gasolina;
CotxeEvents () {
this.estat = CotxeEstats.ATURAT;
this.gasolina = 3;
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
llistaObservers.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
llistaObservers.removePropertyChangeListener(name, listener);
}
public void setEstat (CotxeEstats newValue) {
CotxeEstats oldValue = this.estat;
if (oldValue != newValue) {
if (newValue != CotxeEstats.ATURAT) {
this.setGasolina(this.gasolina - 1);
}
if (this.gasolina > 0) {
this.estat = newValue;
llistaObservers.firePropertyChange("estat", oldValue, newValue);
}
}
}
public void setGasolina (int newValue) {
int oldValue = this.gasolina;
if (newValue > 0) {
this.gasolina = newValue;
} else {
this.gasolina = 0;
this.setEstat(CotxeEstats.ATURAT);
}
if (oldValue != this.gasolina) {
llistaObservers.firePropertyChange("gasolina", oldValue, newValue);
}
}
}
== Exemple de Observer ==
{{ ::designpatterns-exemples_observer.zip |}}