I'm reading the third edition of Essential Java in an attempt to update my knowledge of Java, and the Flyweight
pattern comes up. By the way, I've never done this pattern properly, so I decided to understand and try it.
The Flyweight pattern is one of GoF's design patterns and is a pattern for solving the following problems.
I'm not sure if this is all.
Looking at the sequence, it looks like a cached pattern. Looking at the history item, it seems that it was used in the document editor, and if the font information, for example, the alphabet is 26 characters, if an instance of the character is created each time, a large number of instances will be generated. Therefore, it seems to be a mechanism to create only 26 instances and cache and return them.
Let's make a sample and understand it. In short, it's a factory cache, so it's easy. This time the subject is a little brute force, but it is an application that returns the sound of the place when you enter the string number of the guitar and the fret number. Since there are only 12 sounds, 12 instances should be enough. Try using Sound
as the Flyweight
object in the design pattern diagram.
Sound.java : Flyweight
package com.company;
public interface Sound {
void Play();
}
SoundImpl : Flywieght1
package com.company;
public class SoundImpl implements Sound{
private String note;
public SoundImpl(String note) {
this.note = note;
}
@Override
public void Play() {
System.out.println(note + "- ♪");
}
public String getNote() {
return note;
}
}
SoundFactory : FlywieghtFactory
package com.company;
import java.util.HashMap;
public class SoundFactory {
private static Map<String, Sound> sounds = new HashMap<String, Sound>();
public static Sound getSound(String note) {
if (sounds.containsKey(note)){
return sounds.get(note);
} else {
Sound sound = new SoundImpl(note);
sounds.put(note, sound);
return sound;
}
}
}
The Main swelled up and wasn't cool, but anyway, I got something that worked. The simple point is to give Factory a Map and cache it. It's easy.
Main.java
package com.company;
import java.util.HashMap;
public class Main {
private static HashMap<Integer, Integer> stringMap = new HashMap<Integer, Integer>();
private static HashMap<Integer, String> notes = new HashMap<Integer, String>();
private static void setup() {
stringMap.put(1, 4);
stringMap.put(2, 11);
stringMap.put(3, 7);
stringMap.put(4, 2);
stringMap.put(5, 9);
stringMap.put(6, 4);
notes.put(0, "C");
notes.put(1, "C#");
notes.put(2, "D");
notes.put(3, "D#");
notes.put(4, "E");
notes.put(5, "F");
notes.put(6, "F#");
notes.put(7, "G");
notes.put(8, "G#");
notes.put(9, "A");
notes.put(10, "A#");
notes.put(11, "B");
}
public static void main(String[] args) {
setup();
while(true) {
java.io.Console con = System.console();
if (con != null) {
String input = con.readLine("string:fret:");
if (input.contains("exit")) {
System.out.println("Closing ...");
System.exit(0);
} else {
String[] stringFret = input.split(":");
int openNote = stringMap.get(Integer.parseInt(stringFret[0]));
int fret = Integer.parseInt(stringFret[1]);
int note = openNote + fret;
if (note >= 12) {
note = note - 12;
}
String noteString = notes.get(new Integer(note));
Sound sound = SoundFactory.getSound(noteString);
sound.Play();
}
}
}
}
}
Since the main part was easy, I'm not very familiar with Java, so I decided to take steps. What should I do if it is in a concurrent state? HashMap
doesn't seem to be Thread Safe. If you want to make it Thread Safe, you can use HashTable
or ConcurrentHashMap
. What's the difference?
HashMap
and ConcurrentHashMap
is whether it is thread-safe or not. In other words, can it be used in a concurrent environment? It can't provide the same level of synchronization that HashTable
provides, but it's good enough for practical use.HashMap
returns almost the same collection as HashTable
when wrapped in Collections.synchronizedMap
. That is, all update events lock the entire Map
. In the case of ConcurrentHashMap
, ThreadSafe is designed to partition the entire Map and lock only a part of it.ConcurrentHashMap
is more scalable and has better performance than Synchronized HashMap
. In a single-threaded environment, HashMap
outperforms ConcurrentHashMap
.Looking at these comparisons, it seems clear that there are many cases where it makes sense to use ConcurrentHashMap
. Let's implement it.
Sounds easy.
Micronaut install
In my environment, the source of chocolatey
was rewritten, so I will restore it to the default. It can be installed by starting PowerShell with Administrator privileges.
$ choco isntall micronaut -s https://chocolatey.org/api/v2/
Generate project via template
You can generate a Maven project like this:
$ mn create-app flyweight-server --build maven
First of all, this will bring the server up. The point I was addicted to was that I wrote the code using IntelliJ, but the Terminal JAVA_HOME was not set and I got the error message Caused by: java.lang.IllegalArgumentException: invalid target release: 11
. It came out. Originally, only the Java8 JDK was in the path, so that's a problem, but I reminded myself that the basics of Java are to set JAVA_HOME and pass% JAVA_HOME% / bin in the path. Otherwise maven will not work properly.
$ cd flywieght-server
$ mvn clean package
$ java -jar .\target\flyweight-server-0.1.jar
I will add the controller. Micronaut seems to support various functions like a lightweight version of Spring. I just wanted an Http Server this time, so I'll look at the manual and write the code. To do ruling with a controller, it seems that you just have to write the controller. It's very easy.
FlyweightController.java
package flyweight.server;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/flyweight")
public class FlyweightController {
@Get(value = "/{string}/{fret}", produces = MediaType.TEXT_PLAIN)
public String index(Integer string, Integer fret){
return "String: " + string + " Fret: "+ fret;
}
}
Now that it works, I understand what the controller wants to know. Next, let's modify the Flyweight app. I think this works multithreaded, so let's use the ConcurrentHashMap
earlier.
How shorter it was than before. The computeIfAbsent
method now accepts functions and guarantees atomic behavior. This is the same atmosphere as C #'s ConcurrentDictionary. So, if it doesn't exist, it will make a new one and return it, otherwise it will return the existing one. The best!
SoundFactory.java
package flyweight.server;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class SoundFactory {
private static ConcurrentMap<String, Sound> sounds = new ConcurrentHashMap<String, Sound>();
public static Sound getSound(String note) {
return sounds.computeIfAbsent(note, n -> new SoundImpl(n));
}
}
Overall picture
FlyweightController.java
package flyweight.server;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/flyweight")
public class FlyweightController {
@Get(value = "/{string}/{fret}", produces = MediaType.TEXT_PLAIN)
public String index(Integer string, Integer fret){
Integer openNote = Constants.stringMap.get(string.intValue());
int note = openNote.intValue() + fret.intValue();
if (note >= 12) {
note = note - 12;
}
Sound sound = SoundFactory.getSound(Constants.notes.get(note));
return "String: " + string + " Fret: "+ fret + " Sound: " + sound.Play();
}
}
Constants.java
package flyweight.server;
import java.util.HashMap;
public class Constants {
static HashMap<Integer, Integer> stringMap = new HashMap<Integer, Integer>();
static HashMap<Integer, String> notes = new HashMap<Integer, String>();
static void setup() {
stringMap.put(1, 4);
stringMap.put(2, 11);
stringMap.put(3, 7);
stringMap.put(4, 2);
stringMap.put(5, 9);
stringMap.put(6, 4);
notes.put(0, "C");
notes.put(1, "C#");
notes.put(2, "D");
notes.put(3, "D#");
notes.put(4, "E");
notes.put(5, "F");
notes.put(6, "F#");
notes.put(7, "G");
notes.put(8, "G#");
notes.put(9, "A");
notes.put(10, "A#");
notes.put(11, "B");
}
}
Sound.java
package flyweight.server;
public interface Sound {
String Play();
}
SoundImpl.java
package flyweight.server;
public class SoundImpl implements Sound {
private String note;
public SoundImpl(String note) {
this.note = note;
}
public String Play() {
return this.note + "- note";
}
public String getNote() {
return this.note;
}
}
Application.java
package flyweight.server;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Constants.setup();
Micronaut.run(Application.class, args);
}
}
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing C:\Users\tsushi\Code\java\spike\micronaut\flyweight-server\target\flyweight-server-0.1.jar with C:\Users\tsushi\Code\java\spike\micronaut\flyweight-server\target\flyweight-server-0.1-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.444 s
[INFO] Finished at: 2020-10-26T14:16:48-07:00
[INFO] ------------------------------------------------------------------------
PS C:\Users\tsushi\Code\java\spike\micronaut\flyweight-server> java -jar .\target\flyweight-server-0.1.jar
←[36m14:17:24.022←[0;39m ←[1;30m[main]←[0;39m ←[34mINFO ←[0;39m ←[35mio.micronaut.runtime.Micronaut←[0;39m - Startup completed in 1154ms. Server Running: http://localhost:8080
I'm glad I learned the Flyweight
pattern and the ConcurrentHashMap
lightly today.
Resource
Recommended Posts