I made this project as an easy way to access and revise design patterns.
There are plenty of resources online about design patterns, so why did i made mine?
This is because the resources i found:
- Do not have the right balance between too verbose and straight to the point explanation.
- Are not easily searchable: why so many pages-subpages where i can’t just ctrl+f (or cmd+f), and also always so many clicks away to knowing the solution.
So here you go, take a look into this fabulous handbook of design patterns!
Creational
Now if you change what object is instantated in one of the 2 instantiation, for example in the It can also happen that you extends the Factory method pattern propose a solution where you refactor your code to instantiate the object only in one part of your code, so the error could not happen. In the example above, you will instantiate the object Using static factory method pattern, instead of typing So if you want to add another field, say hours, clients won’t break because they will still call the static method correctly, you are the one that has to change the Notice that: You can see that as the complexity of the object grows, the constructor gets crammed with more and more parameters, which is not optimal. Again this is not optimal with complex objects. The code to do so is the following: Notice how: Notice that:factory method
problem description
MyType
with 2 implementation MyImpl
and MyOtherImpl
and suppose this class is used by your application class, like this:
class MyType { }
class MyImpl extends MyType { }
class MyOtherImpl extends MyType { }
class App {
private MyType mt;
public void init () {
mt = new MyImpl();
}
public void destroy () {
mt = new MyImpl();
}
}
destroy
method, but you forget to change it in the other method, the code will work, but it won’t be correct.class App {
private MyType mt;
public void init () {
mt = new MyImpl();
}
public void destroy () {
mt = new MyOtherImpl(); // I change it here but i forget to change it in init
}
}
App
class, you override only one of the 2 method with another implementation and then you forgot to override the other.
solution using factory method pattern
class App {
private MyType mt;
public MyType createMyType() {
return new MyImpl();
}
public void init () {
mt = createMyType();
}
public void destroy () {
mt = createMyType(); // I change it here but i forget to change it in init
}
}
static factory method
problem description
solution 1. (bad) use constructor overload
class TimeSpan {
private int seconds;
private int minutes;
private int hours;
private TimeSpan (int seconds, int minutes, int hours) {
this.seconds = seconds;
this.minutes = minutes;
this.hours = hours;
}
}
TipeStamp
with new TipeStamp(0,10,0)
. But if you only read this without looking at the implementation of TipeStamp
you can’t understand how the object is instantiated.solution 2. using static factory method pattern
new MyType()
you will write MyType.create()
.class TimeSpan {
private int seconds;
private int minutes;
private TimeSpan (int seconds, int minutes) {
this.seconds = seconds;
this.minutes = minutes;
}
public static TimeSpan ofSeconds (int seconds) {
return new TimeSpan(seconds, 0);
}
public static TimeSpan ofMinutes (int minutes) {
return new TimeSpan(0, minutes);
}
}
TimeSpan
implementation.class TimeSpan {
private int seconds;
private int minutes;
private int hours;
private TimeSpan (int seconds, int minutes, int hours) {
this.seconds = seconds;
this.minutes = minutes;
this.hours = hours;
}
public static TimeSpan ofSeconds (int seconds) {
return new TimeSpan(seconds, 0, 0);
}
public static TimeSpan ofMinutes (int minutes) {
return new TimeSpan(0, minutes, 0);
}
public static TimeSpan ofHours (int hours) {
return new TimeSpan(0, 0, hours);
}
}
abstract factory
problem description
solution using abstract factory pattern
VehicleFactory carFactory = new CarFactory();
VehicleFactory bikeFactory = new BikeFactory();
Vehicle myCar = carFactory.createVehicle();
Vehicle myBike = bikeFactory.createVehicle();
myCar.getType(); // "Car"
myBike.getType(); // "Bike"
interface Vehicle {
String getType();
}
class Car implements Vehicle {
@Override
public String getType() {
return "Car";
}
}
class Bike implements Vehicle {
@Override
public String getType() {
return "Bike";
}
}
abstract class VehicleFactory {
abstract Vehicle createVehicle();
}
class CarFactory extends VehicleFactory {
@Override
Vehicle createVehicle() {
return new Car();
}
}
class BikeFactory extends VehicleFactory {
@Override
Vehicle createVehicle() {
return new Bike();
}
}
notes
example class diagram
textbook class diagram
builder
problem description
solution 1. overloading the constructor
class Car {
private String engine; // This is not optional
private String transmission; // This is optional
private int airbags; // This is optional
Car (String engine) { ... }
Car (String engine, String transmission) { ... }
Car (String engine, String transmission, int airbags) { ... }
}
solution 2. using setters
Car car = new Car("V8");
car.setTransmission("automatic");
car.setAirbags(5);
solution 3. using builder pattern
Car
object you will do something like this
Car car = new Car.CarBuilder("V6")
.withTransmission("Automatic")
.withAirbags(4)
.build();
System.out.println(car);
// Prints: Car [engine=V6, transmission=Automatic, airbags=4]
class Car {
private String engine; // This is not optional
private String transmission; // This is optional
private int airbags; // This is optional
// Private constructor only accessible by builder
private Car () { }
public static class CarBuilder {
private String engine;
private String transmission = "Manual"; // Default value
private int airbags = 2;
public CarBuilder (String engine) {
this.engine = engine; // This way we are enforcing the car to have at least a motor (the non optional field)
}
public CarBuilder withTransmission (String transmission) {
this.transmission = transmission;
return this;
}
public CarBuilder withAirbags (int airbags) {
this.airbags = airbags;
return this;
}
public Car build () {
Car car = new Car(); // It can access private member
car.engine = engine;
car.transmission = transmission;
car.airbags = airbags;
return car;
}
}
@Override
public String toString () {
return "Car [engine=" + engine + ", transmission=" + transmission + ", airbags=" + airbags + "]";
}
}
Car
class is kept private. Only the Builder class can call it.Car
class.fluent interface
problem description
solution using fluent interface pattern
class Car {
private String engine;
private String transmission;
private int airbags;
public Car (String engine) {
this.engine = engine;
}
public Car transmission (String transmission) {
this.transmission = transmission;
return this;
}
public Car airbags (int airbags) {
this.airbags = airbags;
return this;
}
@Override
public String toString () {
return "Car [engine=" + engine + ", transmission=" + transmission + ", airbags=" + airbags + "]";
}
}
public class Client {
public static void main (String[] args) {
Car car = new Car("V6")
.transmission("Automatic")
.airbags(4);
System.out.println(car);
// Prints: Car [engine=V6, transmission=Automatic, airbags=4]
}
}
singleton
problem description
using singleton pattern
public class App {
private static App instance = null;
private App () { }
public static App getInstance () {
if (instance == null) {
instance = new App();
}
return instance;
}
}
Structural
You have an object that has to interact with a broad set of objects that belong to a sophisticated library.
To do that you would normally initilize all the objects, execute methods and so on. The result is that the business logic will be tightly coupled with the implementation of 3rd-party classes, making it hard to mantain. We want to allow objects with incompatible interfaces to work together. Suppose you have an app that displays menus using XML data, like this. One day, you want to enhance how you display menus, and you find the perfect UI library.
The problem is that this library uses JsonData to display menus! So how can we make the 2 components work together? Note that if decorator
problem description
solution using decorator pattern
ConcreteComponent component = new ConcreteComponent();
Decorator decorator = new Decorator(component);
decorator.someLogic(); //outputs:
// Some logic by decorator part 1
// Some logic by Component
// Some logic by decorator part 2
decorator.someDecoratorNewLogic(); // outputs:
// I can do logic without using the decorator component
interface Component {
void someLogic ();
}
class ConcreteComponent implements Component{
@Override
public void someLogic () {
System.out.println("Some logic by Component");
}
}
class Decorator implements Component{
private Component wrappee;
public Decorator (Component wrappee) {
this.wrappee = wrappee;
}
@Override
public void someLogic () {
System.out.println("Some logic by decorator part 1");
wrappee.someLogic();
System.out.println("Some logic by decorator part 2");
}
public void someDecoratorNewLogic () {
System.out.println("I can do logic without using the decorated component");
}
}
notes
example class diagram
example with multiple decorators
ConcreteComponent component = new ConcreteComponent();
Decorator decorator1 = new ConcreteDecorator1(component);
decorator1.someLogic();
Decorator decorator2 = new ConcreteDecorator2(component);
decorator2.someLogic();
interface Component {
void someLogic ();
}
class ConcreteComponent implements Component{
@Override
public void someLogic () {
System.out.println("Some logic by Component");
}
}
abstract class Decorator implements Component{
protected Component wrappee;
public Decorator (Component wrappee) {
this.wrappee = wrappee;
}
@Override
public void someLogic () {
wrappee.someLogic();
}
}
class ConcreteDecorator1 extends Decorator{
public ConcreteDecorator1 (Component wrappee) {
super(wrappee);
}
@Override
public void someLogic () {
System.out.println("Some logic by Decorator1 part 1");
wrappee.someLogic();
System.out.println("Some logic by Decorator1 part 2");
}
}
class ConcreteDecorator2 extends Decorator{
public ConcreteDecorator2 (Component wrappee) {
super(wrappee);
}
@Override
public void someLogic () {
System.out.println("Some different logic by Decorator2 part 1");
wrappee.someLogic();
System.out.println("Some different logic by Decorator2 part 2");
}
}
class diagram
facade
problem description
using facade pattern
ComputerFacade computer = new ComputerFacade();
computer.start();
interface CPU {
void freeze ();
void jump (int position);
void execute ();
}
interface HardDrive {
String read(int lba, int size);
}
interface Memory {
void load(int position, String data);
}
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public void start () {
cpu.freeze();
memory.load(kBootAddress, hardDrive.read(kBootSector, kSectorSize));
cpu.jump(kBootAddress);
cpu.execute();
}
}
notes
facade & decorator
adapter
problem description
interface IApp {
void displayMenus (XmlData xmlData);
}
class App implements IApp {
@Override
public void displayMenus (XmlData xmlData) {
// Displays menu using XML data
}
}
class FancyUIService {
public void displayMenus (JsonData jsonData) {
// Make use of the JsonData to fetch menus
}
}
solution using adapter design pattern
XmlData xmlDataToDisplay = new XmlData();
IApp app = new App();
app.displayMenus(xmlDataToDisplay); // Show menu using old UI
IApp adapter = new FancyUIServiceAdapter();
adapter.displayMenus(xmlDataToDisplay); // Show menu using new fancy UI
class FancyUIServiceAdapter
extends FancyUIService
implements IApp{
@Override
public void displayMenus (XmlData xmlData) {
super.displayMenus(convertXmlToJson(xmlData));
}
private JsonData convertXmlToJson (XmlData xmlData) {
// Convert XmlData to JsonData and return it
}
}
FancyUIService
is final you can use composition instead of hineritance and do like this:
class FancyUIServiceAdapter implements IApp{
private final FancyUIService fancyUIService;
public FancyUIServiceAdapter () {
this.fancyUIService = new FancyUIService();
}
@Override
public void displayMenus (XmlData xmlData) {
super.displayMenus(convertXmlToJson(xmlData));
}
private JsonData convertXmlToJson (XmlData xmlData) {
// Convert XmlData to JsonData and return it
}
}
notes
example class diagram
adapter & decorator
Behavioral
This way, each The problem is the same as the observer pattern: we have a Subject we want to observe for it’s updates. This time though there is a twist: in this case a message is not broadcasted to all observers but a subscriber can subscribe to different topics he is interested into. Dependency Injection is different in that the user is not trying to change the runtime behaviour.
Following the example above, we might be creating an XML export program that uses an XML formatter. Rather than structuring the code like this: you would ‘inject’ the formatter in the constructor:
Differences: You have one (or more) class(es) where you want to add a new functionality.
For example you have a Now you want to add an This one works fine, but we have modified the code of You can see that in this case it does not work.
Pay attention, it’s obvious that if you do This one will output correctly Notice how we are in fact changing the You might be thinking that if you need to add another operation, for example a But this is not the case, you can just abstract all the visitors in an interface and create just 1 accept method for all visitors, like this: They have 2 different intent: The problem of this code is that if i change the implementation of It exposes only 2 interface method: observer
problem description
Subject
has a state and one or more object(s) Observer
s wants to know when the Subject
state changes.
solution 1. (bad) polling
Observer
will constantly look at the state inside Subject
. This solution is inefficient.
solution 2. (good) use observer pattern
Observer
subscribes to a Subject
and it’s the Subject
who has the task of informing all of it’s Observer
s when the state changes.import java.util.ArrayList;
import java.util.List;
// Subject we want to observe
class Subject {
public int state = 1;
private List<Observer> observers = new ArrayList<>();
public void addObserver (Observer observer) {
observers.add(observer);
}
public void removeObserver (Observer observer) {
observers.remove(observer);
}
public void notifyObservers () {
for (Observer observer : this.observers) {
observer.update();
}
// observers.forEach(Observer::update); // It's the same
}
public void someLogic () { // This logic can be even a setter for the state
state = state * 2;
notifyObservers(); // Be aware of this
}
}
interface Observer {
void update ();
}
class ConcreteObserver1 implements Observer {
private Subject sub;
public ConcreteObserver1 (Subject sub) {
this.sub = sub;
}
public void update () {
System.out.println("Subject state is " + sub.state + " (from ConcreteObserver1)");
}
}
class ConcreteObserver2 implements Observer {
private Subject sub;
public ConcreteObserver2 (Subject sub) {
this.sub = sub;
}
public void update () {
System.out.println("Subject state is " + sub.state + " (from ConcreteObserver2)");
}
}
public class Client {
public static void main (String[] args) {
Subject s = new Subject();
Observer observer1 = new ConcreteObserver1(s);
Observer observer2 = new ConcreteObserver2(s);
s.addObserver(observer1);
s.addObserver(observer2);
s.someLogic();
// Will output:
// Subject state is 2 (from ConcreteSubscriber1)
// Subject state is 2 (from ConcreteSubscriber2)
s.someLogic();
// Will output:
// Subject state is 4 (from ConcreteSubscriber1)
// Subject state is 4 (from ConcreteSubscriber2)
}
}
notes
(example) class diagram
abstracting observer logic handling
Subject
in an abstract class Subject
inherit from, like this
abstract class AbstractSubject {
private List<Observer> observers = new ArrayList<>();
public void addObserver (Observer observer) {
observers.add(observer);
}
public void removeObserver (Observer observer) {
observers.remove(observer);
}
public void notifyObservers () {
for (Observer observer : this.observers) {
observer.update();
}
// observers.forEach(Observer::update); // It's the same
}
}
class Subject extends AbstractSubject{
public int state = 1;
public void someLogic () { // This logic can be even a setter for the state
state = state * 2;
notifyObservers(); // Be aware of this
}
}
publish-subscriber
problem description
solution using publish-subscriber pattern
MessageBroker broker = new MessageBroker();
// Subscribers si iscrivono a topic specifici
Subscriber sub1 = new ConcreteSubscriber1();
Subscriber sub2 = new ConcreteSubscriber2();
broker.subscribe("news", sub1);
broker.subscribe("news", sub2);
broker.subscribe("updates", sub2); // sub2 also subscribes to a different topic
Publisher publisher = new Publisher(broker);
publisher.sendMessage("news", "Breaking News!");
// ConcreteSubscriber1 received: Breaking News!
// ConcreteSubscriber2 received: Breaking News!
publisher.sendMessage("updates", "System update available.");
// ConcreteSubscriber2 received: System update available.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Publisher {
private MessageBroker broker;
public Publisher(MessageBroker broker) {
this.broker = broker;
}
public void sendMessage(String topic, String message) {
broker.publish(topic, message);
}
}
class MessageBroker {
private Map<String, List<Subscriber>> subscribers = new HashMap<>();
public void subscribe(String topic, Subscriber subscriber) {
this.subscribers.putIfAbsent(topic, new ArrayList<>());
this.subscribers.get(topic).add(subscriber);
}
public void publish(String topic, String message) {
if (this.subscribers.containsKey(topic)) {
for (Subscriber subscriber : subscribers.get(topic)) {
subscriber.receive(message);
}
}
}
}
interface Subscriber {
void receive(String message);
}
class ConcreteSubscriber1 implements Subscriber {
public void receive(String message) {
System.out.println("ConcreteSubscriber1 received: " + message);
}
}
class ConcreteSubscriber2 implements Subscriber {
public void receive(String message) {
System.out.println("ConcreteSubscriber2 received: " + message);
}
}
notes
(example) class diagram
strategy (and dependency injection)
problem description
Shop
and we want to decide the discount algorithm (called with getTotal
) at runtime.
Shop s = new Shop();
int total = s.getTotal(100);
System.out.println(total); // prints 90 because we are applying a discount of 10%
solution 1. use an enum (bad)
Shop s = new Shop(DiscountType.ABSOLUTE_DISCOUNT);
int total = s.getTotal(100);
System.out.println(total); // prints 90
enum DiscountType {
NO_DISCOUNT,
ABSOLUTE_DISCOUNT,
PERCENTAGE_DISCOUNT
}
class Shop {
private DiscountType dt;
public int absoluteDiscount = 10;
public int percentageDiscount = 20;
public Shop (DiscountType dt) {
this.dt = dt;
}
public void setDiscountType (DiscountType dt) {
this.dt = dt;
}
public int getTotal (int originalPrice) {
switch (dt) {
case NO_DISCOUNT:
return originalPrice;
case ABSOLUTE_DISCOUNT:
return originalPrice - absoluteDiscount;
case PERCENTAGE_DISCOUNT:
return originalPrice - (originalPrice*100/percentageDiscount);
}
return 0;
}
}
solution 2. use strategy pattern (good)
DiscountStrategy ds = new AbsoluteDiscountStrategy(10);
Shop s = new Shop(ds);
int total = s.getTotal(100);
System.out.println(total); // prints 90
interface DiscountStrategy {
int applyDiscount (int originalPrice);
}
class NoDiscountStrategy implements DiscountStrategy {
@Override
public int applyDiscount (int originalPrice) {
return originalPrice;
}
}
class AbsoluteDiscountStrategy implements DiscountStrategy {
private int discount;
public AbsoluteDiscountStrategy (int discount) {
this.discount = discount;
}
@Override
public int applyDiscount (int originalPrice) {
return originalPrice - discount;
}
}
class PercentaceDiscountStrategy implements DiscountStrategy {
private int percentage;
public PercentaceDiscountStrategy (int percentage) {
this.percentage = percentage;
}
@Override
public int applyDiscount (int originalPrice) {
return originalPrice - (originalPrice - originalPrice*percentage/100);
}
}
class Shop {
private DiscountStrategy ds;
public Shop (DiscountStrategy ds) {
this.ds = ds;
}
public void setDiscountStrategy (DiscountStrategy ds) {
this.ds = ds;
}
public int getTotal (int originalPrice) {
return ds.applyDiscount(originalPrice);
}
}
notes
class diagram
dependency injection
public class DataExporter() {
XMLFormatter formatter = new XMLFormatter();
}
public class DataExporter {
IFormatter formatter = null;
public DataExporter(IDataFormatter dataFormatter) {
this.formatter = dataFormatter;
}
}
DataExporter exporter = new DataExporter(new XMLFormatter());
strategy & visitor
state
probelem description
solution 1. use enum & if statement (bad)
Phone phone = new Phone();
JButton home = new JButton("Home");
home.addActionListener(e -> phone.onHomeButton());
JButton onAndOff = new JButton("on/off");
onAndOff.addActionListener(e -> phone.onPowerButton());
enum State {
OFF,
LOCKED,
READY
}
class Phone {
private State state;
public Phone () {
this.state = State.OFF;
}
public void onPowerButton () {
if (this.state == State.OFF) {
this.state = State.LOCKED;
} else if (this.state == State.LOCKED) {
this.State = State.OFF;
} else if (this.State == State.READY) {
this.state = State.OFF;
}
}
public void onHomeButton () {
if (this.state == State.OFF) {
this.state = State.LOCKED;
} else if (this.state == State.LOCKED) {
this.State = State.READY;
} else if (this.State == State.READY) { }
}
}
solution 2. use state design pattern (good)
Phone phone = new Phone();
JButton home = new JButton("Home");
home.addActionListener(e -> phone.getState().onHomeButton());
JButton onAndOff = new JButton("on/off");
onAndOff.addActionListener(e -> phone.getState().onPowerButton());
class Phone {
private State state;
public Phone () {
state = new OffState(this);
}
public State getState () {
return this.state;
}
public void setState (State state) {
this.state = state;
}
}
abstract class State {
protected Phone phone;
public State (Phone phone) {
this.phone = phone;
}
public abstract void onHomeButton ();
public abstract void onPowerButton ();
}
class OffState extends State {
public OffState (Phone phone) {
super(phone);
}
@Override
public void onHomeButton () {
phone.setState(new LockedState(phone));
}
@Override
public void onPowerButton () {
phone.setState(new LockedState(phone));
}
}
class LockedState extends State {
public LockedState (Phone phone) {
super(phone);
}
@Override
public void onHomeButton () {
phone.setState(new ReadyState(phone));
}
@Override
public void onPowerButton () {
phone.setState(new OffState(phone));
}
}
class ReadyState extends State {
public ReadyState (Phone phone) {
super(phone);
}
@Override
public void onHomeButton () { }
@Override
public void onPowerButton () {
phone.setState(new OffState(phone));
}
}
notes
class diagram
state & strategy
visitor
problem description
Dot
class and a Circle
class, like this:interface Shape {
public void draw ();
}
class Dot implements Shape{
float x = 5, y = 8;
public void draw () { }
}
class Circle implements Shape{
float radius;
public void draw () { }
}
export
functionality.
You can do it in two ways.
solution 1. adding `export` functionality directly inside the 2 classes (good)
interface Shape {
public void draw ();
public void export ();
}
class Dot implements Shape {
float x = 5, y = 8;
public void draw () { }
public void export () {
System.out.println("Exporting Dot, x = " + d.x + ", y = " + d.y);
}
}
class Circle implements Shape{
float radius = 7;
public void draw () { }
public void export () {
System.out.println("Exporting Circle, radius = " + radius);
}
}
public class Client {
public static void main(String[] args) {
Shape dot = new Dot();
dot.export();
// outputs correctly: Exporting Dot, x = 5.0, y = 8.0
Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
for(Shape shape: shapeArray){
shape.export();
}
// outputs correctly:
// Exporting Circle, radius = 7.0
// Exporting Dot, x = 5.0, y = 8.0
}
}
Shape
and Dot
. What if we do not want to do that?solution 2. adding new class handling `export` functionality for all shapes (bad)
// Not touching Shapes
interface Shape {
public void draw ();
}
class Dot implements Shape {
float x = 5, y = 8;
public void draw (){ }
}
class Circle implements Shape {
float radius;
public void draw (){ }
}
class Exporter {
void export(Shape s) {
System.out.println("Exporting Shape");
}
void export(Dot d) {
System.out.println("Exporting Dot, x = " + d-x + ", d.y = " + y);
}
void export(Circle c) {
System.out.println("Exporting Circle, radius = " + c.radius);
}
}
public class Client {
public static void main(String[] args) {
Shape s = new Dot();
Exporter ex = new Exporter();
ex.export(s);
// outputs INcorrectly: Exporting Shape
Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
for(Shape shape: shapeArray){
ex.export(shape);
}
// outputs INcorrectly:
// Exporting Shape
// Exporting Shape
}
}
Dot s = new Dot();
Exporter ex = new Exporter();
ex.export(s);
Exporting Dot, x = 5.0, y = 8.0
because at static time we know that the shape is of type Dot
.
solution 2. using visitor pattern (good)
interface Shape {
public void draw ();
public void accept(ExportVisitor visitor);
}
class Dot implements Shape{
float x = 5, y = 8;
public void draw (){ }
public void accept(ExportVisitor visitor){
visitor.visit(this);
}
}
class Circle implements Shape{
float radius = 8;
public void draw (){ }
public void accept(ExportVisitor visitor){
visitor.visit(this);
}
}
class ExportVisitor {
void visit(Dot d){
System.out.println("Exporting Dot, x = " + d.x + ", y = " + d.y);
}
void visit(Circle c){
System.out.println("Exporting Circle, radius = " + c.radius);
}
}
public class Client{
public static void main(String[] args) {
Shape s = new Dot();
ExportVisitor ex = new ExportVisitor();
s.accept(ex);
// outputs correctly: Exporting Dot, x = 5.0, y = 8.0
Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
for(Shape shape: shapeArray){
shape.accept(ex);
}
// outputs correctly:
// Exporting Circle, radius = 7.0
// Exporting Dot, x = 5.0, y = 8.0
}
}
Dot
and Circle
code, but notice how the numbers of line changed inside a class is always constant, because you only just need to accept the visitor, whereas the Visitor logic might be big (now it’s just printing).notes
adding another operation with visitor
Transform
operation, You have to
TransformVisitor
Shape s = new Dot();
ExportVisitor ex = new ExportVisitor();
TransformVisitor tr = new TransformVisitor();
s.accept(ex);
s.accept(tr);
// outputs correctly:
// Exporting Dot, x = 5.0, y = 8.0
// Transforming Dot, 2*x = 10.0, 2*y = 16.0
Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
for(Shape shape: shapeArray){
shape.accept(ex);
shape.accept(tr);
}
// outputs correctly:
// Exporting Circle, radius = 7.0
// Transforming Circle, 2*radius = 16.0
// Exporting Dot, x = 5.0, y = 8.0
// Transforming Dot, 2*x = 10.0, 2*y = 16.0
interface Shape {
public void draw ();
public void accept(Visitor visitor); // Notice how now it accepts a Visitor (not a specific one)
}
class Dot implements Shape{
float x = 5, y = 8;
public void draw (){ }
public void accept(Visitor visitor){
visitor.visit(this);
}
}
class Circle implements Shape{
float radius = 8;
public void draw (){ }
public void accept(Visitor visitor){
visitor.visit(this);
}
}
interface Visitor {
void visit (Dot d);
void visit (Circle c);
}
class ExportVisitor implements Visitor{
public void visit(Dot d){
System.out.println("Exporting Dot, x = " + d.x + ", y = " + d.y);
}
public void visit(Circle c){
System.out.println("Exporting Circle, radius = " + c.radius);
}
}
class TransformVisitor implements Visitor{
public void visit(Dot d){
System.out.println("Tranforming Dot, 2*x = " + (2*d.x) + ", 2*y = " + (2*d.y));
}
public void visit(Circle c){
System.out.println("Tranforming Circle, 2*radius = " + (2*c.radius));
}
}
public class Client{
public static void main(String[] args) {
}
}
class diagram
strategy & visitor
iterator
problem description
class Social1 {
private List<String> emails = new ArrayList<>();
public Social1 () { }
public void addUser (String email) {
this.emails.add(email);
}
public List getEmails () {
return emails;
}
}
emails
from List
to HashSet
clients will break. I can change the type to be a Collection
but again, if i change the implementation to be an array, clients will break again.solution using iterator pattern
hasNext
and getNext
.Social1 s1 = new Social1();
s1.addUser("luigi");
s1.addUser("cennini");
Social1Iterator s1it = s1.createIterator();
while (s1it.hasNext()) {
String user = s1it.getNext();
System.out.println(user);
}
import java.util.ArrayList;
import java.util.List;
class Social1 {
private List<String> emails = new ArrayList<>();
public Social1 () { }
public void addUser (String email) {
this.emails.add(email);
}
public Social1Iterator createIterator () {
return new Social1Iterator(emails);
}
}
class Social1Iterator {
private List<String> emails;
private int currentPosition = 0;
public Social1Iterator (List<String> emails) {
this.emails = emails;
}
public boolean hasNext () {
return currentPosition < emails.size();
}
public String getNext () {
if (!hasNext()) {
return null;
}
String email = emails.get(currentPosition);
currentPosition ++;
return email;
}
}
notes
(example) class diagram
Another example, tree iterator
Node root = new Node(10); // 10
root.left = new Node(20); // 20 30
root.right = new Node(30); // 50 80
root.left.left = new Node(50);
root.left.right = new Node(80);
System.out.println("In order traversal: ");
TreeIterator ti1 = root.createPreOrderIterator();
while (ti1.hasNext()) {
Node current = ti1.getNext();
System.out.println(current.val);
}
//prints: 10 20 50 80 30
System.out.println("Post order traversal: ");
TreeIterator ti2 = root.createInOrderIterator();
while (ti2.hasNext()) {
Node current = ti2.getNext();
System.out.println(current.val);
}
//prints: 50 20 80 10 30
import java.util.Stack;
class Node {
public int val;
public Node left = null, right = null;
public Node (int val){
this.val = val;
}
public TreeIterator createPreOrderIterator () {
return new PreOrderIterator(this);
}
public TreeIterator createInOrderIterator () {
return new InOrderOrderIterator(this);
}
}
interface TreeIterator {
boolean hasNext ();
Node getNext ();
}
class PreOrderIterator implements TreeIterator{
private Stack<Node> stack = new Stack<>();
public PreOrderIterator(Node root) {
stack.push(root);
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public Node getNext() {
if (!hasNext()) {
return null;
}
Node current = stack.pop();
if (current.right != null) {
stack.push(current.right);
}
if (current.left != null) {
stack.push(current.left);
}
return current;
}
}
class InOrderOrderIterator implements TreeIterator{
private Stack<Node> stack = new Stack<>();
private Node current;
public InOrderOrderIterator(Node root) {
this.current = root;
}
@Override
public boolean hasNext() {
return !stack.isEmpty() || current != null;
}
@Override
public Node getNext() {
while (current != null) {
stack.push(current);
current = current.left;
}
if (!stack.isEmpty()) {
Node node = stack.pop();
current = node.right;
return node;
}
return null;
}
}
(official) class diagram
chain of responsabilities
problem description
solution using chain of responsabilities pattern
StringFinder finder = new EqualsStringFinder(
new EqualsNoCaseStringFinder(
new PrefixStringFinder(
new SuffixStringFinder(null))));
System.out.println(
finder.match("hello world", "hello world"));
abstract class StringFinder {
private StringFinder next;
public StringFinder (StringFinder next) {
this.next = next;
}
public boolean match (String aString, String anotherString) {
if (next != null) {
return next.match(aString, anotherString);
}
return false;
}
}
class EqualsStringFinder extends StringFinder {
public EqualsStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean match(String aString, String anotherString) {
if (aString.equals(anotherString))
return true;
return super.match(aString, anotherString);
}
}
class EqualsNoCaseStringFinder extends StringFinder {
public EqualsNoCaseStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean match(String aString, String anotherString) {
if (aString.equalsIgnoreCase(anotherString))
return true;
return super.match(aString, anotherString);
}
}
class PrefixStringFinder extends StringFinder {
public PrefixStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean match(String aString, String anotherString) {
if (aString.startsWith(anotherString))
return true;
return super.match(aString, anotherString);
}
}
class SuffixStringFinder extends StringFinder {
public SuffixStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean match(String aString, String anotherString) {
if (aString.endsWith(anotherString))
return true;
return super.match(aString, anotherString);
}
}
notes
example class diagram
implementation where the forwarding logic is implemented in the abstract class
StringFinder finder = new EqualsStringFinder(
new EqualsNoCaseStringFinder(
new PrefixStringFinder(
new SuffixStringFinder(null))));
System.out.println(
finder.match("hello world", "hello world"));
abstract class StringFinder {
private StringFinder next;
public StringFinder (StringFinder next) {
this.next = next;
}
public boolean match (String aString, String anotherString) {
if (doMatch(aString, anotherString)) {
return true;
} else if (next != null) {
return next.match(aString, anotherString);
}
return false;
}
protected abstract boolean doMatch (String aString, String anotherString);
}
class EqualsStringFinder extends StringFinder {
public EqualsStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean doMatch(String aString, String anotherString) {
return aString.equals(anotherString);
}
}
class EqualsNoCaseStringFinder extends StringFinder {
public EqualsNoCaseStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean doMatch(String aString, String anotherString) {
return aString.equalsIgnoreCase(anotherString);
}
}
class PrefixStringFinder extends StringFinder {
public PrefixStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean doMatch(String aString, String anotherString) {
return aString.startsWith(anotherString);
}
}
class SuffixStringFinder extends StringFinder {
public SuffixStringFinder(StringFinder next) {
super(next);
}
@Override
public boolean doMatch(String aString, String anotherString) {
return aString.endsWith(anotherString);
}
}