image/svg+xml $ $ ing$ ing$ ces$ ces$ Res Res ea ea Res->ea ou ou Res->ou r r ea->r ch ch ea->ch r->ces$ r->ch ch->$ ch->ing$ T T T->ea ou->r

Les patterns comportementaux permettent de faciliter la coopération entre différents objets pour implanter des actions à réaliser.

Patron de méthode (template method)

Exemple : repas d'animaux

public abstract class Animal
{
	public void takeMeal(FoodStore fs)
	{
		eat(fs);
		drink(fs);
	}
	
	public void drink(FoodStore fs)
	{
		fs.takeWater(getDrinkPortion());
	}
	
	/** The normal drink portion of the animal in liters, to be implemented in concrete classes */
	public abstract double getDrinkPortion();
	
	/** Eat a portion of food (specifical for each animal) */
	public abstract void eat(FoodStore fs);
}

public class Cat extends Animal
{
	public double getDrinkPortion() { return 0.1; }
	
	public void eat(FoodStore fs)
	{
		fs.takeMeat(0.2);
	}
}

public class Dog extends Animal
{
	public double getDrinkPortion() { return 0.2; }

	public void eat(FoodStore fs)
	{
		fs.takeMeat(0.5);
	}
}

Patterns strategy et state

Présentation

Exemple 1 : TVA variable

public abstract class BuyableItem
{
	private VATComputer vat;
	
	public BuyableItem(VATComputer vat)
	{
		this.vat = vat;
	}
	
	public abstract int getRawPrice();

	public int getTaxIncludedPrice()
	{
		return vat.computeTaxIncludedPrice(getRawPrice());
	}
}

public interface VATComputer
{
	public int computeTaxIncludedPrice(int rawPrice);
}

Remarque : VATComputer pourrait être une classe Enum si les différents taux de TVA sont fixés une fixés une fois pour toute à la compilation.

Exemple 2 : une voiture hybride

/** Implementation of an hybrid car using first the electric propulsion then the fuel propulsion if the battery is exhausted */
public class HybridCar
{
	protected PropulsionStrategy propulsionStrategy =
		PropulsionStrategies.ELECTRIC;
	
	/** Drive with the car (distance in meters) */
	public double drive(double distance)
	{
		double driven = propulsionStrategy.drive(distance);
		if (driven < distance)
			if (propulsionStrategy == PropulsionStrategies.ELECTRIC)
			{
				// we switch to the fuel propulsion since the battery is exhausted
				propulsionStrategy = PropulsionStrategies.FUEL;
				// drive the remaining distance with the fuel tank
				driven += propulsionStrategy.drive(distance - driven);
			}
		return driven; // return the driven distance
	}

Observateur (observer)

Présentation

Exemple : observation de lignes de texte

public class LineReverser implements Observer
{
	/** This method is called by the observable when a new line arrives
	public void update(Observable o, Object arg)
	{
		String reverse = reverse((String)arg);
		System.out.println(reverse);
	}
	
	public static String reverse(String s)
	{
		return new StringBuilder(s).reverse().toString();
	}
}

public class LineGetter extends Observable implements Runnable
{
	private Scanner scanner;
	
	public LineGetter(Scanner s)
	{
		scanner = s;
	}
	
	public void run()
	{
		try {
			while (scanner.hasNextLine())
			{
				String line = scanner.nextLine();
				notifyObservers(line);
			}
		} catch (IOException e)
		{
			System.err.println("An error occurred: " + e);
		} finally
		{
			try { scanner.close(); }
			catch (Exception e) {}
		}
	}
}

public class LineMain
{
	public static void main(String[] args)
	{
		LineGetter lg = new LineGetter(new Scanner(System.in));
		LineReverser lr = new LineReverser();
		lg.addObserver(lr);
		try {
			lg.run();
		} finally
		{
			lg.deleteObserver(lr); // do not forget to avoid memory leaks
		}
	}
}

Chaîne de responsabilité

Exemple 1 : traitement d'événement pour une interface graphique

Exemple 2 : broadcast ordonné sous Android

Mediateur (mediator)

Exemple : un gestionnaire de virements bancaires

public interface BankAccount
{
	/** Withdraw the given amount and return the new balance of the account */
	public int withdraw(int amount);
	
	/** Deposit the given amount and return the new balance */
	public int deposit(int amount);
}

public class BankTransferer
{
	private TransferAuthorizer authorizer; // we delegate the authorization for the transfor to a dedicated object
	
	public void transfer(BankAccount source, BankAccount destination, int amount)
	{
		int balance = source.withdraw(amount);
		if (balance < 0)
		{
			source.deposit(amount); // we recredit the source since the transfer is not possible
			throw new IllegalStateException("Not enough balance on the source account");
		} else
		{
			if (authorizer.isAuthorizedTransfer(source, destination, amount))
				destination.deposit(amount);
			else
			{
				source.deposit(amount); // we recredit the source since the transfer is not authorized
				throw new RuntimeException("Forbidden transfer");
			}
		}
	}
}

Memento

Example : un wiki

public class WikiPage {
	private final String title;
	private String content;
	private Archive archive;
	private int version = 0;

	public WikiPage(String title, String content, Archive archive) {
		this.title = title;
		this.content = content;
		this.archive = archive;
	}

	public void setContent(String newContent) {
		if (! content.equals(newContent)) {
			content = newContent;
			StringsComparator cmp = new StringsComparator(content, newContent);
			EditScript<Character> script = cmp.getScript();
			archive.postDiff(title, version++, script);
		}
	}

	public void restoreVersion(int wantedVersion) {
		var editScript = archive.getDiff(version, wantedVersion);
		// TODO: apply the edit script
	}
}

Commande

Exemple : une interface graphique de dessin

Visiteur

Présentation

Exemple : visiteurs sur expression arithmétiques

public interface Expr
{
	public <R> R apply(Visitor<R> visitor);
}

public class IntegerExpr implements Expr
{
	public int number;
	
	public IntegerExpr(int number) { this.number = number; }
	
	@Override
	public <R> R apply(Visitor<R> visitor) { return visitor.visit(this); }
}

public abstract class BinaryExpr implements Expr
{
	public Expr operand1, operand2;
	
	public BinaryExpr(Expr operand1, Expr operand2)
	{
		this.operand1 = operand1;
		this.operand2 = operand2;
	}
}

public class AdditionExpr extends BinaryExpr
{
	public AdditionExpr(Expr operand1, Expr operand2) { super(operand1, operand2); }
	
	@Override
	public <R> R apply(Visitor<R> visitor) { return visitor.visit(this); }
}

public class ProductExpr extends BinaryExpr
{
	public ProductExpr(Expr operand1, Expr operand2) { super(operand1, operand2); }
	
	@Override
	public <R> R apply(Visitor<R> visitor) { return visitor.visit(this); }
}

/** Interface for visitor
 * The visitors should implements all the visit method (otherwise an exception could be raised)
 */
public interface Visitor<R>
{
	public default R visit(Expr expr) { throw new RuntimeException("unimplemented"); }
	public default R visit(IntegerExpr expr) { throw new RuntimeException("unimplemented"); }
	public default R visit(BinaryExpr expr) { throw new RuntimeException("unimplemented"); }
	public default R visit(AdditionExpr expr) { throw new RuntimeException("unimplemented"); }
	public default R visit(ProductExpr expr) { throw new RuntimeException("unimplemented"); }
}

/** A visitor to compute the value of the expression */
public class ComputerVisitor extends Visitor<Integer>
{
	public Integer visit(IntegerExpr expr) { return expr.number; }
	public Integer visit(AdditionExpr expr) { return expr.operand1.apply(this) + expr.operand2.apply(this); }
	public Integer visit(ProductExpr expr) { return expr.operand1.apply(this) * expr.operand2.apply(this); }
}

/** A visitor to display the expression using an infix format */
public class InfixDisplayVisitor extends Visitor<String>
{
	public String visit(IntegerExpr expr) { return "" + expr.number; }
	public String visit(AdditionExpr expr) { return "(" + expr.operand1.apply(this) + "+" + expr.operand2.apply(this) + ")"; }
	public String visit(ProductExpr expr) { return "(" + expr.operand1.apply(this) + "*" + expr.operand2.apply(this) + ")"; }
}

⚠ Il est obligatoire de redéfinir la méthode apply sur tous les types de noeuds (même si son implantation est la même) pour permettre le double-dispatch.