diff --git a/context-object/README.md b/context-object/README.md new file mode 100644 index 000000000..0c4462aa6 --- /dev/null +++ b/context-object/README.md @@ -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) \ No newline at end of file diff --git a/context-object/etc/context-object.png b/context-object/etc/context-object.png new file mode 100644 index 000000000..a1f670812 Binary files /dev/null and b/context-object/etc/context-object.png differ diff --git a/context-object/etc/context-object.urm.puml b/context-object/etc/context-object.urm.puml new file mode 100644 index 000000000..fa5eb1ace --- /dev/null +++ b/context-object/etc/context-object.urm.puml @@ -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 ..|> "<>" ServiceContext +LayerB ..|> LayerA +@enduml \ No newline at end of file diff --git a/context-object/pom.xml b/context-object/pom.xml new file mode 100644 index 000000000..4eb0237a6 --- /dev/null +++ b/context-object/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + context-object + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.compositeview.App + + + + + + + + + \ No newline at end of file diff --git a/context-object/src/main/java/com/iluwatar/context/object/App.java b/context-object/src/main/java/com/iluwatar/context/object/App.java new file mode 100644 index 000000000..c76ddd93b --- /dev/null +++ b/context-object/src/main/java/com/iluwatar/context/object/App.java @@ -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. + * + *

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.

+ */ +@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()); + } +} diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerA.java b/context-object/src/main/java/com/iluwatar/context/object/LayerA.java new file mode 100644 index 000000000..f7a634448 --- /dev/null +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerA.java @@ -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); + } +} diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerB.java b/context-object/src/main/java/com/iluwatar/context/object/LayerB.java new file mode 100644 index 000000000..8e4bd9c0f --- /dev/null +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerB.java @@ -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); + } +} diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerC.java b/context-object/src/main/java/com/iluwatar/context/object/LayerC.java new file mode 100644 index 000000000..b6a23b539 --- /dev/null +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerC.java @@ -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); + } +} diff --git a/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java b/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java new file mode 100644 index 000000000..9f334d374 --- /dev/null +++ b/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java @@ -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; +} diff --git a/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java b/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java new file mode 100644 index 000000000..d3fe442df --- /dev/null +++ b/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java @@ -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(); + } +} diff --git a/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java b/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java new file mode 100644 index 000000000..d65cb440d --- /dev/null +++ b/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java @@ -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[]{})); + } +} diff --git a/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java b/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java new file mode 100644 index 000000000..48d9ce76b --- /dev/null +++ b/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java @@ -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)"); + } +}