0%

Structural Patterns

Structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.

Adapter Pattern

Adapter pattern involves a single class which is responsible to join functionalities of independent or incompatible interfaces. It allows incompatible classes to work together by converting the interface of one class into an interface expected by the clients.

A real life example could be a case of card reader which acts as an adapter between memory card and a laptop. You plugin the memory card into card reader and card reader into the laptop so that memory card can be read via laptop.

此处输入图片的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AdapteeToClientAdapter implements Client {

private final Adaptee instance;

public AdapteeToClientAdapter(final Adaptee instance) {
this.instance = instance;
}

@Override
public void clientMethod() {
// call Adaptee's method(s) to implement Client's clientMethod
}

}

Bridge Pattern

Bridge pattern is used when we need to decouple an abstraction from its implementation so that the two can vary independently. This pattern involves an interface which acts as a bridge which makes the functionality of concrete classes independent from interface implementer classes. Both types of classes can be altered structurally without affecting each other.

此处输入图片的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public interface DrawAPI {
void draw();
}

public class DrawCircle implements DrawAPI {
@Override
public void draw() {
System.out.println("Draw a circle");
}
}

public class DrawSquare implements DrawAPI {
@Override
public void draw() {
System.out.println("Draw a Square");
}
}

public abstract class Shape {
DrawAPI drawAPI;
public Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}

public class ShapeDemo extends Shape {
public ShapeDemo(DrawAPI drawAPI) {
super(drawAPI);
}

@Override
public void draw() {
drawAPI.draw();
}
}

public class BridgeTest {
public static void main(String[] args) {
ShapeDemo demo1 = new ShapeDemo(new DrawCircle());
ShapeDemo demo2 = new ShapeDemo(new DrawSquare());
demo1.draw();
demo2.draw();
}
}

Composite Pattern

The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to “compose” objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.

Composite should be used when clients ignore the difference between compositions of objects and individual objects. If programmers find that they are using multiple objects in the same way, and often have nearly identical code to handle each of them, then composite is a good choice; it is less complex in this situation to treat primitives and composites as homogeneous.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class CompositeGraphic implements Graphic {

//Collection of child graphics.
private List<Graphic> childGraphics = new ArrayList<Graphic>();

//Prints the graphic.
public void print() {
for (Graphic graphic : childGraphics) {
graphic.print();
}
}

//Adds the graphic to the composition.
public void add(Graphic graphic) {
childGraphics.add(graphic);
}

//Removes the graphic from the composition.
public void remove(Graphic graphic) {
childGraphics.remove(graphic);
}
}

/** "Leaf" */
class Ellipse implements Graphic {

//Prints the graphic.
public void print() {
System.out.println("Ellipse");
}
}

/** Client */
public class Program {

public static void main(String[] args) {
//Initialize four ellipses
Ellipse ellipse1 = new Ellipse();
Ellipse ellipse2 = new Ellipse();
Ellipse ellipse3 = new Ellipse();
Ellipse ellipse4 = new Ellipse();

//Initialize three composite graphics
CompositeGraphic graphic = new CompositeGraphic();
CompositeGraphic graphic1 = new CompositeGraphic();
CompositeGraphic graphic2 = new CompositeGraphic();

//Composes the graphics
graphic1.add(ellipse1);
graphic1.add(ellipse2);
graphic1.add(ellipse3);

graphic2.add(ellipse4);

graphic.add(graphic1);
graphic.add(graphic2);

//Prints the complete graphic (four times the string "Ellipse").
graphic.print();
}
}

Decorator Pattern

Decorator pattern allows a user to add new functionality to an existing object without altering its structure. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.
此处输入图片的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public interface Window {
public void draw(); // Draws the Window
public String getDescription(); // Returns a description of the Window
}

abstract class WindowDecorator implements Window {
protected Window windowToBeDecorated; // the Window being decorated

public WindowDecorator (Window windowToBeDecorated) {
this.windowToBeDecorated = windowToBeDecorated;
}
public void draw() {
windowToBeDecorated.draw(); //Delegation
}
public String getDescription() {
return windowToBeDecorated.getDescription(); //Delegation
}
}

class VerticalScrollBarDecorator extends WindowDecorator {
public VerticalScrollBarDecorator (Window windowToBeDecorated) {
super(windowToBeDecorated);
}

@Override
public void draw() {
super.draw();
drawVerticalScrollBar();
}

private void drawVerticalScrollBar() {
// Draw the vertical scrollbar
}

@Override
public String getDescription() {
return super.getDescription() + ", including vertical scrollbars";
}
}

class HorizontalScrollBarDecorator extends WindowDecorator {
public HorizontalScrollBarDecorator (Window windowToBeDecorated) {
super(windowToBeDecorated);
}

@Override
public void draw() {
super.draw();
drawHorizontalScrollBar();
}

private void drawHorizontalScrollBar() {
// Draw the horizontal scrollbar
}

@Override
public String getDescription() {
return super.getDescription() + ", including horizontal scrollbars";
}
}

public class DecoratedWindowTest {
public static void main(String[] args) {
// Create a decorated Window with horizontal and vertical scrollbars
Window decoratedWindow = new HorizontalScrollBarDecorator (
new VerticalScrollBarDecorator (new SimpleWindow()));

// Print the Window's description
System.out.println(decoratedWindow.getDescription());
}
}

Facade Pattern

Facade pattern hides the complexities of the system and provides an interface to the client using which the client can access the system. This pattern involves a single class which provides simplified methods required by client and delegates calls to methods of existing system classes.

The Facade design pattern is often used when a system is very complex or difficult to understand because the system has a large number of interdependent classes or its source code is unavailable. This pattern hides the complexities of the larger system and provides a simpler interface to the client. It typically involves a single wrapper class which contains a set of members required by client. These members access the system on behalf of the facade client and hide the implementation details.
此处输入图片的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class CPU {
public void freeze() { ... }
public void jump(long position) { ... }
public void execute() { ... }
}

class Memory {
public void load(long position, byte[] data) { ... }
}

class HardDrive {
public byte[] read(long lba, int size) { ... }
}

/* Facade */

class ComputerFacade {
private CPU processor;
private Memory ram;
private HardDrive hd;

public ComputerFacade() {
this.processor = new CPU();
this.ram = new Memory();
this.hd = new HardDrive();
}

public void start() {
processor.freeze();
ram.load(BOOT_ADDRESS, hd.read(BOOT_SECTOR, SECTOR_SIZE));
processor.jump(BOOT_ADDRESS);
processor.execute();
}
}

/* Client */

class You {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
computer.start();
}
}

Flyweight Pattern

Flyweight pattern is primarily used to reduce the number of objects created and to decrease memory footprint and increase performance.

Flyweight pattern tries to reuse already existing similar kind objects by storing them and creates new object when no matching object is found.

We will demonstrate this pattern by drawing 20 circles of different locations but we will create only 5 objects. Only 5 colors are available so color property is used to check already existing Circle objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public interface Shape {
void draw();
}
public class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;

public Circle(String color){
this.color = color;
}

public void setX(int x) {
this.x = x;
}

public void setY(int y) {
this.y = y;
}

public void setRadius(int radius) {
this.radius = radius;
}

@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x + ", y :" + y + ", radius :" + radius);
}
}

public class ShapeFactory {
private static final HashMap<String, Shape> circleMap = new HashMap();

public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);

if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}

public class FlyweightPatternDemo {
private static final String colors[] = { "Red", "Green", "Blue", "White", "Black" };
public static void main(String[] args) {

for(int i=0; i < 20; ++i) {
Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int)(Math.random()*colors.length)];
}
private static int getRandomX() {
return (int)(Math.random()*100 );
}
private static int getRandomY() {
return (int)(Math.random()*100);
}
}

Proxy Pattern

In proxy pattern, a class represents functionality of another class. We create object having original object to interface its functionality to outer world. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. In short, a proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes.

  • Remote Proxy – Represents an object locally which belongs to a different address space. Think of an ATM implementation, it will hold proxy objects for bank information that exists in the remote server.

  • Virtual Proxy – In place of a complex or heavy object, use a skeleton representation. When an underlying image is huge in size, just represent it using a virtual proxy object and on demand load the real object. You know that the real object is expensive in terms of instantiation and so without the real need we are not going to use the real object. Until the need arises we will use the virtual proxy.

  • Protection Proxy – Are you working on an MNC? If so, we might be well aware of the proxy server that provides us internet by restricting access to some sort of websites like public e-mail, social networking, data storage etc. The management feels that, it is better to block some content and provide only work related web pages. Proxy server does that job. This is a type of proxy design pattern.
    此处输入图片的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
interface Image {
public void displayImage();
}

//on System A
class RealImage implements Image {

private String filename = null;
/**
* Constructor
* @param filename
*/
public RealImage(final String filename) {
this.filename = filename;
loadImageFromDisk();
}

/**
* Loads the image from the disk
*/
private void loadImageFromDisk() {
System.out.println("Loading " + filename);
}

/**
* Displays the image
*/
public void displayImage() {
System.out.println("Displaying " + filename);
}

}

//on System B
class ProxyImage implements Image {

private RealImage image = null;
private String filename = null;
/**
* Constructor
* @param filename
*/
public ProxyImage(final String filename) {
this.filename = filename;
}

/**
* Displays the image
*/
public void displayImage() {
if (image == null) {
image = new RealImage(filename);
}
image.displayImage();
}

}

class ProxyExample {

/**
* Test method
*/
public static void main(String[] args) {
final Image IMAGE1 = new ProxyImage("HiRes_10MB_Photo1");
final Image IMAGE2 = new ProxyImage("HiRes_10MB_Photo2");

IMAGE1.displayImage(); // loading necessary
IMAGE1.displayImage(); // loading unnecessary
IMAGE2.displayImage(); // loading necessary
IMAGE2.displayImage(); // loading unnecessary
IMAGE1.displayImage(); // loading unnecessary
}

}

References

Structural pattern