feature: Add context object pattern (#2304)

* Add context object pattern and corresponding tests

* Update context pattern

* Add README, class diagram, puml file.
Add toString method in ServiceContext.java.
Add tests for coverage.

* Improvements:
Remove plugin in pom file
Add comments in app.java
Change local variable keyword to var in app.java
Use lombok for getters, setters and tostring
Change method signature in context object

* Refreshing environment-1

* Reconfigure pom file.

Co-authored-by: Alvis Chan <u7287079@edu.anu.au>
Co-authored-by: u7287079 <u7287079@anu.edu.au>
This commit is contained in:
Chak-C
2022-12-29 20:17:28 +11:00
committed by GitHub
parent 695e12379d
commit e223459db8
12 changed files with 498 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
---
title: Context object
category: Creational
language: en
tags:
- Data access
---
## Name / classification
Context Object
## Also known as
Context, Encapsulate Context
## Intent
Decouple data from protocol-specific classes and store the scoped data in an object independent
of the underlying protocol technology.
## Explanation
Real-world example
> This application has different layers labelled A, B and C with each extracting specific information
> from a similar context for further use in the software. Passing down each pieces of information
> individually would be inefficient, a method to efficiently store and pass information is needed.
In plain words
> Create an object and store the data there and pass this object to where it is needed.
[Core J2EE Patterns](http://corej2eepatterns.com/ContextObject.htm) says
> Use a Context Object to encapsulate state in a protocol-independent way to be shared throughout your application.
**Programmatic Example**
We define what data a service context object contains.
```Java
public class ServiceContext {
String ACCOUNT_SERVICE, SESSION_SERVICE, SEARCH_SERVICE;
public void setACCOUNT_SERVICE(String ACCOUNT_SERVICE) {
this.ACCOUNT_SERVICE = ACCOUNT_SERVICE;
}
public void setSESSION_SERVICE(String SESSION_SERVICE) {
this.SESSION_SERVICE = SESSION_SERVICE;
}
public void setSEARCH_SERVICE(String SEARCH_SERVICE) {
this.SEARCH_SERVICE = SEARCH_SERVICE;
}
public String getACCOUNT_SERVICE() {
return ACCOUNT_SERVICE;
}
public String getSESSION_SERVICE() {
return SESSION_SERVICE;
}
public String getSEARCH_SERVICE() {
return SEARCH_SERVICE;
}
public String toString() { return ACCOUNT_SERVICE + " " + SESSION_SERVICE + " " + SEARCH_SERVICE;}
}
```
Create an interface used in parts of the application for context objects to be created.
```Java
public class ServiceContextFactory {
public static ServiceContext createContext() {
return new ServiceContext();
}
}
```
Instantiate the context object in the first layer and the adjoining layer upcalls the context in the current layer, which
then further structures the object.
```Java
public class LayerA {
private static ServiceContext context;
public LayerA() {
context = ServiceContextFactory.createContext();
}
public static ServiceContext getContext() {
return context;
}
public void addAccountInfo(String accountService) {
context.setACCOUNT_SERVICE(accountService);
}
}
public class LayerB {
private static ServiceContext context;
public LayerB(LayerA layerA) {
this.context = layerA.getContext();
}
public static ServiceContext getContext() {
return context;
}
public void addSessionInfo(String sessionService) {
context.setSESSION_SERVICE(sessionService);
}
}
public class LayerC {
public static ServiceContext context;
public LayerC(LayerB layerB) {
this.context = layerB.getContext();
}
public static ServiceContext getContext() {
return context;
}
public void addSearchInfo(String searchService) {
context.setSEARCH_SERVICE(searchService);
}
}
```
Here is the context object and layers in action.
```Java
var layerA = new LayerA();
layerA.addAccountInfo(SERVICE);
LOGGER.info("Context = {}",layerA.getContext());
var layerB = new LayerB(layerA);
layerB.addSessionInfo(SERVICE);
LOGGER.info("Context = {}",layerB.getContext());
var layerC = new LayerC(layerB);
layerC.addSearchInfo(SERVICE);
LOGGER.info("Context = {}",layerC.getContext());
```
Program output:
```Java
Context = SERVICE null null
Context = SERVICE SERVICE null
Context = SERVICE SERVICE SERVICE
```
## Class diagram
![alt text](./etc/context-object.png "Context object")
## Application
Use the Context Object pattern for:
* Sharing information across different system layers.
* Decoupling software data from protocol-specific contexts.
* Exposing only the relevant API's within the context.
## Known uses
* [Spring: ApplicationContext](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html)
* [Oracle: SecurityContext](https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/SecurityContext.html)
* [Oracle: ServletContext](https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html)
## Credits
* [J2EE Design Patterns](http://corej2eepatterns.com/ContextObject.htm)
* [Allan Kelly - The Encapsulate Context Pattern](https://accu.org/journals/overload/12/63/kelly_246/)
* [Arvid S. Krishna et al. - Context Object](https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf)
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

@@ -0,0 +1,50 @@
@startuml
package com.iluwatar.context.object {
class App {
- LOGGER : Logger {static}
+ App()
+ main(args : String[]) {static}
}
class ServiceContext {
- ACCOUNT_SERVICE : String
- SESSION_SERVICE : String
- SEARCH_SERVICE : String
+ ServiceContext()
+ getACCOUNT_SERVICE() : String
+ getSESSION_SERVICE() : String
+ getSEARCH_SERVICE() : String
+ setACCOUNT_SERVICE(service : String)
+ setSESSION_SERVICE(service : String)
+ setSEARCH_SERVICE(service : String)
}
class ServiceContextFactory {
+ ServiceContextFactory()
+ createContext() : ServiceContext
}
class LayerA {
- context : ServiceContext
+ LayerA()
+ getContext() : ServiceContext
+ addAccountInfo()
}
class LayerB {
- context : ServiceContext
+ LayerB(layerA : LayerA)
+ getContext() : ServiceContext
+ addAccountInfo()
}
class LayerC {
- context : ServiceContext
+ LayerC(layerB : LayerB)
+ getContext() : ServiceContext
+ addAccountInfo()
}
}
LayerC ..|> LayerB
ServiceContext --> LayerC
ServiceContext --> LayerB
ServiceContext --> LayerA
ServiceContextFactory ..|> "<<creates>>" ServiceContext
LayerB ..|> LayerA
@enduml
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
The MIT License
Copyright © 2014-2022 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<artifactId>context-object</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.compositeview.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,41 @@
package com.iluwatar.context.object;
import lombok.extern.slf4j.Slf4j;
/**
* In the context object pattern, information and data from underlying protocol-specific classes/systems is decoupled
* and stored into a protocol-independent object in an organised format. The pattern ensures the data contained within
* the context object can be shared and further structured between different layers of a software system.
*
* <p> In this example we show how a context object {@link ServiceContext} can be initiated, edited and passed/retrieved
* in different layers of the program ({@link LayerA}, {@link LayerB}, {@link LayerC}) through use of static methods. </p>
*/
@Slf4j
public class App {
private static final String SERVICE = "SERVICE";
/**
* Program entry point.
* @param args command line args
*/
public static void main(String[] args) {
//Initiate first layer and add service information into context
var layerA = new LayerA();
layerA.addAccountInfo(SERVICE);
LOGGER.info("Context = {}",layerA.getContext());
//Initiate second layer and preserving information retrieved in first layer through passing context object
var layerB = new LayerB(layerA);
layerB.addSessionInfo(SERVICE);
LOGGER.info("Context = {}",layerB.getContext());
//Initiate third layer and preserving information retrieved in first and second layer through passing context object
var layerC = new LayerC(layerB);
layerC.addSearchInfo(SERVICE);
LOGGER.info("Context = {}",layerC.getContext());
}
}
@@ -0,0 +1,17 @@
package com.iluwatar.context.object;
import lombok.Getter;
@Getter
public class LayerA {
private ServiceContext context;
public LayerA() {
context = ServiceContextFactory.createContext();
}
public void addAccountInfo(String accountService) {
context.setAccountService(accountService);
}
}
@@ -0,0 +1,17 @@
package com.iluwatar.context.object;
import lombok.Getter;
@Getter
public class LayerB {
private ServiceContext context;
public LayerB(LayerA layerA) {
this.context = layerA.getContext();
}
public void addSessionInfo(String sessionService) {
context.setSessionService(sessionService);
}
}
@@ -0,0 +1,17 @@
package com.iluwatar.context.object;
import lombok.Getter;
@Getter
public class LayerC {
public ServiceContext context;
public LayerC(LayerB layerB) {
this.context = layerB.getContext();
}
public void addSearchInfo(String searchService) {
context.setSearchService(searchService);
}
}
@@ -0,0 +1,16 @@
package com.iluwatar.context.object;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* Where context objects are defined.
*/
@ToString
@Getter
@Setter
public class ServiceContext {
String AccountService, SessionService, SearchService;
}
@@ -0,0 +1,11 @@
package com.iluwatar.context.object;
/**
* An interface to create context objects passed through layers.
*/
public class ServiceContextFactory {
public static ServiceContext createContext() {
return new ServiceContext();
}
}
@@ -0,0 +1,17 @@
package com.iluwatar.contect.object;
import com.iluwatar.context.object.App;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
public class AppTest {
/**
* Test example app runs without error.
*/
@Test
void shouldExecuteWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}
@@ -0,0 +1,66 @@
package com.iluwatar.contect.object;
import com.iluwatar.context.object.LayerA;
import com.iluwatar.context.object.LayerB;
import com.iluwatar.context.object.LayerC;
import com.iluwatar.context.object.ServiceContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* Date: 10/24/2022 - 3:18
*
* @author Chak Chan
*/
public class ServiceContextTest {
private static final String SERVICE = "SERVICE";
private LayerA layerA;
@BeforeEach
void initiateLayerA() {
this.layerA = new LayerA();
}
@Test
void testSameContextPassedBetweenLayers() {
ServiceContext context1 = layerA.getContext();
var layerB = new LayerB(layerA);
ServiceContext context2 = layerB.getContext();
var layerC = new LayerC(layerB);
ServiceContext context3 = layerC.getContext();
assertSame(context1, context2);
assertSame(context2, context3);
assertSame(context3, context1);
}
@Test
void testScopedDataPassedBetweenLayers() {
layerA.addAccountInfo(SERVICE);
var layerB = new LayerB(layerA);
var layerC = new LayerC(layerB);
layerC.addSearchInfo(SERVICE);
ServiceContext context = layerC.getContext();
assertEquals(SERVICE,context.getAccountService());
assertNull(context.getSessionService());
assertEquals(SERVICE,context.getSearchService());
}
@Test
void testToString() {
assertEquals(layerA.getContext().toString(),"ServiceContext(AccountService=null, SessionService=null, SearchService=null)");
layerA.addAccountInfo(SERVICE);
assertEquals(layerA.getContext().toString(), "ServiceContext(AccountService=SERVICE, SessionService=null, SearchService=null)");
var layerB = new LayerB(layerA);
layerB.addSessionInfo(SERVICE);
var layerC = new LayerC(layerB);
assertEquals(layerC.getContext().toString(), "ServiceContext(AccountService=SERVICE, SessionService=SERVICE, SearchService=null)");
}
}