Wednesday, March 20, 2024

How Many Types of Databases Are There?

There are nearly a dozen types of database. Some of the more commonly used categories of database include:

Hierarchical Databases

Developed in the 1960s, the hierarchical database looks similar to a family tree. A single object (the “parent”) has one or more objects beneath it (the “child”). No child can have more than one parent. In exchange for the rigid and complex navigation of the parent child structure, the hierarchical database offers high performance, as there’s easy access and a quick querying time. The Windows Registry is one example of this system.

Relational Databases

Relational databases are a system designed in the 1970s. This database commonly uses Structured Query Language (SQL) for operations like creating, reading, updating, and deleting (CRUD) data.

This database stores data in discrete tables, which can be joined together by fields known as foreign keys. For example, you might have a User table that contains data about your users, and join the users table to a Purchases table, which contains data about the purchases the users have made. MySQL, Microsoft SQL Server, and Oracle are examples.

Non-Relational Databases

Non-relational management systems are commonly referred to as NoSQL databases. This type of database matured due to increasingly complex modern web applications. These databases' varieties have proliferated over the last decade. Examples include MongoDB and Redis.

Object oriented databases

Object oriented databases store and manage objects on a database server's disk. Object oriented databases are unique because associations between objects can persist. This means that object oriented programming and the querying of data across complex relationships is fast and powerful. One example of an object oriented database is MongoDB Realm, where the query language constructs native objects through your chosen SDK. Object oriented programming is the most popular programming paradigm. 

NoSQL

NoSQL is an umbrella term for any alternative system to traditional SQL databases. Sometimes, when we say NoSQL management systems, we mean any database that doesn't use a relational model. NoSQL databases use a data model that has a different structure than the rows and columns table structure used with RDBMS.

NoSQL databases are different from each other. There are four kinds of this database: 

document databases, 
key-value stores, 
column-oriented databases, and
Graph databases


Document databases

A document database stores data in JSON, BSON, or XML documents. Documents in the database can be nested. Particular elements can be indexed for faster querying.

You can access, store, and retrieve documents from your network in a form that is much closer to the data objects used in applications, which means less translation is required to use and access the data in an application. SQL data must often be assembled and disassembled when moving between applications, storage, or more than one network.

Document databases are popular with developers because they offer the flexibility to rework their document structures as needed to suit their applications. The flexibility of this database speeds development — data becomes like code and is under the control of developers. They can more easily access and use it. In SQL databases, intervention by database administrators may be required to change the structure of a database.

Document databases are usually implemented with a scale-out architecture, providing a path to the scalability of data volumes and traffic. Use cases include eCommerce platforms, trading platforms, and mobile app development.

Key-value stores

This is the simplest type of NoSQL database. Every element is stored as a key-value pair consisting of an attribute name ("key") and a value. This database is like an RDBMS with two columns: the attribute name (such as "state") and the value (such as "Alaska").

Use cases for NoSQL databases include shopping carts, user preferences, and user profiles.

Column-oriented databases

While an RDBMS stores data in rows and reads it row by row, column-oriented databases are organized as a set of columns. When you want to run analytics on a small number of columns in the network, you can read those columns directly without consuming memory with unwanted data. Columns are of the same type and benefit from more efficient compression, making reads even faster. A column-oriented database can aggregate the value of a given column (adding up sales for the year, for example). Use cases of a column-oriented database include analytics.

While column-oriented databases are great for analytics, the way they write data makes it difficult for them to be consistent as writes of all the columns in the column-oriented database require multiple write events on disk. Relational databases don't suffer from this problem as row data is written contiguously to disk.

Graph databases

A graph database focuses on the relationship between data elements. Each element is contained as a node. The connections between elements in the database are called links or relationships. Connections are first-class elements of the database, stored directly.

A graph database is optimized to capture and search the connections between elements, overcoming the overhead associated with JOINing several tables in SQL. Very few real-world business systems can survive solely on graph databases. As a result, graph databases are usually run alongside more traditional databases.

Use cases include fraud detection and social networks.

These databases are diverse in their data formats and applications. Furthermore, systems store data in different ways:

Data can be stored in a structured document, similar to JSON (JavaScript Object Notation). MongoDB is a popular document distributed database.

Data can be in a key-value format that maps a single attribute (key) to its value. Redis and Riak KV are examples. They’re typically used for simple one-to-one relationships, like associating users with preferences.

A graph datastore uses nodes to represent objects and edges to describe the relationship between them. Neo4j is one example.

These differ from RDBMS in that they can be schema-agnostic, allowing unstructured and semi-structured data in a network to be stored and processed.

Database

 A database(DB) is a collection of data in a format that can be easily accessed (Digital). A software application used to manage our DB is called DBMS (Database Management System).

Or

A database is an organized collection of data stored in a computer system and usually controlled by a database management system (DBMS).

Tuesday, May 4, 2010

Visitor Pattern

The visitor pattern is a behavioral object design pattern. The visitor pattern is used to simplify operations on groupings of related objects. These operations are performed by the visitor rather than by placing this code in the classes being visited. Since the operations are performed by the visitor rather than by the classes being visited, the operation code gets centralized in the visitor rather than being spread out across the grouping of objects, thus leading to code maintainability. The visitor pattern also avoids the use of the instanceof operator in order to perform calculations on similar classes.
In the visitor pattern, we have a Visitor interface that declares visit() methods for the various types of elements that can be visited. Concrete Visitors implement the Visitor interface's visit() methods. The visit() methods are the operations that should be performed by the visitor on an element being visited.
The related classes that will be visited implement an Element interface that declares an accept() method that takes a visitor as an argument. Concrete Elements implement the Element interface and implement the accept() method. In the accept() method, the visitor's visit() method is called with 'this', the current object of the Concrete Element type.
The elements to visit all implement the accept() method that takes a visitor as an argument. In this method, they call the visitor's visit() method with 'this'. As a result of this, an element takes a visitor and then the visitor performs its operation on the element.
Let's illustrate the visitor pattern with an example. First, we'll define a NumberVisitor interface. This interface declares three visit methods which take different types as arguments. Note that if we only wrote one visit method, we'd have to use the instanceof operator or a similar technique to handle the different element types. However, since we have separate visit methods, we don't need the instanceof operator, since each visit method handles a different type.

NumberVisitor.java

package com.cakes;

import java.util.List;

public interface NumberVisitor {

 public void visit(TwoElement twoElement);

 public void visit(ThreeElement threeElement);

 public void visit(List elementList);

}
All of the elements classes to be visited will implement the NumberElement interface. This interface has a single method that takes a NumberVisitor as an argument.

NumberElement.java

package com.cakes;

public interface NumberElement {

 public void accept(NumberVisitor visitor);

}
Let's create a TwoElement class that implements NumberElement. It has two int fields. Its accept() method calls the visitor's visit() method with 'this'. The operator to be performed on TwoElement is performed by the visitor.

TwoElement.java

package com.cakes;

public class TwoElement implements NumberElement {

 int a;
 int b;

 public TwoElement(int a, int b) {
  this.a = a;
  this.b = b;
 }

 @Override
 public void accept(NumberVisitor visitor) {
  visitor.visit(this);
 }

}
The ThreeElement class is similar to TwoElement, except that it has three int fields.

ThreeElement.java

package com.cakes;

public class ThreeElement implements NumberElement {

 int a;
 int b;
 int c;

 public ThreeElement(int a, int b, int c) {
  this.a = a;
  this.b = b;
  this.c = c;
 }

 @Override
 public void accept(NumberVisitor visitor) {
  visitor.visit(this);
 }

}
Now, let's create a visitor called SumVisitor that implements the NumberVisitor interface. For TwoElement and ThreeElement objects, this visitor will sum up the int fields. For a List of NumElements (ie, TwoElement and ThreeElement objects), this visitor will iterate over the elements and call their accept() methods. As a result of this, the visitor will perform visit operations on all the TwoElement and ThreeElement objects that make up the list, since the call to accept() in turn calls the visitor's visit methods for the TwoElement and ThreeElement objects. 

SumVisitor.java

package com.cakes; import java.util.List; public class SumVisitor implements NumberVisitor { @Override public void visit(TwoElement twoElement) { int sum = twoElement.a + twoElement.b; System.out.println(twoElement.a + "+" + twoElement.b + "=" + sum); } @Override public void visit(ThreeElement threeElement) { int sum = threeElement.a + threeElement.b + threeElement.c; System.out.println(threeElement.a + "+" + threeElement.b + "+" + threeElement.c + "=" + sum); } @Override public void visit(List elementList) { for (NumberElement ne : elementList) { ne.accept(this); } } } Here is another visitor, TotalSumVisitor. In addition to summing up the int fields and displaying the sum, this visitor will keep track of the total sums of all the elements that are visited.

TotalSumVisitor.java

package com.cakes; import java.util.List; public class TotalSumVisitor implements NumberVisitor { int totalSum = 0; @Override public void visit(TwoElement twoElement) { int sum = twoElement.a + twoElement.b; System.out.println("Adding " + twoElement.a + "+" + twoElement.b + "=" + sum + " to total"); totalSum += sum; } @Override public void visit(ThreeElement threeElement) { int sum = threeElement.a + threeElement.b + threeElement.c; System.out.println("Adding " + threeElement.a + "+" + threeElement.b + "+" + threeElement.c + "=" + sum + " to total"); totalSum += sum; } @Override public void visit(List elementList) { for (NumberElement ne : elementList) { ne.accept(this); } } public int getTotalSum() { return totalSum; } } Let's see the visitor pattern in action. The Demo class creates two TwoElement objects and one ThreeElement object. It creates a list of NumberElements and adds the TwoElement object and the ThreeElement object to the list. Next, we create a SumVisitor and we visit the list with the SumVisitor. After this, we create a TotalSumVisitor and visit the list with the TotalSumVisitor. We display the total sum via the call to TotalSumVisitor's getTotalSum() method.

Demo.java

package com.cakes; import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) { TwoElement two1 = new TwoElement(3, 3); TwoElement two2 = new TwoElement(2, 7); ThreeElement three1 = new ThreeElement(3, 4, 5); List numberElements = new ArrayList(); numberElements.add(two1); numberElements.add(two2); numberElements.add(three1); System.out.println("Visiting element list with SumVisitor"); NumberVisitor sumVisitor = new SumVisitor(); sumVisitor.visit(numberElements); System.out.println("\nVisiting element list with TotalSumVisitor"); TotalSumVisitor totalSumVisitor = new TotalSumVisitor(); totalSumVisitor.visit(numberElements); System.out.println("Total sum:" + totalSumVisitor.getTotalSum()); } } The console output of executing Demo is shown here.

Console Output

Visiting element list with SumVisitor 3+3=6 2+7=9 3+4+5=12 Visiting element list with TotalSumVisitor Adding 3+3=6 to total Adding 2+7=9 to total Adding 3+4+5=12 to total Total sum:27 Notice that if we'd like to perform new operations on the grouping of elements, all we would need to do is write a new visitor class. We would not have to make any additions to the existing element classes, since they provide the data but none of the code for the operations.

Template Method Pattern

The template method pattern is a behavioral class pattern. A behavioral class pattern uses inheritance for distribution of behavior. In the template method pattern, a method (the 'template method') defines the steps of an algorithm. The implementation of these steps (ie, methods) can be deferred to subclasses. Thus, a particular algorithm is defined in the template method, but the exact steps of this algorithm can be defined in subclasses. The template method is implemented in an abstract class. The steps (methods) of the algorithm are declared in the abstract class, and the methods whose implementations are to be delegated to subclasses are declared abstract.
Here is an example of the template method pattern. Meal is an abstract class with a template method called doMeal() that defines the steps involved in a meal. We declare the method as final so that it can't be overridden. The algorithm defined by doMeal() consists of four steps: prepareIngredients(), cook(), eat(), and cleanUp(). The eat() method is implemented although subclasses can override the implementation. The prepareIngredients(), cook(), and cleanUp() methods are are declared abstract so that subclasses need to implement them.

Meal.java

package com.cakes;

public abstract class Meal {

 // template method
 public final void doMeal() {
  prepareIngredients();
  cook();
  eat();
  cleanUp();
 }

 public abstract void prepareIngredients();

 public abstract void cook();

 public void eat() {
  System.out.println("Mmm, that's good");
 }

 public abstract void cleanUp();

}
The HamburgerMeal class extends Meal and implements Meal's three abstract methods.

HamburgerMeal.java

package com.cakes;

public class HamburgerMeal extends Meal {

 @Override
 public void prepareIngredients() {
  System.out.println("Getting burgers, buns, and french fries");
 }

 @Override
 public void cook() {
  System.out.println("Cooking burgers on grill and fries in oven");
 }

 @Override
 public void cleanUp() {
  System.out.println("Throwing away paper plates");
 }

}
The TacoMeal class implements Meal's three abstract methods and also overrides the eat() method.

TacoMeal.java

package com.cakes;

public class TacoMeal extends Meal {

 @Override
 public void prepareIngredients() {
  System.out.println("Getting ground beef and shells");
 }

 @Override
 public void cook() {
  System.out.println("Cooking ground beef in pan");
 }

 @Override
 public void eat() {
  System.out.println("The tacos are tasty");
 }

 @Override
 public void cleanUp() {
  System.out.println("Doing the dishes");
 }

}
The Demo class creates a HamburgerMeal object and calls its doMeal() method. It creates a TacoMeal object and calls doMeal() on the TacoMeal object.

Demo.java

package com.cakes;

public class Demo {

 public static void main(String[] args) {

  Meal meal1 = new HamburgerMeal();
  meal1.doMeal();

  System.out.println();

  Meal meal2 = new TacoMeal();
  meal2.doMeal();

 }

}
The console output of the execution of Demo is shown here.

Console Output

Getting burgers, buns, and french fries
Cooking burgers on grill and fries in oven
Mmm, that's good
Throwing away paper plates

Getting ground beef and shells
Cooking ground beef in pan
The tacos are tasty
Doing the dishes
As you can see, the template method design pattern allows us to define the steps in an algorithm and pass the implementation of these steps to subclasses.

Startergy Pattern

The strategy pattern is a behavioral object design pattern. In the strategy pattern, different algorithms are represented as Concrete Strategy classes, and they share a common Strategy interface. A Context object contains a reference to a Strategy. By changing the Context's Strategy, different behaviors can be obtained. Although these behaviors are different, the different strategies all operate on data from the Context.
The strategy pattern is one way that composition can be used as an alternative to subclassing. Rather than providing different behaviors via subclasses overriding methods in superclasses, the strategy pattern allows different behaviors to be placed in Concrete Strategy classes which share the common Strategy interface. A Context class is composed of a reference to a Strategy.
Here is an example of the strategy pattern. First, we'll define a Strategy interface. It declares a checkTemperature() method.

Strategy.java

package com.cakes;

public interface Strategy {

 boolean checkTemperature(int temperatureInF);

}
The HikeStrategy class is a concrete strategy class that implements the Strategy interface. The checkTemperature method is implemented so that if the temperature is between 50 and 90, it returns true. Otherwise it returns false.

HikeStrategy.java

package com.cakes;

public class HikeStrategy implements Strategy {

 @Override
 public boolean checkTemperature(int temperatureInF) {
  if ((temperatureInF >= 50) && (temperatureInF <= 90)) {
   return true;
  } else {
   return false;
  }
 }

}
The SkiStrategy implements the Strategy interface. If the temperature is 32 or less, the checkTemperature method returns true. Otherwise it returns false.

SkiStrategy.java

package com.cakes;

public class SkiStrategy implements Strategy {

 @Override
 public boolean checkTemperature(int temperatureInF) {
  if (temperatureInF <= 32) {
   return true;
  } else {
   return false;
  }
 }

}
The Context class contains a temperature and a reference to a Strategy. The Strategy can be changed, resulting in different behavior that operates on the same data in the Context. The result of this can be obtained from the Context via the getResult() method.

Context.java

package com.cakes;

public class Context {

 int temperatureInF;
 Strategy strategy;

 public Context(int temperatureInF, Strategy strategy) {
  this.temperatureInF = temperatureInF;
  this.strategy = strategy;
 }

 public void setStrategy(Strategy strategy) {
  this.strategy = strategy;
 }

 public int getTemperatureInF() {
  return temperatureInF;
 }

 public boolean getResult() {
  return strategy.checkTemperature(temperatureInF);
 }

}
The Demo class creates a Context object with a temperature of 60 and with a SkiStrategy. It displays the temperature from the context and whether that temperature is OK for skiing. After that, it sets the Strategy in the Context to HikeStrategy. It then displays the temperature from the context and whether that temperature is OK for hiking. 

Demo.java

package com.cakes; public class Demo { public static void main(String[] args) { int temperatureInF = 60; Strategy skiStrategy = new SkiStrategy(); Context context = new Context(temperatureInF, skiStrategy); System.out.println("Is the temperature (" + context.getTemperatureInF() + "F) good for skiing? " + context.getResult()); Strategy hikeStrategy = new HikeStrategy(); context.setStrategy(hikeStrategy); System.out.println("Is the temperature (" + context.getTemperatureInF() + "F) good for hiking? " + context.getResult()); } } The console output of executing Demo is shown here.

Console Output

Is the temperature (60F) good for skiing? false Is the temperature (60F) good for hiking? true

State Pattern

The state pattern is a behavioral object design pattern. The idea behind the state pattern is for an object to change its behavior depending on its state. In the state pattern, we have a Context class, and this class has a State reference to a Concrete State instance. The State interface declares particular methods that represent the behaviors of a particular state. Concrete States implement these behaviors. By changing a Context's Concrete State, we change its behavior. In essence, in the state pattern, a class (the Context) is supposed to behave like different classes depending on its state. The state pattern avoids the use of switch and if statements to change behavior.
Let's look at an example of the state pattern. First off, we'll define the EmotionalState interface. It declares two methods, sayHello() and sayGoodbye().

EmotionalState.java

package com.cakes;

// State
public interface EmotionalState {

 public String sayHello();

 public String sayGoodbye();

}
The HappyState class is a Concrete State that implements sayHello() and sayGoodbye() of EmotionalState. These messages are cheerful (representing a happy state).

HappyState.java

package com.cakes;

// Concrete State
public class HappyState implements EmotionalState {

 @Override
 public String sayGoodbye() {
  return "Bye, friend!";
 }

 @Override
 public String sayHello() {
  return "Hello, friend!";
 }

}
The SadState class also implements the EmotionalState interface. The messages are sad (representing a sad state).

SadState.java

package com.cakes;

//Concrete State
public class SadState implements EmotionalState {

 @Override
 public String sayGoodbye() {
  return "Bye. Sniff, sniff.";
 }

 @Override
 public String sayHello() {
  return "Hello. Sniff, sniff.";
 }

}
The Person class is the Context class. It contains an EmotionalState reference to a concrete state. In this example, we have Person implement the EmotionalState reference, and we pass the calls to Person's sayHello() and sayGoodbye() methods on to the corresponding methods on the emotionalState reference. As a result of this, a Person object behaves differently depending on the state of Person (ie, the current EmotionalState reference).

Person.java

package com.cakes;

// Context
public class Person implements EmotionalState {

 EmotionalState emotionalState;

 public Person(EmotionalState emotionalState) {
  this.emotionalState = emotionalState;
 }

 public void setEmotionalState(EmotionalState emotionalState) {
  this.emotionalState = emotionalState;
 }

 @Override
 public String sayGoodbye() {
  return emotionalState.sayGoodbye();
 }

 @Override
 public String sayHello() {
  return emotionalState.sayHello();
 }

}
The Demo class demonstrates the state pattern. First, it creates a Person object with a HappyState object. We display the results of sayHello() and sayGoodbyte() when the person object is in the happy state. Next, we change the person object's state with a SadState object. We display the results of sayHello() and sayGoodbyte(), and we see that in the sad state, the person object's behavior is different.

Demo.java

package com.cakes; public class Demo { public static void main(String[] args) { Person person = new Person(new HappyState()); System.out.println("Hello in happy state: " + person.sayHello()); System.out.println("Goodbye in happy state: " + person.sayGoodbye()); person.setEmotionalState(new SadState()); System.out.println("Hello in sad state: " + person.sayHello()); System.out.println("Goodbye in sad state: " + person.sayGoodbye()); } } The console output of executing Demo is shown here.

Console Output

Hello in happy state: Hello, friend! Goodbye in happy state: Bye, friend! Hello in sad state: Hello. Sniff, sniff. Goodbye in sad state: Bye. Sniff, sniff. Note that we don't necessarily need to have the Context (ie, Person) implement the EmotionalState interface. The behavioral changes could have been internal to the Context rather than exposing EmotionalState's methods to the outside. However, having the Context class implement the State interface allows us to directly access the different behaviors that result from the different states of the Context.

Observer Pattern

The observer pattern is a behavioral object design pattern. In the observer pattern, an object called the subject maintains a collection of objects called observers. When the subject changes, it notifies the observers. Observers can be added or removed from the collection of observers in the subject. The changes in state of the subject can be passed to the observers so that the observers can change their own state to reflect this change.
The subject has an interface that defines methods for attaching and detaching observers from the subject's collection of observers. This interface also features a notification method. This method should be called when the state of the subject changes. This notifies the observers that the subject's state has changed. The observers have an interface with a method to update the observer. This update method is called for each observer in the subject's notification method. Since this communication occurs via an interface, any concrete observer implementing the observer interface can be updated by the subject. This results in loose coupling between the subject and the observer classes.
Now we'll look at an example of the observer pattern. We'll start by creating an interface for the subject called WeatherSubject. This will declare three methods: addObserver(), removeObserver(), and doNotify().

WeatherSubject.java

package com.cakes;

public interface WeatherSubject {

 public void addObserver(WeatherObserver weatherObserver);

 public void removeObserver(WeatherObserver weatherObserver);

 public void doNotify();

}
We'll also create an interface for the observers called WeatherObserver. It features one method, a doUpdate() method.

WeatherObserver.java

package com.cakes;

public interface WeatherObserver {

 public void doUpdate(int temperature);
 
}
The WeatherStation class implements WeatherSubject. It is our subject class. It maintains a set of WeatherObservers which are added via addObserver() and removed via removeObserver(). When WeatherSubject's state changes via setTemperature(), the doNotify() method is called, which contacts all the WeatherObservers with the temperature via their doUpdate() methods.

WeatherStation.java

package com.cakes;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class WeatherStation implements WeatherSubject {

 Set weatherObservers;
 int temperature;

 public WeatherStation(int temperature) {
  weatherObservers = new HashSet();
  this.temperature = temperature;
 }

 @Override
 public void addObserver(WeatherObserver weatherObserver) {
  weatherObservers.add(weatherObserver);
 }

 @Override
 public void removeObserver(WeatherObserver weatherObserver) {
  weatherObservers.remove(weatherObserver);
 }

 @Override
 public void doNotify() {
  Iterator it = weatherObservers.iterator();
  while (it.hasNext()) {
   WeatherObserver weatherObserver = it.next();
   weatherObserver.doUpdate(temperature);
  }
 }

 public void setTemperature(int newTemperature) {
  System.out.println("\nWeather station setting temperature to " + newTemperature);
  temperature = newTemperature;
  doNotify();
 }

}
WeatherCustomer1 is an observer that implements WeatherObserver. Its doUpdate() method gets the current temperature from the WeatherStation and displays it.

WeatherCustomer1.java

package com.cakes; public class WeatherCustomer1 implements WeatherObserver { @Override public void doUpdate(int temperature) { System.out.println("Weather customer 1 just found out the temperature is:" + temperature); } } WeatherCustomer2 performs similar functionality as WeatherCustomer1.

WeatherCustomer2.java

package com.cakes; public class WeatherCustomer2 implements WeatherObserver { @Override public void doUpdate(int temperature) { System.out.println("Weather customer 2 just found out the temperature is:" + temperature); } } The Demo class demonstrates the observer pattern. It creates a WeatherStation and then a WeatherCustomer1 and a WeatherCustomer2. The two customers are added as observers to the weather station. Then the setTemperature() method of the weather station is called. This changes the state of the weather station and the customers are notified of this temperature update. Next, the WeatherCustomer1 object is removed from the station's collection of observers. Then, the setTemperature() method is called again. This results in the notification of the WeatherCustomer2 object.

Demo.java

package com.cakes; public class Demo { public static void main(String[] args) { WeatherStation weatherStation = new WeatherStation(33); WeatherCustomer1 wc1 = new WeatherCustomer1(); WeatherCustomer2 wc2 = new WeatherCustomer2(); weatherStation.addObserver(wc1); weatherStation.addObserver(wc2); weatherStation.setTemperature(34); weatherStation.removeObserver(wc1); weatherStation.setTemperature(35); } } The console output of executing Demo is shown here.

Console Output

Weather station setting temperature to 34 Weather customer 2 just found out the temperature is:34 Weather customer 1 just found out the temperature is:34 Weather station setting temperature to 35 Weather customer 2 just found out the temperature is:35 In a more advanced case, we might have given each observer a reference to the weather station object. This could allow the observer the ability to compare the state of the subject in detail with its own state and make any necessary updates to its own state.