diff --git a/business-delegate/README.md b/business-delegate/README.md index b00c67819..886781632 100644 --- a/business-delegate/README.md +++ b/business-delegate/README.md @@ -9,21 +9,156 @@ tags: --- ## Intent + The Business Delegate pattern adds an abstraction layer between presentation and business tiers. By using the pattern we gain loose coupling between the tiers and encapsulate knowledge about how to locate, connect to, and interact with the business objects that make up the application. +## Explanation + +Real world example + +> A mobile phone application promises to stream any movie in existence to your phone. It captures +> the user's search string and passes this on to the business delegate. The business delegate +> selects the most suitable video streaming service and plays the video from there. + +In Plain Words + +> Business delegate adds an abstraction layer between the presentation and business tiers. + +Wikipedia says + +> Business delegate is a Java EE design pattern. This pattern is directing to reduce the coupling +> in between business services and the connected presentation tier, and to hide the implementation +> details of services (including lookup and accessibility of EJB architecture). Business delegates +> acts as an adaptor to invoke business objects from the presentation tier. + +**Programmatic Example** + +First, we have an abstraction for video streaming services and a couple of implementations. + +```java +public interface VideoStreamingService { + void doProcessing(); +} + +@Slf4j +public class NetflixService implements VideoStreamingService { + @Override + public void doProcessing() { + LOGGER.info("NetflixService is now processing"); + } +} + +@Slf4j +public class YouTubeService implements VideoStreamingService { + @Override + public void doProcessing() { + LOGGER.info("YouTubeService is now processing"); + } +} +``` + +Then we have a lookup service that decides which video streaming service is used. + +```java +@Setter +public class BusinessLookup { + + private NetflixService netflixService; + private YouTubeService youTubeService; + + public VideoStreamingService getBusinessService(String movie) { + if (movie.toLowerCase(Locale.ROOT).contains("die hard")) { + return netflixService; + } else { + return youTubeService; + } + } +} +``` + +The business delegate uses a business lookup to route movie playback requests to a suitable +video streaming service. + +```java +@Setter +public class BusinessDelegate { + + private BusinessLookup lookupService; + + public void playbackMovie(String movie) { + VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie); + videoStreamingService.doProcessing(); + } +} +``` + +The mobile client utilizes business delegate to call the business tier. + +```java +public class MobileClient { + + private final BusinessDelegate businessDelegate; + + public MobileClient(BusinessDelegate businessDelegate) { + this.businessDelegate = businessDelegate; + } + + public void playbackMovie(String movie) { + businessDelegate.playbackMovie(movie); + } +} +``` + +Finally, we can show the full example in action. + +```java + public static void main(String[] args) { + + // prepare the objects + var businessDelegate = new BusinessDelegate(); + var businessLookup = new BusinessLookup(); + businessLookup.setNetflixService(new NetflixService()); + businessLookup.setYouTubeService(new YouTubeService()); + businessDelegate.setLookupService(businessLookup); + + // create the client and use the business delegate + var client = new MobileClient(businessDelegate); + client.playbackMovie("Die Hard 2"); + client.playbackMovie("Maradona: The Greatest Ever"); + } +``` + +Here is the console output. + +``` +21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing +21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing +``` + ## Class diagram -![alt text](./etc/business-delegate.png "Business Delegate") + +![alt text](./etc/business-delegate.urm.png "Business Delegate") + +## Related patterns + +* [Service locator pattern](https://java-design-patterns.com/patterns/service-locator/) ## Applicability + Use the Business Delegate pattern when -* you want loose coupling between presentation and business tiers -* you want to orchestrate calls to multiple business services -* you want to encapsulate service lookups and service calls +* You want loose coupling between presentation and business tiers +* You want to orchestrate calls to multiple business services +* You want to encapsulate service lookups and service calls + +## Tutorials + +* [Business Delegate Pattern at TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) ## Credits * [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947) diff --git a/business-delegate/etc/business-delegate.png b/business-delegate/etc/business-delegate.png deleted file mode 100644 index 928cf9346..000000000 Binary files a/business-delegate/etc/business-delegate.png and /dev/null differ diff --git a/business-delegate/etc/business-delegate.ucls b/business-delegate/etc/business-delegate.ucls deleted file mode 100644 index 668a6579e..000000000 --- a/business-delegate/etc/business-delegate.ucls +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/business-delegate/etc/business-delegate.urm.png b/business-delegate/etc/business-delegate.urm.png new file mode 100644 index 000000000..4dca6c263 Binary files /dev/null and b/business-delegate/etc/business-delegate.urm.png differ diff --git a/business-delegate/etc/business-delegate.urm.puml b/business-delegate/etc/business-delegate.urm.puml index 40aa2d6f0..407e3e12d 100644 --- a/business-delegate/etc/business-delegate.urm.puml +++ b/business-delegate/etc/business-delegate.urm.puml @@ -5,53 +5,42 @@ package com.iluwatar.business.delegate { + main(args : String[]) {static} } class BusinessDelegate { - - businessService : BusinessService - lookupService : BusinessLookup - - serviceType : ServiceType + BusinessDelegate() - + doTask() - + setLookupService(businessLookup : BusinessLookup) - + setServiceType(serviceType : ServiceType) + + playbackMovie(movie : String) + + setLookupService(lookupService : BusinessLookup) } class BusinessLookup { - - ejbService : EjbService - - jmsService : JmsService + - netflixService : NetflixService + - youTubeService : YouTubeService + BusinessLookup() - + getBusinessService(serviceType : ServiceType) : BusinessService - + setEjbService(ejbService : EjbService) - + setJmsService(jmsService : JmsService) + + getBusinessService(movie : String) : VideoStreamingService + + setNetflixService(netflixService : NetflixService) + + setYouTubeService(youTubeService : YouTubeService) } - interface BusinessService { + class MobileClient { + - businessDelegate : BusinessDelegate + + MobileClient(businessDelegate : BusinessDelegate) + + playbackMovie(movie : String) + } + class NetflixService { + - LOGGER : Logger {static} + + NetflixService() + + doProcessing() + } + interface VideoStreamingService { + doProcessing() {abstract} } - class Client { - - businessDelegate : BusinessDelegate - + Client(businessDelegate : BusinessDelegate) - + doTask() - } - class EjbService { + class YouTubeService { - LOGGER : Logger {static} - + EjbService() + + YouTubeService() + doProcessing() } - class JmsService { - - LOGGER : Logger {static} - + JmsService() - + doProcessing() - } - enum ServiceType { - + EJB {static} - + JMS {static} - + valueOf(name : String) : ServiceType {static} - + values() : ServiceType[] {static} - } } -BusinessLookup --> "-ejbService" EjbService -BusinessDelegate --> "-serviceType" ServiceType -Client --> "-businessDelegate" BusinessDelegate -BusinessDelegate --> "-businessService" BusinessService +BusinessLookup --> "-netflixService" NetflixService +BusinessLookup --> "-youTubeService" YouTubeService +MobileClient --> "-businessDelegate" BusinessDelegate BusinessDelegate --> "-lookupService" BusinessLookup -BusinessLookup --> "-jmsService" JmsService -EjbService ..|> BusinessService -JmsService ..|> BusinessService +NetflixService ..|> VideoStreamingService +YouTubeService ..|> VideoStreamingService @enduml \ No newline at end of file diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java index 8bd4d12c6..e87ca9c64 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java @@ -33,9 +33,9 @@ package com.iluwatar.business.delegate; * retrieved through service lookups. The Business Delegate itself may contain business logic too * potentially tying together multiple service calls, exception handling, retrying etc. * - *

In this example the client ({@link Client}) utilizes a business delegate ( - * {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate - * service and makes the service call. + *

In this example the client ({@link MobileClient}) utilizes a business delegate ( + * {@link BusinessDelegate}) to search for movies in video streaming services. The Business Delegate + * then selects the appropriate service and makes the service call. */ public class App { @@ -46,18 +46,16 @@ public class App { */ public static void main(String[] args) { + // prepare the objects var businessDelegate = new BusinessDelegate(); var businessLookup = new BusinessLookup(); - businessLookup.setEjbService(new EjbService()); - businessLookup.setJmsService(new JmsService()); - + businessLookup.setNetflixService(new NetflixService()); + businessLookup.setYouTubeService(new YouTubeService()); businessDelegate.setLookupService(businessLookup); - businessDelegate.setServiceType(ServiceType.EJB); - var client = new Client(businessDelegate); - client.doTask(); - - businessDelegate.setServiceType(ServiceType.JMS); - client.doTask(); + // create the client and use the business delegate + var client = new MobileClient(businessDelegate); + client.playbackMovie("Die Hard 2"); + client.playbackMovie("Maradona: The Greatest Ever"); } } diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java index d1255bf5f..6246145e7 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java @@ -23,24 +23,18 @@ package com.iluwatar.business.delegate; +import lombok.Setter; + /** * BusinessDelegate separates the presentation and business tiers. */ +@Setter public class BusinessDelegate { private BusinessLookup lookupService; - private ServiceType serviceType; - public void setLookupService(BusinessLookup businessLookup) { - this.lookupService = businessLookup; - } - - public void setServiceType(ServiceType serviceType) { - this.serviceType = serviceType; - } - - public void doTask() { - BusinessService businessService = lookupService.getBusinessService(serviceType); - businessService.doProcessing(); + public void playbackMovie(String movie) { + VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie); + videoStreamingService.doProcessing(); } } diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java index 07ad6342e..0369c04e8 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java @@ -23,6 +23,7 @@ package com.iluwatar.business.delegate; +import java.util.Locale; import lombok.Setter; /** @@ -31,21 +32,21 @@ import lombok.Setter; @Setter public class BusinessLookup { - private EjbService ejbService; + private NetflixService netflixService; - private JmsService jmsService; + private YouTubeService youTubeService; /** - * Gets service instance based on service type. + * Gets service instance based on given movie search string. * - * @param serviceType Type of service instance to be returned. + * @param movie Search string for the movie. * @return Service instance. */ - public BusinessService getBusinessService(ServiceType serviceType) { - if (serviceType.equals(ServiceType.EJB)) { - return ejbService; + public VideoStreamingService getBusinessService(String movie) { + if (movie.toLowerCase(Locale.ROOT).contains("die hard")) { + return netflixService; } else { - return jmsService; + return youTubeService; } } } diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java similarity index 84% rename from business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java rename to business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java index 4d5c151e3..2cfb6f344 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java @@ -24,17 +24,17 @@ package com.iluwatar.business.delegate; /** - * Client utilizes BusinessDelegate to call the business tier. + * MobileClient utilizes BusinessDelegate to call the business tier. */ -public class Client { +public class MobileClient { private final BusinessDelegate businessDelegate; - public Client(BusinessDelegate businessDelegate) { + public MobileClient(BusinessDelegate businessDelegate) { this.businessDelegate = businessDelegate; } - public void doTask() { - businessDelegate.doTask(); + public void playbackMovie(String movie) { + businessDelegate.playbackMovie(movie); } } diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java similarity index 89% rename from business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java rename to business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java index 6813dfec1..ae9da8747 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java @@ -26,13 +26,13 @@ package com.iluwatar.business.delegate; import lombok.extern.slf4j.Slf4j; /** - * Service EJB implementation. + * NetflixService implementation. */ @Slf4j -public class EjbService implements BusinessService { +public class NetflixService implements VideoStreamingService { @Override public void doProcessing() { - LOGGER.info("EjbService is now processing"); + LOGGER.info("NetflixService is now processing"); } } diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java deleted file mode 100644 index 503f30797..000000000 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2021 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. - */ - -package com.iluwatar.business.delegate; - -/** - * Enumeration for service types. - */ -public enum ServiceType { - - EJB, - JMS -} diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java similarity index 92% rename from business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java rename to business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java index 20845841f..3c8b7e3fb 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java @@ -24,9 +24,9 @@ package com.iluwatar.business.delegate; /** - * Interface for service implementations. + * Interface for video streaming service implementations. */ -public interface BusinessService { +public interface VideoStreamingService { void doProcessing(); } diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java similarity index 89% rename from business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java rename to business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java index 932c5038d..aa79e7309 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java @@ -26,13 +26,13 @@ package com.iluwatar.business.delegate; import lombok.extern.slf4j.Slf4j; /** - * Service JMS implementation. + * YouTubeService implementation. */ @Slf4j -public class JmsService implements BusinessService { +public class YouTubeService implements VideoStreamingService { @Override public void doProcessing() { - LOGGER.info("JmsService is now processing"); + LOGGER.info("YouTubeService is now processing"); } } diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java index b59759328..8cd5e2021 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java @@ -26,25 +26,20 @@ package com.iluwatar.business.delegate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** - * The Business Delegate pattern adds an abstraction layer between the presentation and business - * tiers. By using the pattern we gain loose coupling between the tiers. The Business Delegate - * encapsulates knowledge about how to locate, connect to, and interact with the business objects - * that make up the application. - * - *

Some of the services the Business Delegate uses are instantiated directly, and some can be - * retrieved through service lookups. The Business Delegate itself may contain business logic too - * potentially tying together multiple service calls, exception handling, retrying etc. + * Tests for the {@link BusinessDelegate} */ class BusinessDelegateTest { - private EjbService ejbService; + private NetflixService netflixService; - private JmsService jmsService; + private YouTubeService youTubeService; private BusinessDelegate businessDelegate; @@ -54,19 +49,19 @@ class BusinessDelegateTest { */ @BeforeEach public void setup() { - ejbService = spy(new EjbService()); - jmsService = spy(new JmsService()); + netflixService = spy(new NetflixService()); + youTubeService = spy(new YouTubeService()); BusinessLookup businessLookup = spy(new BusinessLookup()); - businessLookup.setEjbService(ejbService); - businessLookup.setJmsService(jmsService); + businessLookup.setNetflixService(netflixService); + businessLookup.setYouTubeService(youTubeService); businessDelegate = spy(new BusinessDelegate()); businessDelegate.setLookupService(businessLookup); } /** - * In this example the client ({@link Client}) utilizes a business delegate ( + * In this example the client ({@link MobileClient}) utilizes a business delegate ( * {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate * service and makes the service call. */ @@ -74,26 +69,20 @@ class BusinessDelegateTest { void testBusinessDelegate() { // setup a client object - var client = new Client(businessDelegate); - - // set the service type - businessDelegate.setServiceType(ServiceType.EJB); + var client = new MobileClient(businessDelegate); // action - client.doTask(); + client.playbackMovie("Die hard"); - // verifying that the businessDelegate was used by client during doTask() method. - verify(businessDelegate).doTask(); - verify(ejbService).doProcessing(); - - // set the service type - businessDelegate.setServiceType(ServiceType.JMS); + // verifying that the businessDelegate was used by client during playbackMovie() method. + verify(businessDelegate).playbackMovie(anyString()); + verify(netflixService).doProcessing(); // action - client.doTask(); + client.playbackMovie("Maradona"); // verifying that the businessDelegate was used by client during doTask() method. - verify(businessDelegate, times(2)).doTask(); - verify(jmsService).doProcessing(); + verify(businessDelegate, times(2)).playbackMovie(anyString()); + verify(youTubeService).doProcessing(); } } diff --git a/bytecode/README.md b/bytecode/README.md index ee3f96ed8..115f0b96a 100644 --- a/bytecode/README.md +++ b/bytecode/README.md @@ -9,18 +9,234 @@ tags: --- ## Intent -Allows to encode behaviour as instructions for virtual machine. + +Allows encoding behavior as instructions for a virtual machine. + +## Explanation + +Real world example + +> A team is working on a new game where wizards battle against each other. The wizard behavior +> needs to be carefully adjusted and iterated hundreds of times through playtesting. It's not +> optimal to ask the programmer to make changes each time the game designer wants to vary the +> behavior, so the wizard behavior is implemented as a data-driven virtual machine. + +In plain words + +> Bytecode pattern enables behavior driven by data instead of code. + +[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) documentation +states: + +> An instruction set defines the low-level operations that can be performed. A series of +> instructions is encoded as a sequence of bytes. A virtual machine executes these instructions one +> at a time, using a stack for intermediate values. By combining instructions, complex high-level +> behavior can be defined. + +**Programmatic Example** + +One of the most important game objects is the `Wizard` class. + +```java +@AllArgsConstructor +@Setter +@Getter +@Slf4j +public class Wizard { + + private int health; + private int agility; + private int wisdom; + private int numberOfPlayedSounds; + private int numberOfSpawnedParticles; + + public void playSound() { + LOGGER.info("Playing sound"); + numberOfPlayedSounds++; + } + + public void spawnParticles() { + LOGGER.info("Spawning particles"); + numberOfSpawnedParticles++; + } +} +``` + +Next, we show the available instructions for our virtual machine. Each of the instructions has its +own semantics on how it operates with the stack data. For example, the ADD instruction takes the top +two items from the stack, adds them together and pushes the result to the stack. + +```java +@AllArgsConstructor +@Getter +public enum Instruction { + + LITERAL(1), // e.g. "LITERAL 0", push 0 to stack + SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health + SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom + SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility + PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound + SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles + GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health + GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility + GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom + ADD(10), // e.g. "ADD", pop 2 values, push their sum + DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division + // ... +} +``` + +At the heart of our example is the `VirtualMachine` class. It takes instructions as input and +executes them to provide the game object behavior. + +```java +@Getter +@Slf4j +public class VirtualMachine { + + private final Stack stack = new Stack<>(); + + private final Wizard[] wizards = new Wizard[2]; + + public VirtualMachine() { + wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + } + + public VirtualMachine(Wizard wizard1, Wizard wizard2) { + wizards[0] = wizard1; + wizards[1] = wizard2; + } + + public void execute(int[] bytecode) { + for (var i = 0; i < bytecode.length; i++) { + Instruction instruction = Instruction.getInstruction(bytecode[i]); + switch (instruction) { + case LITERAL: + // Read the next byte from the bytecode. + int value = bytecode[++i]; + // Push the next value to stack + stack.push(value); + break; + case SET_AGILITY: + var amount = stack.pop(); + var wizard = stack.pop(); + setAgility(wizard, amount); + break; + case SET_WISDOM: + amount = stack.pop(); + wizard = stack.pop(); + setWisdom(wizard, amount); + break; + case SET_HEALTH: + amount = stack.pop(); + wizard = stack.pop(); + setHealth(wizard, amount); + break; + case GET_HEALTH: + wizard = stack.pop(); + stack.push(getHealth(wizard)); + break; + case GET_AGILITY: + wizard = stack.pop(); + stack.push(getAgility(wizard)); + break; + case GET_WISDOM: + wizard = stack.pop(); + stack.push(getWisdom(wizard)); + break; + case ADD: + var a = stack.pop(); + var b = stack.pop(); + stack.push(a + b); + break; + case DIVIDE: + a = stack.pop(); + b = stack.pop(); + stack.push(b / a); + break; + case PLAY_SOUND: + wizard = stack.pop(); + getWizards()[wizard].playSound(); + break; + case SPAWN_PARTICLES: + wizard = stack.pop(); + getWizards()[wizard].spawnParticles(); + break; + default: + throw new IllegalArgumentException("Invalid instruction value"); + } + LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack()); + } + } + + public void setHealth(int wizard, int amount) { + wizards[wizard].setHealth(amount); + } + // other setters -> + // ... +} +``` + +Now we can show the full example utilizing the virtual machine. + +```java + public static void main(String[] args) { + + var vm = new VirtualMachine( + new Wizard(45, 7, 11, 0, 0), + new Wizard(36, 18, 8, 0, 0)); + + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2")); + vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH")); + } +``` + +Here is the console output. + +``` +16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0] +16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains [] +``` ## Class diagram + ![alt text](./etc/bytecode.urm.png "Bytecode class diagram") ## Applicability + Use the Bytecode pattern when you have a lot of behavior you need to define and your game’s implementation language isn’t a good fit because: -* it’s too low-level, making it tedious or error-prone to program in. -* iterating on it takes too long due to slow compile times or other tooling issues. -* it has too much trust. If you want to ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of the codebase. +* It’s too low-level, making it tedious or error-prone to program in. +* Iterating on it takes too long due to slow compile times or other tooling issues. +* It has too much trust. If you want to ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of the codebase. + +## Related patterns + +* [Interpreter](https://java-design-patterns.com/patterns/interpreter/) ## Credits diff --git a/bytecode/etc/bytecode.png b/bytecode/etc/bytecode.png deleted file mode 100644 index 31b6bc6ed..000000000 Binary files a/bytecode/etc/bytecode.png and /dev/null differ diff --git a/bytecode/etc/bytecode.ucls b/bytecode/etc/bytecode.ucls deleted file mode 100644 index 3ec390458..000000000 --- a/bytecode/etc/bytecode.ucls +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/bytecode/etc/bytecode.urm.png b/bytecode/etc/bytecode.urm.png index 82036a78a..51335fa0a 100644 Binary files a/bytecode/etc/bytecode.urm.png and b/bytecode/etc/bytecode.urm.png differ diff --git a/bytecode/etc/bytecode.urm.puml b/bytecode/etc/bytecode.urm.puml index d675ae398..224e909ef 100644 --- a/bytecode/etc/bytecode.urm.puml +++ b/bytecode/etc/bytecode.urm.puml @@ -3,7 +3,6 @@ package com.iluwatar.bytecode { class App { - LOGGER : Logger {static} + App() - - interpretInstruction(instruction : String, vm : VirtualMachine) {static} + main(args : String[]) {static} } enum Instruction { @@ -18,22 +17,25 @@ package com.iluwatar.bytecode { + SET_HEALTH {static} + SET_WISDOM {static} + SPAWN_PARTICLES {static} - - value : int + - intValue : int + getInstruction(value : int) : Instruction {static} + getIntValue() : int + valueOf(name : String) : Instruction {static} + values() : Instruction[] {static} } class VirtualMachine { + - LOGGER : Logger {static} - stack : Stack - wizards : Wizard[] + VirtualMachine() + + VirtualMachine(wizard1 : Wizard, wizard2 : Wizard) + execute(bytecode : int[]) + getAgility(wizard : int) : int + getHealth(wizard : int) : int + getStack() : Stack + getWisdom(wizard : int) : int + getWizards() : Wizard[] + - randomInt(min : int, max : int) : int + setAgility(wizard : int, amount : int) + setHealth(wizard : int, amount : int) + setWisdom(wizard : int, amount : int) @@ -45,7 +47,7 @@ package com.iluwatar.bytecode { - numberOfPlayedSounds : int - numberOfSpawnedParticles : int - wisdom : int - + Wizard() + + Wizard(health : int, agility : int, wisdom : int, numberOfPlayedSounds : int, numberOfSpawnedParticles : int) + getAgility() : int + getHealth() : int + getNumberOfPlayedSounds() : int @@ -54,6 +56,8 @@ package com.iluwatar.bytecode { + playSound() + setAgility(agility : int) + setHealth(health : int) + + setNumberOfPlayedSounds(numberOfPlayedSounds : int) + + setNumberOfSpawnedParticles(numberOfSpawnedParticles : int) + setWisdom(wisdom : int) + spawnParticles() } diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/App.java b/bytecode/src/main/java/com/iluwatar/bytecode/App.java index 4d41fe6b9..f76a8e6a4 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/App.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/App.java @@ -49,33 +49,21 @@ public class App { */ public static void main(String[] args) { - var wizard = new Wizard(); - wizard.setHealth(45); - wizard.setAgility(7); - wizard.setWisdom(11); + var vm = new VirtualMachine( + new Wizard(45, 7, 11, 0, 0), + new Wizard(36, 18, 8, 0, 0)); - var vm = new VirtualMachine(); - vm.getWizards()[0] = wizard; - - String literal = "LITERAL 0"; - - interpretInstruction(literal, vm); - interpretInstruction(literal, vm); - interpretInstruction("GET_HEALTH", vm); - interpretInstruction(literal, vm); - interpretInstruction("GET_AGILITY", vm); - interpretInstruction(literal, vm); - interpretInstruction("GET_WISDOM ", vm); - interpretInstruction("ADD", vm); - interpretInstruction("LITERAL 2", vm); - interpretInstruction("DIVIDE", vm); - interpretInstruction("ADD", vm); - interpretInstruction("SET_HEALTH", vm); - } - - private static void interpretInstruction(String instruction, VirtualMachine vm) { - vm.execute(InstructionConverterUtil.convertToByteCode(instruction)); - var stack = vm.getStack(); - LOGGER.info(instruction + String.format("%" + (12 - instruction.length()) + "s", "") + stack); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2")); + vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH")); } } diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java index 52b6e325a..ad16fb7f2 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java @@ -33,17 +33,17 @@ import lombok.Getter; @Getter public enum Instruction { - LITERAL(1), - SET_HEALTH(2), - SET_WISDOM(3), - SET_AGILITY(4), - PLAY_SOUND(5), - SPAWN_PARTICLES(6), - GET_HEALTH(7), - GET_AGILITY(8), - GET_WISDOM(9), - ADD(10), - DIVIDE(11); + LITERAL(1), // e.g. "LITERAL 0", push 0 to stack + SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health + SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom + SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility + PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound + SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles + GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health + GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility + GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom + ADD(10), // e.g. "ADD", pop 2 values, push their sum + DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division private final int intValue; diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java index 526a8a377..ee223b5d8 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java @@ -24,12 +24,15 @@ package com.iluwatar.bytecode; import java.util.Stack; +import java.util.concurrent.ThreadLocalRandom; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; /** * Implementation of virtual machine. */ @Getter +@Slf4j public class VirtualMachine { private final Stack stack = new Stack<>(); @@ -37,12 +40,21 @@ public class VirtualMachine { private final Wizard[] wizards = new Wizard[2]; /** - * Constructor. + * No-args constructor. */ public VirtualMachine() { - for (var i = 0; i < wizards.length; i++) { - wizards[i] = new Wizard(); - } + wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + } + + /** + * Constructor taking the wizards as arguments. + */ + public VirtualMachine(Wizard wizard1, Wizard wizard2) { + wizards[0] = wizard1; + wizards[1] = wizard2; } /** @@ -57,6 +69,7 @@ public class VirtualMachine { case LITERAL: // Read the next byte from the bytecode. int value = bytecode[++i]; + // Push the next value to stack stack.push(value); break; case SET_AGILITY: @@ -107,6 +120,7 @@ public class VirtualMachine { default: throw new IllegalArgumentException("Invalid instruction value"); } + LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack()); } } @@ -133,4 +147,8 @@ public class VirtualMachine { public int getAgility(int wizard) { return wizards[wizard].getAgility(); } + + private int randomInt(int min, int max) { + return ThreadLocalRandom.current().nextInt(min, max + 1); + } } diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java b/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java index 4db97f119..ce62b276a 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java @@ -23,6 +23,7 @@ package com.iluwatar.bytecode; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -31,16 +32,15 @@ import lombok.extern.slf4j.Slf4j; * This class represent game objects which properties can be changed by instructions interpreted by * virtual machine. */ +@AllArgsConstructor @Setter @Getter @Slf4j public class Wizard { private int health; - private int agility; private int wisdom; - private int numberOfPlayedSounds; private int numberOfSpawnedParticles; @@ -53,5 +53,4 @@ public class Wizard { LOGGER.info("Spawning particles"); numberOfSpawnedParticles++; } - } diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java index 196d2b55d..ab7643129 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java @@ -73,6 +73,4 @@ public class InstructionConverterUtil { return false; } } - - } diff --git a/model-view-viewmodel/README.md b/model-view-viewmodel/README.md index 6df1ea5d7..928b5a06f 100644 --- a/model-view-viewmodel/README.md +++ b/model-view-viewmodel/README.md @@ -99,11 +99,11 @@ public class BookViewModel { ``` -Note: -* To deploy this, go to model-view-viewmodel folder and run: -* mvn clean install -* mvn jetty:run -Djetty.http.port=9911 -* In browser, http://localhost:9911/model-view-viewmodel/ +To deploy the example, go to model-view-viewmodel folder and run: + +* `mvn clean install` +* `mvn jetty:run -Djetty.http.port=9911` +* Open browser to address: http://localhost:9911/model-view-viewmodel/ ## Class diagram