Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitdea7ecf

Browse files
ignite1771ohbus
andauthored
iluwatar#1317 Special Case Pattern (iluwatar#1624)
*iluwatar#1317 Add Special Case PatternTo focus on pattern itself, I implement DB andmaintenance lock by the singleton instance.*iluwatar#1317 Add special cases unit testsAssert the logger output(ref:https://stackoverflow.com/a/52229629)*iluwatar#1317 Add README.mdAdd Special Case Pattern README*iluwatar#1317 Format: add a new line to end of fileCo-authored-by: Subhrodip Mohanta <subhrodipmohanta@gmail.com>
1 parentbbc4fdf commitdea7ecf

21 files changed

+1084
-0
lines changed

‎pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@
200200
<module>factory</module>
201201
<module>separated-interface</module>
202202
<module>data-transfer-object-enum-impl</module>
203+
<module>special-case</module>
203204
</modules>
204205

205206
<repositories>

‎special-case/README.md

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
---
2+
layout:pattern
3+
title:Special Case
4+
folder:special-case
5+
permalink:/patterns/special-case/
6+
categories:Behavioral
7+
tags:
8+
-Extensibility
9+
---
10+
11+
##Intent
12+
13+
Define some special cases, and encapsulates them into subclasses that provide different special behaviors.
14+
15+
##Explanation
16+
17+
Real world example
18+
19+
>In an e-commerce system, presentation layer expects application layer to produce certain view model.
20+
>We have a successful scenario, in which receipt view model contains actual data from the purchase,
21+
>and a couple of failure scenarios.
22+
23+
In plain words
24+
25+
>Special Case pattern allows returning non-null real objects that perform special behaviors.
26+
27+
In[Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html) says
28+
the difference from Null Object Pattern
29+
30+
>If you’ll pardon the unresistable pun, I see Null Object as special case of Special Case.
31+
32+
**Programmatic Example**
33+
34+
To focus on the pattern itself, we implement DB and maintenance lock of the e-commerce system by the singleton instance.
35+
36+
```java
37+
publicclassDb {
38+
privatestaticDb instance;
39+
privateMap<String,User> userName2User;
40+
privateMap<User,Account> user2Account;
41+
privateMap<String,Product> itemName2Product;
42+
43+
publicstaticDbgetInstance() {
44+
if (instance==null) {
45+
synchronized (Db.class) {
46+
if (instance==null) {
47+
instance=newDb();
48+
instance.userName2User=newHashMap<>();
49+
instance.user2Account=newHashMap<>();
50+
instance.itemName2Product=newHashMap<>();
51+
}
52+
}
53+
}
54+
return instance;
55+
}
56+
57+
publicvoidseedUser(StringuserName,Doubleamount) {
58+
User user=newUser(userName);
59+
instance.userName2User.put(userName, user);
60+
Account account=newAccount(amount);
61+
instance.user2Account.put(user, account);
62+
}
63+
64+
publicvoidseedItem(StringitemName,Doubleprice) {
65+
Product item=newProduct(price);
66+
itemName2Product.put(itemName, item);
67+
}
68+
69+
publicUserfindUserByUserName(StringuserName) {
70+
if (!userName2User.containsKey(userName)) {
71+
returnnull;
72+
}
73+
return userName2User.get(userName);
74+
}
75+
76+
publicAccountfindAccountByUser(Useruser) {
77+
if (!user2Account.containsKey(user)) {
78+
returnnull;
79+
}
80+
return user2Account.get(user);
81+
}
82+
83+
publicProductfindProductByItemName(StringitemName) {
84+
if (!itemName2Product.containsKey(itemName)) {
85+
returnnull;
86+
}
87+
return itemName2Product.get(itemName);
88+
}
89+
90+
publicclassUser {
91+
privateString userName;
92+
93+
publicUser(StringuserName) {
94+
this.userName= userName;
95+
}
96+
97+
publicStringgetUserName() {
98+
return userName;
99+
}
100+
101+
publicReceiptDtopurchase(Productitem) {
102+
returnnewReceiptDto(item.getPrice());
103+
}
104+
}
105+
106+
publicclassAccount {
107+
privateDouble amount;
108+
109+
publicAccount(Doubleamount) {
110+
this.amount= amount;
111+
}
112+
113+
publicMoneyTransactionwithdraw(Doubleprice) {
114+
if (price> amount) {
115+
returnnull;
116+
}
117+
returnnewMoneyTransaction(amount, price);
118+
}
119+
120+
publicDoublegetAmount() {
121+
return amount;
122+
}
123+
}
124+
125+
publicclassProduct {
126+
privateDouble price;
127+
128+
publicProduct(Doubleprice) {
129+
this.price= price;
130+
}
131+
132+
publicDoublegetPrice() {
133+
return price;
134+
}
135+
}
136+
}
137+
138+
publicclassMaintenanceLock {
139+
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(MaintenanceLock.class);
140+
141+
privatestaticMaintenanceLock instance;
142+
privateboolean lock=true;
143+
144+
publicstaticMaintenanceLockgetInstance() {
145+
if (instance==null) {
146+
synchronized (MaintenanceLock.class) {
147+
if (instance==null) {
148+
instance=newMaintenanceLock();
149+
}
150+
}
151+
}
152+
return instance;
153+
}
154+
155+
publicbooleanisLock() {
156+
return lock;
157+
}
158+
159+
publicvoidsetLock(booleanlock) {
160+
this.lock= lock;
161+
LOGGER.info("Maintenance lock is set to:"+ lock);
162+
}
163+
}
164+
```
165+
166+
Let's first introduce presentation layer, the receipt view model interface and its implementation of successful scenario.
167+
168+
```java
169+
publicinterfaceReceiptViewModel {
170+
voidshow();
171+
}
172+
173+
publicclassReceiptDtoimplementsReceiptViewModel {
174+
175+
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(ReceiptDto.class);
176+
177+
privateDouble price;
178+
179+
publicReceiptDto(Doubleprice) {
180+
this.price= price;
181+
}
182+
183+
publicDoublegetPrice() {
184+
return price;
185+
}
186+
187+
@Override
188+
publicvoidshow() {
189+
LOGGER.info("Receipt:"+ price+" paid");
190+
}
191+
}
192+
```
193+
194+
And here are the implementations of failure scenarios, which are the special cases.
195+
196+
```java
197+
publicclassDownForMaintenanceimplementsReceiptViewModel {
198+
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(DownForMaintenance.class);
199+
200+
@Override
201+
publicvoidshow() {
202+
LOGGER.info("Down for maintenance");
203+
}
204+
}
205+
206+
publicclassInvalidUserimplementsReceiptViewModel {
207+
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(InvalidUser.class);
208+
209+
privatefinalString userName;
210+
211+
publicInvalidUser(StringuserName) {
212+
this.userName= userName;
213+
}
214+
215+
@Override
216+
publicvoidshow() {
217+
LOGGER.info("Invalid user:"+ userName);
218+
}
219+
}
220+
221+
publicclassOutOfStockimplementsReceiptViewModel {
222+
223+
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(OutOfStock.class);
224+
225+
privateString userName;
226+
privateString itemName;
227+
228+
publicOutOfStock(StringuserName,StringitemName) {
229+
this.userName= userName;
230+
this.itemName= itemName;
231+
}
232+
233+
@Override
234+
publicvoidshow() {
235+
LOGGER.info("Out of stock:"+ itemName+" for user ="+ userName+" to buy");
236+
}
237+
}
238+
239+
publicclassInsufficientFundsimplementsReceiptViewModel {
240+
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(InsufficientFunds.class);
241+
242+
privateString userName;
243+
privateDouble amount;
244+
privateString itemName;
245+
246+
publicInsufficientFunds(StringuserName,Doubleamount,StringitemName) {
247+
this.userName= userName;
248+
this.amount= amount;
249+
this.itemName= itemName;
250+
}
251+
252+
@Override
253+
publicvoidshow() {
254+
LOGGER.info("Insufficient funds:"+ amount+" of user:"+ userName
255+
+" for buying item:"+ itemName);
256+
}
257+
}
258+
```
259+
260+
Second, here's the application layer, the application services implementation and the domain services implementation.
261+
262+
```java
263+
publicclassApplicationServicesImplimplementsApplicationServices {
264+
privateDomainServicesImpl domain=newDomainServicesImpl();
265+
266+
@Override
267+
publicReceiptViewModelloggedInUserPurchase(StringuserName,StringitemName) {
268+
if (isDownForMaintenance()) {
269+
returnnewDownForMaintenance();
270+
}
271+
returnthis.domain.purchase(userName, itemName);
272+
}
273+
274+
privatebooleanisDownForMaintenance() {
275+
returnMaintenanceLock.getInstance().isLock();
276+
}
277+
}
278+
279+
publicclassDomainServicesImplimplementsDomainServices {
280+
publicReceiptViewModelpurchase(StringuserName,StringitemName) {
281+
Db.User user=Db.getInstance().findUserByUserName(userName);
282+
if (user==null) {
283+
returnnewInvalidUser(userName);
284+
}
285+
286+
Db.Account account=Db.getInstance().findAccountByUser(user);
287+
return purchase(user, account, itemName);
288+
}
289+
290+
privateReceiptViewModelpurchase(Db.Useruser,Db.Accountaccount,StringitemName) {
291+
Db.Product item=Db.getInstance().findProductByItemName(itemName);
292+
if (item==null) {
293+
returnnewOutOfStock(user.getUserName(), itemName);
294+
}
295+
296+
ReceiptDto receipt= user.purchase(item);
297+
MoneyTransaction transaction= account.withdraw(receipt.getPrice());
298+
if (transaction==null) {
299+
returnnewInsufficientFunds(user.getUserName(), account.getAmount(), itemName);
300+
}
301+
302+
return receipt;
303+
}
304+
}
305+
```
306+
307+
Finally, the client send requests the application services to get the presentation view.
308+
309+
```java
310+
// DB seeding
311+
LOGGER.info("Db seeding:"+"1 user: {\"ignite1771\", amount = 1000.0},"
312+
+"2 products: {\"computer\": price = 800.0,\"car\": price = 20000.0}");
313+
Db.getInstance().seedUser("ignite1771",1000.0);
314+
Db.getInstance().seedItem("computer",800.0);
315+
Db.getInstance().seedItem("car",20000.0);
316+
317+
var applicationServices=newApplicationServicesImpl();
318+
ReceiptViewModel receipt;
319+
320+
LOGGER.info("[REQUEST] User:"+"abc123"+" buy product:"+"tv");
321+
receipt= applicationServices.loggedInUserPurchase("abc123","tv");
322+
receipt.show();
323+
MaintenanceLock.getInstance().setLock(false);
324+
LOGGER.info("[REQUEST] User:"+"abc123"+" buy product:"+"tv");
325+
receipt= applicationServices.loggedInUserPurchase("abc123","tv");
326+
receipt.show();
327+
LOGGER.info("[REQUEST] User:"+"ignite1771"+" buy product:"+"tv");
328+
receipt= applicationServices.loggedInUserPurchase("ignite1771","tv");
329+
receipt.show();
330+
LOGGER.info("[REQUEST] User:"+"ignite1771"+" buy product:"+"car");
331+
receipt= applicationServices.loggedInUserPurchase("ignite1771","car");
332+
receipt.show();
333+
LOGGER.info("[REQUEST] User:"+"ignite1771"+" buy product:"+"computer");
334+
receipt= applicationServices.loggedInUserPurchase("ignite1771","computer");
335+
receipt.show();
336+
```
337+
338+
Program output of every request:
339+
340+
```
341+
Down for maintenance
342+
Invalid user: abc123
343+
Out of stock: tv for user = ignite1771 to buy
344+
Insufficient funds: 1000.0 of user: ignite1771 for buying item: car
345+
Receipt: 800.0 paid
346+
```
347+
348+
##Class diagram
349+
350+
![alt text](./etc/special_case_urm.png"Special Case")
351+
352+
##Applicability
353+
354+
Use the Special Case pattern when
355+
356+
* You have multiple places in the system that have the same behavior after a conditional check
357+
for a particular class instance, or the same behavior after a null check.
358+
* Return a real object that performs the real behavior, instead of a null object that performs nothing.
359+
360+
##Tutorial
361+
362+
*[Special Case Tutorial](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case)
363+
364+
##Credits
365+
366+
*[How to Reduce Cyclomatic Complexity Part 2: Special Case Pattern](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case)
367+
*[Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html)
368+
*[Special Case](https://www.martinfowler.com/eaaCatalog/specialCase.html)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp