Skip to content

Commit 122e6ed

Browse files
noamgrinchohbus
andauthored
feature: resolve iluwatar#1282 for Lockable Object pattern. (iluwatar#1702)
* Added Lockable-Object pattern. Closes iluwatar#1282. * Refactor method name. * Refactor sonar lint bugs. * Added tests and enum Constants. * Increase coverage. * Changed @DaTa to Getters and Setters. * Iluwatar's comment on pull request iluwatar#1702. * Fixed codes mells. * Incremented wait time to 3 seconds. * Reduced wait time to 2 seconds. * Cleaned Code Smells. * Incremented wait time, removed cool down. * Refactored README.md file. Co-authored-by: Subhrodip Mohanta <subhrodipmohanta@gmail.com>
1 parent ea3c9d9 commit 122e6ed

22 files changed

+1118
-0
lines changed

lockable-object/README.md

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
---
2+
layout: pattern
3+
title: Lockable Object
4+
folder: lockable-object
5+
permalink: /patterns/lockable-object/
6+
categories: Concurrency
7+
tags:
8+
- Performance
9+
---
10+
11+
12+
## Intent
13+
14+
The lockable object design pattern ensures that there is only one user using the target object. Compared to the built-in synchronization mechanisms such as using the `synchronized` keyword, this pattern can lock objects for an undetermined time and is not tied to the duration of the request.
15+
16+
## Explanation
17+
18+
19+
Real-world example
20+
21+
>The Sword Of Aragorn is a legendary object that only one creature can possess at the time.
22+
>Every creature in the middle earth wants to possess is, so as long as it's not locked, every creature will fight for it.
23+
24+
Under the hood
25+
26+
>In this particular module, the SwordOfAragorn.java is a class that implements the Lockable interface.
27+
It reaches the goal of the Lockable-Object pattern by implementing unlock() and unlock() methods using
28+
thread-safety logic. The thread-safety logic is implemented with the built-in monitor mechanism of Java.
29+
The SwordOfAaragorn.java has an Object property called "synchronizer". In every crucial concurrency code block,
30+
it's synchronizing the block by using the synchronizer.
31+
32+
33+
34+
**Programmatic Example**
35+
36+
```java
37+
/** This interface describes the methods to be supported by a lockable object. */
38+
public interface Lockable {
39+
40+
/**
41+
* Checks if the object is locked.
42+
*
43+
* @return true if it is locked.
44+
*/
45+
boolean isLocked();
46+
47+
/**
48+
* locks the object with the creature as the locker.
49+
*
50+
* @param creature as the locker.
51+
* @return true if the object was locked successfully.
52+
*/
53+
boolean lock(Creature creature);
54+
55+
/**
56+
* Unlocks the object.
57+
*
58+
* @param creature as the locker.
59+
*/
60+
void unlock(Creature creature);
61+
62+
/**
63+
* Gets the locker.
64+
*
65+
* @return the Creature that holds the object. Returns null if no one is locking.
66+
*/
67+
Creature getLocker();
68+
69+
/**
70+
* Returns the name of the object.
71+
*
72+
* @return the name of the object.
73+
*/
74+
String getName();
75+
}
76+
77+
```
78+
79+
We have defined that according to our context, the object must implement the Lockable interface.
80+
81+
For example, the SwordOfAragorn class:
82+
83+
```java
84+
public class SwordOfAragorn implements Lockable {
85+
86+
private Creature locker;
87+
private final Object synchronizer;
88+
private static final String NAME = "The Sword of Aragorn";
89+
90+
public SwordOfAragorn() {
91+
this.locker = null;
92+
this.synchronizer = new Object();
93+
}
94+
95+
@Override
96+
public boolean isLocked() {
97+
return this.locker != null;
98+
}
99+
100+
@Override
101+
public boolean lock(@NonNull Creature creature) {
102+
synchronized (synchronizer) {
103+
LOGGER.info("{} is now trying to acquire {}!", creature.getName(), this.getName());
104+
if (!isLocked()) {
105+
locker = creature;
106+
return true;
107+
} else {
108+
if (!locker.getName().equals(creature.getName())) {
109+
return false;
110+
}
111+
}
112+
}
113+
return false;
114+
}
115+
116+
@Override
117+
public void unlock(@NonNull Creature creature) {
118+
synchronized (synchronizer) {
119+
if (locker != null && locker.getName().equals(creature.getName())) {
120+
locker = null;
121+
LOGGER.info("{} is now free!", this.getName());
122+
}
123+
if (locker != null) {
124+
throw new LockingException("You cannot unlock an object you are not the owner of.");
125+
}
126+
}
127+
}
128+
129+
@Override
130+
public Creature getLocker() {
131+
return this.locker;
132+
}
133+
134+
@Override
135+
public String getName() {
136+
return NAME;
137+
}
138+
}
139+
```
140+
141+
According to our context, there are creatures that are looking for the sword, so must define the parent class:
142+
143+
```java
144+
public abstract class Creature {
145+
146+
private String name;
147+
private CreatureType type;
148+
private int health;
149+
private int damage;
150+
Set<Lockable> instruments;
151+
152+
protected Creature(@NonNull String name) {
153+
this.name = name;
154+
this.instruments = new HashSet<>();
155+
}
156+
157+
/**
158+
* Reaches for the Lockable and tried to hold it.
159+
*
160+
* @param lockable as the Lockable to lock.
161+
* @return true of Lockable was locked by this creature.
162+
*/
163+
public boolean acquire(@NonNull Lockable lockable) {
164+
if (lockable.lock(this)) {
165+
instruments.add(lockable);
166+
return true;
167+
}
168+
return false;
169+
}
170+
171+
/** Terminates the Creature and unlocks all of the Lockable that it posses. */
172+
public synchronized void kill() {
173+
LOGGER.info("{} {} has been slayed!", type, name);
174+
for (Lockable lockable : instruments) {
175+
lockable.unlock(this);
176+
}
177+
this.instruments.clear();
178+
}
179+
180+
/**
181+
* Attacks a foe.
182+
*
183+
* @param creature as the foe to be attacked.
184+
*/
185+
public synchronized void attack(@NonNull Creature creature) {
186+
creature.hit(getDamage());
187+
}
188+
189+
/**
190+
* When a creature gets hit it removed the amount of damage from the creature's life.
191+
*
192+
* @param damage as the damage that was taken.
193+
*/
194+
public synchronized void hit(int damage) {
195+
if (damage < 0) {
196+
throw new IllegalArgumentException("Damage cannot be a negative number");
197+
}
198+
if (isAlive()) {
199+
setHealth(getHealth() - damage);
200+
if (!isAlive()) {
201+
kill();
202+
}
203+
}
204+
}
205+
206+
/**
207+
* Checks if the creature is still alive.
208+
*
209+
* @return true of creature is alive.
210+
*/
211+
public synchronized boolean isAlive() {
212+
return getHealth() > 0;
213+
}
214+
215+
}
216+
```
217+
218+
As mentioned before, we have classes that extend the Creature class, such as Elf, Orc, and Human.
219+
220+
Finally, the following program will simulate a battle for the sword:
221+
222+
```java
223+
public class App implements Runnable {
224+
225+
private static final int WAIT_TIME = 3;
226+
private static final int WORKERS = 2;
227+
private static final int MULTIPLICATION_FACTOR = 3;
228+
229+
/**
230+
* main method.
231+
*
232+
* @param args as arguments for the main method.
233+
*/
234+
public static void main(String[] args) {
235+
var app = new App();
236+
app.run();
237+
}
238+
239+
@Override
240+
public void run() {
241+
// The target object for this example.
242+
var sword = new SwordOfAragorn();
243+
// Creation of creatures.
244+
List<Creature> creatures = new ArrayList<>();
245+
for (var i = 0; i < WORKERS; i++) {
246+
creatures.add(new Elf(String.format("Elf %s", i)));
247+
creatures.add(new Orc(String.format("Orc %s", i)));
248+
creatures.add(new Human(String.format("Human %s", i)));
249+
}
250+
int totalFiends = WORKERS * MULTIPLICATION_FACTOR;
251+
ExecutorService service = Executors.newFixedThreadPool(totalFiends);
252+
// Attach every creature and the sword is a Fiend to fight for the sword.
253+
for (var i = 0; i < totalFiends; i = i + MULTIPLICATION_FACTOR) {
254+
service.submit(new Feind(creatures.get(i), sword));
255+
service.submit(new Feind(creatures.get(i + 1), sword));
256+
service.submit(new Feind(creatures.get(i + 2), sword));
257+
}
258+
// Wait for program to terminate.
259+
try {
260+
if (!service.awaitTermination(WAIT_TIME, TimeUnit.SECONDS)) {
261+
LOGGER.info("The master of the sword is now {}.", sword.getLocker().getName());
262+
}
263+
} catch (InterruptedException e) {
264+
LOGGER.error(e.getMessage());
265+
Thread.currentThread().interrupt();
266+
} finally {
267+
service.shutdown();
268+
}
269+
}
270+
}
271+
```
272+
273+
## Applicability
274+
275+
The Lockable Object pattern is ideal for non distributed applications, that needs to be thread-safe
276+
and keeping their domain models in memory(in contrast to persisted models such as databases).
277+
278+
## Class diagram
279+
280+
![alt text](./etc/lockable-object.urm.png "Lockable Object class diagram")
281+
282+
283+
## Credits
284+
285+
* [Lockable Object - Chapter 10.3, J2EE Design Patterns, O'Reilly](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf)
113 KB
Loading
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
@startuml
2+
package com.iluwatar.lockableobject.domain {
3+
abstract class Creature {
4+
- LOGGER : Logger {static}
5+
- damage : int
6+
- health : int
7+
~ instruments : Set<Lockable>
8+
- name : String
9+
- type : CreatureType
10+
+ Creature(name : String)
11+
+ acquire(lockable : Lockable) : boolean
12+
+ attack(creature : Creature)
13+
# canEqual(other : Object) : boolean
14+
+ equals(o : Object) : boolean
15+
+ getDamage() : int
16+
+ getHealth() : int
17+
+ getInstruments() : Set<Lockable>
18+
+ getName() : String
19+
+ getType() : CreatureType
20+
+ hashCode() : int
21+
+ hit(damage : int)
22+
+ isAlive() : boolean
23+
+ kill()
24+
+ setDamage(damage : int)
25+
+ setHealth(health : int)
26+
+ setInstruments(instruments : Set<Lockable>)
27+
+ setName(name : String)
28+
+ setType(type : CreatureType)
29+
+ toString() : String
30+
}
31+
enum CreatureType {
32+
+ ELF {static}
33+
+ HUMAN {static}
34+
+ ORC {static}
35+
+ valueOf(name : String) : CreatureType {static}
36+
+ values() : CreatureType[] {static}
37+
}
38+
class Elf {
39+
+ Elf(name : String)
40+
}
41+
class Feind {
42+
- LOGGER : Logger {static}
43+
- feind : Creature
44+
- target : Lockable
45+
+ Feind(feind : Creature, target : Lockable)
46+
- fightForTheSword(reacher : Creature, holder : Creature, sword : Lockable)
47+
+ run()
48+
}
49+
class Human {
50+
+ Human(name : String)
51+
}
52+
class Orc {
53+
+ Orc(name : String)
54+
}
55+
}
56+
package com.iluwatar.lockableobject {
57+
class App {
58+
- LOGGER : Logger {static}
59+
- MULTIPLICATION_FACTOR : int {static}
60+
- WAIT_TIME : int {static}
61+
- WORKERS : int {static}
62+
+ App()
63+
+ main(args : String[]) {static}
64+
}
65+
interface Lockable {
66+
+ getLocker() : Creature {abstract}
67+
+ getName() : String {abstract}
68+
+ isLocked() : boolean {abstract}
69+
+ lock(Creature) : boolean {abstract}
70+
+ unlock(Creature) {abstract}
71+
}
72+
class SwordOfAragorn {
73+
- LOGGER : Logger {static}
74+
- NAME : String {static}
75+
- locker : Creature
76+
- synchronizer : Object
77+
+ SwordOfAragorn()
78+
+ getLocker() : Creature
79+
+ getName() : String
80+
+ isLocked() : boolean
81+
+ lock(creature : Creature) : boolean
82+
+ unlock(creature : Creature)
83+
}
84+
}
85+
Creature --> "-type" CreatureType
86+
Creature --> "-instruments" Lockable
87+
Feind --> "-feind" Creature
88+
Feind --> "-target" Lockable
89+
SwordOfAragorn --> "-locker" Creature
90+
SwordOfAragorn ..|> Lockable
91+
Elf --|> Creature
92+
Human --|> Creature
93+
Orc --|> Creature
94+
@enduml

0 commit comments

Comments
 (0)