Skip to content

7. Design patterns

  • design patterns are convenient solutions for software design problems that employed by experience developers.
  • work like recipes to solve software design problems.
  • patterns are used to improve existing designs or code by rearranging it according to a specific pattern.
  • design patterns:
    • increase efficiency, by avoiding lengthy process of trial and errors searching for a solution.
    • increase predictability, because the solution is known to work for this problem.
    • facilitate component-based development and reusability.
    • facilitate assigning responsibilities to different classes or objects.

5.1 Indirect Communication: Publisher-Subscriber (Observer) Pattern

  • Indirect communication is usually used when:
    • an object cannot or does not want to know the identity of the object whose method it calls.
    • an object does not want to know the effect of the method call.
  • uses of publisher-subscribers:
    • enables building reusable components
    • facilitate separation of business logic (concerns, responsibilities)
  • Centralized vs. decentralized execution/program-control spreads responsibilities for better balancing.
  • Decentralized control does not necessarily imply concurrent threads of execution.
  • pub-sub
  • steps:
    • Information source acquires information in some way and we assume that this information is important for other objects to do the work they are designed for.
    • Once the source acquires information (becomes “information expert”).
    • The information expert then publishes the information to the subscribers.
    • information experts are usually called “publishers”.
// Subscriber interface
public interface Subscriber {
 public void receive(Content content);
}

// Content or event schema that needs to be published by publishers, received by subscribers
// both Publisher and Subscriber should know about this schema
// A Content object contains only data, no business logic, and is meant to transfer data from Publisher to Subscriber.
import java.util.ArrayList;
public class Content {
 public Publisher source_;
 public ArrayList data_;
 public Content(Publisher src, ArrayList dat) {
 source_ = src;
 data_ = (ArrayList) dat.clone(); // for write safety avoid aliasing and create a new copy
 }
}

// Publisher interface
public interface Publisher {
 public subscribe(Subscriber subscriber);
 public unsubscribe(Subscriber subscriber);
}

// Subscriber class
public class SubscriberClass implements Subscriber {
 public constructor(Publisher publisherObject) {
    publisherObject.subscribe(this); // subscribe to publisher upon creation
 }
 public void receive(Content content) {
    if(content.source_ == instanceof PublisherClass) {
        // do something with the content that comes from the publisher that we subscribed to
    }
    // other content that we don't care about
 }
}

// Publisher class
public class PublisherClass implements Publisher {
 protected ArrayList<Subscriber> subscribers_ = new ArrayList<Subscriber>();

 public void publish(Content content) {
    for(Subscriber subscriber : subscribers_) {
        subscriber.receive(content);
    }
 }
 public void subscribe(Subscriber subscriber) {
    subscribers_.add(subscriber);
 }
 public void unsubscribe(Subscriber subscriber) {
    subscribers_.remove(subscriber);
 }

 public notifySubscribers (String: message) {
    Content content = new Content(this, message);
    publish(content);
 }
}
  • subscriber may subscribe to multiple publishers:
    • subscriber needs to check the source of the content before doing any action.
    • the subscriber.receive(content) method now contains multiple nested if statements checking for source, then content type.
    • to avoid this, we can use polymorphism, so instead of having one subscriber, we can have multiple subscribers each is specialized for a different type of content.
    • the code below represents this case, and can be improved in a different ways.
    • we have now two subscribers, each specialized for a different type of content.
    • the tradeoff is that the publisher code is more complicated.
// Publisher class
public class PublisherClass implements Publisher {
 protected ArrayList<Subscriber> subscribersOfEvent1_ = new ArrayList<Subscriber>();
 protected ArrayList<Subscriber> subscribersOfEvent2_ = new ArrayList<Subscriber>();

 public void publishEvent1(Content content) {
    for(Subscriber subscribersOfEvent1_ : subscribers_) {
        subscriber.receiveEvent1(content);
    }
 }

 public void publishEvent2(Content content) {
    for(Subscriber subscribersOfEvent2_ : subscribers_) {
        subscriber.receiveEvent2(content);
    }
 }
 public void subscribeEvent1(Subscriber subscriber) {
    subscribersEvent1_.add(subscriber);
 }

 public void subscribeEvent2(Subscriber subscriber) {
    subscribersEvent2_.add(subscriber);
 }

 public void unsubscribeEvent1(Subscriber subscriber) {
     subscribersEvent1_.remove(subscriber);
 }

 public void unsubscribeEvent2(Subscriber subscriber) {
    subscribersEvent2_.remove(subscriber);
 }

 public void notifySubscribers(String: message) {
    Content content = new Content(this, message);
    if(message == "event1") {
        publishEvent1(content);
    } else if(message == "event2") {
        publishEvent2(content);
    }
 }

}


// Subscriber class
public class SubscriberClass1 implements Subscriber {
 public constructor(Publisher publisherObject) {
    publisherObject.subscribeEvent1(this); // subscribe to publisher upon creation
 }
 public void receiveEvent1(Content content) {
    if(content.source_ == instanceof PublisherClass && content.data_ == "event1") {
        // do something with the content that comes from the publisher that we subscribed to
        // event1 type
    }
 }
}

public class SubscriberClass2 implements Subscriber {
 public constructor(Publisher publisherObject) {
    publisherObject.subscribeEvent2(this);
 }

  public void receiveEvent2(Content content) {
    if(content.source_ == instanceof PublisherClass && content.data_ == "event2") {
        // do something with the content that comes from the publisher that we subscribed to
        // event2 type
    }
 }
}

5.1.1 Applications of Publisher-Subscriber

  • The Publisher-Subscriber design pattern is used in the Java AWT and Swing tool kits for notification of the GUI interface components about user generated events.
  • the publisher-subscriber is known in java as Source-Listener or delegation event model.
  • making a component independent of others makes it reusable
  • In the “ideal” case, all objects could be made self-contained and thus reusable by applying the pub-sub design pattern.
  • trade offs:
    • indirect communication requires much more code, which results in increased demand for memory and decreased performance.
    • if it is not likely that a component will need to be reused or if performance is critical, direct communication should be applied and the pub-sub pattern should be avoided.

5.1.2 Control Flow

  • in direct communication, the control is centralized and all flows emanate from the Controller.
  • in indirect communication, the control is decentralized, and it is passed as a token around, cascading from object to object.
  • control flow

5.1.3 Pub-Sub Pattern Initialization

  • there should be a controller that subscribes each subscribers to its publisher (s) where such initialization is needed.

5.2 More Patterns

  • Publisher-Subscriber belongs to the category of behavioral design patterns.
  • behavioral design patterns: separate the interdependent behavior of objects from the objects themselves, they separate functionality from the object to which the functionality applies.
  • Command is also a behavioral design pattern.
  • Proxy is a structural design pattern.

5.2.1 Command Pattern

  • Objects invoke methods on other objects.
  • The need for the Command pattern arises if:
    • the invoking object (client) needs to reverse the effect of a previous method invocation.
    • the ability to trace the course of the system operation.
  • The purpose of the Command patter is to delegate the functionality associated with rolling back the server object’s state and logging the history of the system operation away from the client object to the Command object.
  • client => command => server (receiver), so the client does not talk directly to the server, instead the talk to the Command which in tur delegate the task to the server.
  • Command Class:
    • represents operations as classes and is used whenever a method call alone is not sufficient.
    • is the central player in the Command pattern,
    • CommandHistory maintains history log of Commands in linear sequence of their execution
  • command pattern
  • It is common to use Command pattern in operating across the Internet. For example, you send an http request with arguments to the server, the http server (Command) receives the request invokes some method on the request, then returns the result to the client into an http response.
  • Undo/Redo:
    • Command pattern optionally supports undo and redo functionality (rollback).
    • CommandHistory should respond properly to undo/redo requests.

5.2.2 Decorator Pattern

  • The Decorator pattern is used to add non-essential behavior to key objects in a software design.
  • The embellished class (or, decoratee) is wrapped up by an arbitrary number of Decorator classes, which provide special-case behaviors (embellishments).
  • decorator pattern
  • the Decorator is an abstract class (the class and method names are italicized). The reason for this choice is to collect the common things from all different decorators into a base decorator class.
  • In this case, the Decorator class will contain a reference to the next decorator.
  • The decorators are linked in a chain.
  • The client has a reference to the start of the chain and the chain is terminated by the real subject.

5.2.3 State

  • The State design pattern is usually used when an object’s behavior depends on its state in a complex way
  • In this case, the state determines a mode of operation.
  • The State pattern externalizes the relevant attributes into a State object, and this State object has the responsibility of managing the state transitions of the original object.
  • The original object is called “Context” and its attributes are externalized into a State object
  • Each concrete State class implements the behavior of the Context associated with the state implemented by this State class.
  • state pattern
class Context {
    private State state;
    public Context(State defaultState) {
        this.state = defaultState;
    }
    public void request() {
        this.state.handle(this);
    }
    public void changeState(State newState) {
        this.state = newState;
    }
}

class State {
    public void handle(Context context) {
        // do nothing
    }
}


class ConcreteStateA extends State {
    public void handle(Context context) {
        // do something
        log("ConcreteStateA");
    }
}

class ConcreteStateB extends State {
    public void handle(Context context) {
        // do something
        log("ConcreteStateB");
    }
}

ConcreteStateA stateA = new ConcreteStateA();
ConcreteStateB stateB = new ConcreteStateB();
Context context = new Context(StateA);
context.request(); // ConcreteStateA
context.changeState(StateB);
context.request(); // ConcreteStateB

5.2.4 Proxy

  • The Proxy pattern is used to manage or control access to an object.
  • Proxy is needed when the logistics of accessing the subject’s services is overly complex and comparable or greater in size than that of client’s primary responsibility.
  • In such cases, we introduce a helper object (called “proxy”) for management of the subject invocation.
  • A Proxy object is a surrogate that acts as a stand-in for the actual subject, and controls or enhances the access to it
  • The proxy object forwards requests to the subject when appropriate, depending on whether the constraint of the proxy is satisfied.
  • proxy pattern
  • The causes of access complexity and the associated constraints include:
    • The subject is located in a remote address space, e.g., on a remote host, in which case the invocation (sending messages to it) requires following complex networking protocols. Solution: use the Remote Proxy pattern for crossing the barrier between different memory spaces.
    • Different access policies constrain the access to the subject. Security policies require that access is provided only to the authorized clients, filtering out others. Safety policies may impose an upper limit on the number of simultaneous accesses to the subject. Solution: use the Protection Proxy pattern for additional housekeeping
    • Deferred instantiation of the subject, to speed up the performance (provided that its full functionality may not be immediately necessary). For example, a graphics editor can be started faster if the graphical elements outside the initial view are not loaded until they are needed; only if and when the user changes the viewpoint, the missing graphics will be loaded. Graphical proxies make this process transparent for the rest of the program. Solution: use the Virtual Proxy pattern for optimization in object creation
  • In essence we could say that proxy allows client objects to cross a barrier to server objects
  • The barrier may be:
    • physical (such as network between the client and server computers).
    • imposed (such as security policies to prevent unauthorized access).
  • As a result, the client cannot or should not access the server by a simple method call as when the barrier does not exist.
  • The additional functionality needed to cross the barrier is extraneous to the client’s business logic.
  • the client has an illusion it is directly communicating with the subject, and does not know that there is a barrier in the middle which is the proxy.
  • Proxy offers the same interface (set of methods and their signatures) as the real subject and ensures correct access to the real subject.
  • Because of the identical interface, the client does not need to change its calling behavior and syntax from that which it would use if there were no barrier involved.

Protection Proxy

  • protection proxy

5.3 Concurrent Programming

  • In event-driven applications, such as graphical user interfaces, the user expects a quick response from the system.
  • If the (single processor) system processes all requests sequentially, then it will respond with significant delays and most of the requestors will be unhappy
  • to handle this, processors use time-sharing or time slicing: a single processor dedicates a small amount of time for each task, so all of them move forward collectively by taking turns on the processor.
  • Although none of the tasks progresses as fast as it would if it were alone, none of them has to wait as long as it could have if the processing were performed sequentially.
  • The task executions are really sequential but interleaved with each other, so they virtually appear as concurrent.
  • real concurrency when the system has multiple processors, and virtual concurrency on a single-processor system.

5.4 Broker and Distributed Computing

  • Middleware is a good software engineering practice that should be applied any time the communication between objects becomes complex and starts rivaling the object’s business logic in terms of the implementation code size
  • Middleware is a collection of objects that offer a set of services related to object communication, so that extraneous functionality is offloaded to the middleware.

5.4.1 Broker Pattern

  • broker pattern
  • The Broker pattern is an architectural pattern used to structure distributed software systems with components that interact by remote method calls
  • the functions that are common to all or most of the proxies are delegated to the Broker component
  • remote method invocation

5.5 Information Security

  • Information security is a nonfunctional property of the system, it is an emergent property.
  • there are two main security disciplines:
    • Communication security is concerned with protecting information when it is being transported between different systems.
    • Computer security is related to protecting information within a single system, where it can be stored, accessed, and processed
  • The main objectives of information security are:
    • Confidentiality: ensuring that information is not disclosed or revealed to unauthorized persons
    • Integrity: ensuring consistency of data, in particular, preventing unauthorized creation, modification, or destruction of data
    • Availability: ensuring that legitimate users are not unduly denied access to resources, including information resources, computing resources, and communication resources
    • Authorized use: ensuring that resources are not used by unauthorized persons or in unauthorized ways
  • To achieve these objectives, we institute various safeguards:
    • concealing (encryption) confidential information so that its meaning is hidden from spying eyes
    • key management which involves secure distribution and handling of the “keys” used for encryption.
  • steps:
    • a sending computer encrypts the original data using an encryption algorithm to make it unintelligible to any intruder
    • The data in the original form is known as plaintext or cleartext. The encrypted message is known as ciphertext.
    • Without a corresponding “decoder,” the transmitted information (ciphertext) would remain scrambled and be unusable.
    • The receiving computer must regenerate the original plaintext from the ciphertext with the correct decryption algorithm in order to read it
    • This pair of data transformations, encryption and decryption, together forms a cryptosystem.
  • There are two basic types of cryptosystems:
    • (i) symmetric systems, where both parties use the same (secret) key in encryption and decryption transformations;
    • (ii) public-key systems, also known as asymmetric systems, where the parties use two related keys, one of which is secret and the other one can be publicly disclosed.
  • Kerckhoffs’ Principle states that a cryptosystem should remain secure even if everything about it other than the key is public knowledge.

5.5.1 Symmetric and Public-Key Cryptosystems

  • trapdoor function:
    • We call f a trapdoor function if f is easy to compute, but f-1 (reverse f) is very hard, indeed impossible for practical purposes.
    • is not a very practical code, because the legitimate user finds it just as hard to decode the message as the adversary does.
  • public-key cryptosystems has 3 steps:
    1. the sender encode the message with the public key and send it to the receiver.
    2. the receiver secures the message with the private key key. and send it back to the sender.
    3. the sender receives the message and removes his security from the message. and send it back to the receiver.

5.5.2 Cryptographic Algorithms

  • Symmetric Cryptography : The Advanced Encryption Standard has a fixed block size of 128 bits and a key size of 128, 192, and 256 bits.
  • example: RSA
  • In the RSA system, the receiver does as follows:
    1. Randomly select two large prime numbers p and q, which always must be kept secret.
    2. Select an integer number E, known as the public exponent, such that (p - 1) and E have no common divisors, and (q - 1) and E have no common divisors.
    3. Determine the product n = p*q, known as public modulus.
    4. Determine the private exponent, D, such that (E*D-1) is exactly divisible by both (p - 1) and (q - 1). In other words, given E, we choose D such that the integer remainder when E*D is divided by (p - 1)*(q - 1) is 1.
    5. Release publicly the public key, which is the pair of numbers n and E, K(+) = (n, E).
    6. Keep secret the private key, K(-) = (n, D).