Compare commits
2 Commits
all-contri
...
all-contri
Author | SHA1 | Date | |
---|---|---|---|
|
7e44713ae2 | ||
|
39f1c34c96 |
@@ -1359,24 +1359,6 @@
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ignite1771",
|
||||
"name": "ignite1771",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/59446563?v=4",
|
||||
"profile": "https://github.com/ignite1771",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "demirhalil",
|
||||
"name": "Halil Demir",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/22895118?v=4",
|
||||
"profile": "https://github.com/demirhalil",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 4,
|
||||
|
@@ -10,12 +10,12 @@
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
<br/>
|
||||
|
||||
Read in different language : [**CN**](/zh/README.md),[**KR**](/ko/README.md),[**FR**](/fr/README.md),[**TR**](/tr/README.md),
|
||||
Read in different language : [**CN**](/zh/README.md),[**KR**](/ko/README.md),[**FR**](/fr/README.md),
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -299,8 +299,6 @@ This project is licensed under the terms of the MIT license.
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://libkhadir.fr"><img src="https://avatars1.githubusercontent.com/u/45130488?v=4?s=100" width="100px;" alt=""/><br /><sub><b>KHADIR Tayeb</b></sub></a><br /><a href="#translation-tkhadir" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/ignite1771"><img src="https://avatars2.githubusercontent.com/u/59446563?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ignite1771</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ignite1771" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/demirhalil"><img src="https://avatars1.githubusercontent.com/u/22895118?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Halil Demir</b></sub></a><br /><a href="#translation-demirhalil" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 802 B |
1
pom.xml
1
pom.xml
@@ -200,7 +200,6 @@
|
||||
<module>factory</module>
|
||||
<module>separated-interface</module>
|
||||
<module>data-transfer-object-enum-impl</module>
|
||||
<module>special-case</module>
|
||||
</modules>
|
||||
|
||||
<repositories>
|
||||
|
@@ -1,368 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Special Case
|
||||
folder: special-case
|
||||
permalink: /patterns/special-case/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Define some special cases, and encapsulates them into subclasses that provide different special behaviors.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> In an e-commerce system, presentation layer expects application layer to produce certain view model.
|
||||
> We have a successful scenario, in which receipt view model contains actual data from the purchase,
|
||||
> and a couple of failure scenarios.
|
||||
|
||||
In plain words
|
||||
|
||||
> Special Case pattern allows returning non-null real objects that perform special behaviors.
|
||||
|
||||
In [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html) says
|
||||
the difference from Null Object Pattern
|
||||
|
||||
> If you’ll pardon the unresistable pun, I see Null Object as special case of Special Case.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
To focus on the pattern itself, we implement DB and maintenance lock of the e-commerce system by the singleton instance.
|
||||
|
||||
```java
|
||||
public class Db {
|
||||
private static Db instance;
|
||||
private Map<String, User> userName2User;
|
||||
private Map<User, Account> user2Account;
|
||||
private Map<String, Product> itemName2Product;
|
||||
|
||||
public static Db getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Db.class) {
|
||||
if (instance == null) {
|
||||
instance = new Db();
|
||||
instance.userName2User = new HashMap<>();
|
||||
instance.user2Account = new HashMap<>();
|
||||
instance.itemName2Product = new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void seedUser(String userName, Double amount) {
|
||||
User user = new User(userName);
|
||||
instance.userName2User.put(userName, user);
|
||||
Account account = new Account(amount);
|
||||
instance.user2Account.put(user, account);
|
||||
}
|
||||
|
||||
public void seedItem(String itemName, Double price) {
|
||||
Product item = new Product(price);
|
||||
itemName2Product.put(itemName, item);
|
||||
}
|
||||
|
||||
public User findUserByUserName(String userName) {
|
||||
if (!userName2User.containsKey(userName)) {
|
||||
return null;
|
||||
}
|
||||
return userName2User.get(userName);
|
||||
}
|
||||
|
||||
public Account findAccountByUser(User user) {
|
||||
if (!user2Account.containsKey(user)) {
|
||||
return null;
|
||||
}
|
||||
return user2Account.get(user);
|
||||
}
|
||||
|
||||
public Product findProductByItemName(String itemName) {
|
||||
if (!itemName2Product.containsKey(itemName)) {
|
||||
return null;
|
||||
}
|
||||
return itemName2Product.get(itemName);
|
||||
}
|
||||
|
||||
public class User {
|
||||
private String userName;
|
||||
|
||||
public User(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public ReceiptDto purchase(Product item) {
|
||||
return new ReceiptDto(item.getPrice());
|
||||
}
|
||||
}
|
||||
|
||||
public class Account {
|
||||
private Double amount;
|
||||
|
||||
public Account(Double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public MoneyTransaction withdraw(Double price) {
|
||||
if (price > amount) {
|
||||
return null;
|
||||
}
|
||||
return new MoneyTransaction(amount, price);
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
||||
public class Product {
|
||||
private Double price;
|
||||
|
||||
public Product(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MaintenanceLock {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class);
|
||||
|
||||
private static MaintenanceLock instance;
|
||||
private boolean lock = true;
|
||||
|
||||
public static MaintenanceLock getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (MaintenanceLock.class) {
|
||||
if (instance == null) {
|
||||
instance = new MaintenanceLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean isLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
public void setLock(boolean lock) {
|
||||
this.lock = lock;
|
||||
LOGGER.info("Maintenance lock is set to: " + lock);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's first introduce presentation layer, the receipt view model interface and its implementation of successful scenario.
|
||||
|
||||
```java
|
||||
public interface ReceiptViewModel {
|
||||
void show();
|
||||
}
|
||||
|
||||
public class ReceiptDto implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class);
|
||||
|
||||
private Double price;
|
||||
|
||||
public ReceiptDto(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Receipt: " + price + " paid");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And here are the implementations of failure scenarios, which are the special cases.
|
||||
|
||||
```java
|
||||
public class DownForMaintenance implements ReceiptViewModel {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class);
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Down for maintenance");
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidUser implements ReceiptViewModel {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class);
|
||||
|
||||
private final String userName;
|
||||
|
||||
public InvalidUser(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Invalid user: " + userName);
|
||||
}
|
||||
}
|
||||
|
||||
public class OutOfStock implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class);
|
||||
|
||||
private String userName;
|
||||
private String itemName;
|
||||
|
||||
public OutOfStock(String userName, String itemName) {
|
||||
this.userName = userName;
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy");
|
||||
}
|
||||
}
|
||||
|
||||
public class InsufficientFunds implements ReceiptViewModel {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class);
|
||||
|
||||
private String userName;
|
||||
private Double amount;
|
||||
private String itemName;
|
||||
|
||||
public InsufficientFunds(String userName, Double amount, String itemName) {
|
||||
this.userName = userName;
|
||||
this.amount = amount;
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Insufficient funds: " + amount + " of user: " + userName
|
||||
+ " for buying item: " + itemName);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Second, here's the application layer, the application services implementation and the domain services implementation.
|
||||
|
||||
```java
|
||||
public class ApplicationServicesImpl implements ApplicationServices {
|
||||
private DomainServicesImpl domain = new DomainServicesImpl();
|
||||
|
||||
@Override
|
||||
public ReceiptViewModel loggedInUserPurchase(String userName, String itemName) {
|
||||
if (isDownForMaintenance()) {
|
||||
return new DownForMaintenance();
|
||||
}
|
||||
return this.domain.purchase(userName, itemName);
|
||||
}
|
||||
|
||||
private boolean isDownForMaintenance() {
|
||||
return MaintenanceLock.getInstance().isLock();
|
||||
}
|
||||
}
|
||||
|
||||
public class DomainServicesImpl implements DomainServices {
|
||||
public ReceiptViewModel purchase(String userName, String itemName) {
|
||||
Db.User user = Db.getInstance().findUserByUserName(userName);
|
||||
if (user == null) {
|
||||
return new InvalidUser(userName);
|
||||
}
|
||||
|
||||
Db.Account account = Db.getInstance().findAccountByUser(user);
|
||||
return purchase(user, account, itemName);
|
||||
}
|
||||
|
||||
private ReceiptViewModel purchase(Db.User user, Db.Account account, String itemName) {
|
||||
Db.Product item = Db.getInstance().findProductByItemName(itemName);
|
||||
if (item == null) {
|
||||
return new OutOfStock(user.getUserName(), itemName);
|
||||
}
|
||||
|
||||
ReceiptDto receipt = user.purchase(item);
|
||||
MoneyTransaction transaction = account.withdraw(receipt.getPrice());
|
||||
if (transaction == null) {
|
||||
return new InsufficientFunds(user.getUserName(), account.getAmount(), itemName);
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, the client send requests the application services to get the presentation view.
|
||||
|
||||
```java
|
||||
// DB seeding
|
||||
LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, "
|
||||
+ "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}");
|
||||
Db.getInstance().seedUser("ignite1771", 1000.0);
|
||||
Db.getInstance().seedItem("computer", 800.0);
|
||||
Db.getInstance().seedItem("car", 20000.0);
|
||||
|
||||
var applicationServices = new ApplicationServicesImpl();
|
||||
ReceiptViewModel receipt;
|
||||
|
||||
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||
receipt.show();
|
||||
MaintenanceLock.getInstance().setLock(false);
|
||||
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "car");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "car");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "computer");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer");
|
||||
receipt.show();
|
||||
```
|
||||
|
||||
Program output of every request:
|
||||
|
||||
```
|
||||
Down for maintenance
|
||||
Invalid user: abc123
|
||||
Out of stock: tv for user = ignite1771 to buy
|
||||
Insufficient funds: 1000.0 of user: ignite1771 for buying item: car
|
||||
Receipt: 800.0 paid
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Special Case pattern when
|
||||
|
||||
* You have multiple places in the system that have the same behavior after a conditional check
|
||||
for a particular class instance, or the same behavior after a null check.
|
||||
* Return a real object that performs the real behavior, instead of a null object that performs nothing.
|
||||
|
||||
## Tutorial
|
||||
|
||||
* [Special Case Tutorial](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case)
|
||||
|
||||
## Credits
|
||||
|
||||
* [How to Reduce Cyclomatic Complexity Part 2: Special Case Pattern](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case)
|
||||
* [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html)
|
||||
* [Special Case](https://www.martinfowler.com/eaaCatalog/specialCase.html)
|
@@ -1,119 +0,0 @@
|
||||
@startuml
|
||||
left to right direction
|
||||
package com.iluwatar.specialcase {
|
||||
class App {
|
||||
- LOGGER : Logger {static}
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
interface ApplicationServices {
|
||||
+ loggedInUserPurchase(String, String) : ReceiptViewModel {abstract}
|
||||
}
|
||||
class ApplicationServicesImpl {
|
||||
- domain : DomainServicesImpl
|
||||
+ ApplicationServicesImpl()
|
||||
- isDownForMaintenance() : boolean
|
||||
+ loggedInUserPurchase(userName : String, itemName : String) : ReceiptViewModel
|
||||
}
|
||||
class Db {
|
||||
- instance : Db {static}
|
||||
- itemName2Product : Map<String, Product>
|
||||
- user2Account : Map<User, Account>
|
||||
- userName2User : Map<String, User>
|
||||
+ Db()
|
||||
+ findAccountByUser(user : User) : Account
|
||||
+ findProductByItemName(itemName : String) : Product
|
||||
+ findUserByUserName(userName : String) : User
|
||||
+ getInstance() : Db {static}
|
||||
+ seedItem(itemName : String, price : Double)
|
||||
+ seedUser(userName : String, amount : Double)
|
||||
}
|
||||
class Account {
|
||||
- amount : Double
|
||||
+ Account(this$0 : Double)
|
||||
+ getAmount() : Double
|
||||
+ withdraw(price : Double) : MoneyTransaction
|
||||
}
|
||||
class Product {
|
||||
- price : Double
|
||||
+ Product(this$0 : Double)
|
||||
+ getPrice() : Double
|
||||
}
|
||||
class User {
|
||||
- userName : String
|
||||
+ User(this$0 : String)
|
||||
+ getUserName() : String
|
||||
+ purchase(item : Product) : ReceiptDto
|
||||
}
|
||||
interface DomainServices {
|
||||
}
|
||||
class DomainServicesImpl {
|
||||
+ DomainServicesImpl()
|
||||
- purchase(user : User, account : Account, itemName : String) : ReceiptViewModel
|
||||
+ purchase(userName : String, itemName : String) : ReceiptViewModel
|
||||
}
|
||||
class DownForMaintenance {
|
||||
- LOGGER : Logger {static}
|
||||
+ DownForMaintenance()
|
||||
+ show()
|
||||
}
|
||||
class InsufficientFunds {
|
||||
- LOGGER : Logger {static}
|
||||
- amount : Double
|
||||
- itemName : String
|
||||
- userName : String
|
||||
+ InsufficientFunds(userName : String, amount : Double, itemName : String)
|
||||
+ show()
|
||||
}
|
||||
class InvalidUser {
|
||||
- LOGGER : Logger {static}
|
||||
- userName : String
|
||||
+ InvalidUser(userName : String)
|
||||
+ show()
|
||||
}
|
||||
class MaintenanceLock {
|
||||
- LOGGER : Logger {static}
|
||||
- instance : MaintenanceLock {static}
|
||||
- lock : boolean
|
||||
+ MaintenanceLock()
|
||||
+ getInstance() : MaintenanceLock {static}
|
||||
+ isLock() : boolean
|
||||
+ setLock(lock : boolean)
|
||||
}
|
||||
class MoneyTransaction {
|
||||
- amount : Double
|
||||
- price : Double
|
||||
+ MoneyTransaction(amount : Double, price : Double)
|
||||
}
|
||||
class OutOfStock {
|
||||
- LOGGER : Logger {static}
|
||||
- itemName : String
|
||||
- userName : String
|
||||
+ OutOfStock(userName : String, itemName : String)
|
||||
+ show()
|
||||
}
|
||||
class ReceiptDto {
|
||||
- LOGGER : Logger {static}
|
||||
- price : Double
|
||||
+ ReceiptDto(price : Double)
|
||||
+ getPrice() : Double
|
||||
+ show()
|
||||
}
|
||||
interface ReceiptViewModel {
|
||||
+ show() {abstract}
|
||||
}
|
||||
}
|
||||
User --+ Db
|
||||
Product --+ Db
|
||||
MaintenanceLock --> "-instance" MaintenanceLock
|
||||
Db --> "-instance" Db
|
||||
ApplicationServicesImpl --> "-domain" DomainServicesImpl
|
||||
Account --+ Db
|
||||
ApplicationServicesImpl ..|> ApplicationServices
|
||||
DomainServicesImpl ..|> DomainServices
|
||||
DownForMaintenance ..|> ReceiptViewModel
|
||||
InsufficientFunds ..|> ReceiptViewModel
|
||||
InvalidUser ..|> ReceiptViewModel
|
||||
OutOfStock ..|> ReceiptViewModel
|
||||
ReceiptDto ..|> ReceiptViewModel
|
||||
@enduml
|
Binary file not shown.
Before Width: | Height: | Size: 448 KiB |
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>special-case</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
@@ -1,47 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>The Special Case Pattern is a software design pattern that encapsulates particular cases
|
||||
* into subclasses that provide special behaviors.</p>
|
||||
*
|
||||
* <p>In this example ({@link ReceiptViewModel}) encapsulates all particular cases.</p>
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// DB seeding
|
||||
LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, "
|
||||
+ "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}");
|
||||
Db.getInstance().seedUser("ignite1771", 1000.0);
|
||||
Db.getInstance().seedItem("computer", 800.0);
|
||||
Db.getInstance().seedItem("car", 20000.0);
|
||||
|
||||
final var applicationServices = new ApplicationServicesImpl();
|
||||
ReceiptViewModel receipt;
|
||||
|
||||
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||
receipt.show();
|
||||
MaintenanceLock.getInstance().setLock(false);
|
||||
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "car");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "car");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "computer");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer");
|
||||
receipt.show();
|
||||
}
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public interface ApplicationServices {
|
||||
|
||||
ReceiptViewModel loggedInUserPurchase(String userName, String itemName);
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public class ApplicationServicesImpl implements ApplicationServices {
|
||||
|
||||
private DomainServicesImpl domain = new DomainServicesImpl();
|
||||
|
||||
@Override
|
||||
public ReceiptViewModel loggedInUserPurchase(String userName, String itemName) {
|
||||
if (isDownForMaintenance()) {
|
||||
return new DownForMaintenance();
|
||||
}
|
||||
return this.domain.purchase(userName, itemName);
|
||||
}
|
||||
|
||||
private boolean isDownForMaintenance() {
|
||||
return MaintenanceLock.getInstance().isLock();
|
||||
}
|
||||
}
|
@@ -1,150 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Db {
|
||||
|
||||
private static Db instance;
|
||||
private Map<String, User> userName2User;
|
||||
private Map<User, Account> user2Account;
|
||||
private Map<String, Product> itemName2Product;
|
||||
|
||||
/**
|
||||
* Get the instance of Db.
|
||||
*
|
||||
* @return singleton instance of Db class
|
||||
*/
|
||||
public static Db getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Db.class) {
|
||||
if (instance == null) {
|
||||
instance = new Db();
|
||||
instance.userName2User = new HashMap<>();
|
||||
instance.user2Account = new HashMap<>();
|
||||
instance.itemName2Product = new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed a user into Db.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @param amount of the user's account
|
||||
*/
|
||||
public void seedUser(String userName, Double amount) {
|
||||
User user = new User(userName);
|
||||
instance.userName2User.put(userName, user);
|
||||
Account account = new Account(amount);
|
||||
instance.user2Account.put(user, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed an item into Db.
|
||||
*
|
||||
* @param itemName of the item
|
||||
* @param price of the item
|
||||
*/
|
||||
public void seedItem(String itemName, Double price) {
|
||||
Product item = new Product(price);
|
||||
itemName2Product.put(itemName, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user with the userName.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @return instance of User
|
||||
*/
|
||||
public User findUserByUserName(String userName) {
|
||||
if (!userName2User.containsKey(userName)) {
|
||||
return null;
|
||||
}
|
||||
return userName2User.get(userName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an account of the user.
|
||||
*
|
||||
* @param user in Db
|
||||
* @return instance of Account of the user
|
||||
*/
|
||||
public Account findAccountByUser(User user) {
|
||||
if (!user2Account.containsKey(user)) {
|
||||
return null;
|
||||
}
|
||||
return user2Account.get(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a product with the itemName.
|
||||
*
|
||||
* @param itemName of the item
|
||||
* @return instance of Product
|
||||
*/
|
||||
public Product findProductByItemName(String itemName) {
|
||||
if (!itemName2Product.containsKey(itemName)) {
|
||||
return null;
|
||||
}
|
||||
return itemName2Product.get(itemName);
|
||||
}
|
||||
|
||||
public class User {
|
||||
|
||||
private String userName;
|
||||
|
||||
public User(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public ReceiptDto purchase(Product item) {
|
||||
return new ReceiptDto(item.getPrice());
|
||||
}
|
||||
}
|
||||
|
||||
public class Account {
|
||||
|
||||
private Double amount;
|
||||
|
||||
public Account(Double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraw the price of the item from the account.
|
||||
*
|
||||
* @param price of the item
|
||||
* @return instance of MoneyTransaction
|
||||
*/
|
||||
public MoneyTransaction withdraw(Double price) {
|
||||
if (price > amount) {
|
||||
return null;
|
||||
}
|
||||
return new MoneyTransaction(amount, price);
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
||||
public class Product {
|
||||
|
||||
private Double price;
|
||||
|
||||
public Product(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public interface DomainServices {
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public class DomainServicesImpl implements DomainServices {
|
||||
|
||||
/**
|
||||
* Domain purchase with userName and itemName, with validation for userName.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @param itemName of the item
|
||||
* @return instance of ReceiptViewModel
|
||||
*/
|
||||
public ReceiptViewModel purchase(String userName, String itemName) {
|
||||
Db.User user = Db.getInstance().findUserByUserName(userName);
|
||||
if (user == null) {
|
||||
return new InvalidUser(userName);
|
||||
}
|
||||
|
||||
Db.Account account = Db.getInstance().findAccountByUser(user);
|
||||
return purchase(user, account, itemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain purchase with user, account and itemName,
|
||||
* with validation for whether product is out of stock
|
||||
* and whether user has insufficient funds in the account.
|
||||
*
|
||||
* @param user in Db
|
||||
* @param account in Db
|
||||
* @param itemName of the item
|
||||
* @return instance of ReceiptViewModel
|
||||
*/
|
||||
private ReceiptViewModel purchase(Db.User user, Db.Account account, String itemName) {
|
||||
Db.Product item = Db.getInstance().findProductByItemName(itemName);
|
||||
if (item == null) {
|
||||
return new OutOfStock(user.getUserName(), itemName);
|
||||
}
|
||||
|
||||
ReceiptDto receipt = user.purchase(item);
|
||||
MoneyTransaction transaction = account.withdraw(receipt.getPrice());
|
||||
if (transaction == null) {
|
||||
return new InsufficientFunds(user.getUserName(), account.getAmount(), itemName);
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DownForMaintenance implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class);
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Down for maintenance");
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InsufficientFunds implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class);
|
||||
|
||||
private String userName;
|
||||
private Double amount;
|
||||
private String itemName;
|
||||
|
||||
/**
|
||||
* Constructor of InsufficientFunds.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @param amount of the user's account
|
||||
* @param itemName of the item
|
||||
*/
|
||||
public InsufficientFunds(String userName, Double amount, String itemName) {
|
||||
this.userName = userName;
|
||||
this.amount = amount;
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Insufficient funds: " + amount + " of user: " + userName
|
||||
+ " for buying item: " + itemName);
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InvalidUser implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class);
|
||||
|
||||
private final String userName;
|
||||
|
||||
public InvalidUser(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Invalid user: " + userName);
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MaintenanceLock {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class);
|
||||
|
||||
private static MaintenanceLock instance;
|
||||
private boolean lock = true;
|
||||
|
||||
/**
|
||||
* Get the instance of MaintenanceLock.
|
||||
*
|
||||
* @return singleton instance of MaintenanceLock
|
||||
*/
|
||||
public static MaintenanceLock getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (MaintenanceLock.class) {
|
||||
if (instance == null) {
|
||||
instance = new MaintenanceLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean isLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
public void setLock(boolean lock) {
|
||||
this.lock = lock;
|
||||
LOGGER.info("Maintenance lock is set to: " + lock);
|
||||
}
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public class MoneyTransaction {
|
||||
|
||||
private Double amount;
|
||||
private Double price;
|
||||
|
||||
public MoneyTransaction(Double amount, Double price) {
|
||||
this.amount = amount;
|
||||
this.price = price;
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class OutOfStock implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class);
|
||||
|
||||
private String userName;
|
||||
private String itemName;
|
||||
|
||||
public OutOfStock(String userName, String itemName) {
|
||||
this.userName = userName;
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy");
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ReceiptDto implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class);
|
||||
|
||||
private Double price;
|
||||
|
||||
public ReceiptDto(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Receipt: " + price + " paid");
|
||||
}
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public interface ReceiptViewModel {
|
||||
|
||||
void show();
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Application test.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.read.ListAppender;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/**
|
||||
* Special cases unit tests. (including the successful scenario {@link ReceiptDto})
|
||||
*/
|
||||
public class SpecialCasesTest {
|
||||
private static ApplicationServices applicationServices;
|
||||
private static ReceiptViewModel receipt;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
Db.getInstance().seedUser("ignite1771", 1000.0);
|
||||
Db.getInstance().seedItem("computer", 800.0);
|
||||
Db.getInstance().seedItem("car", 20000.0);
|
||||
|
||||
applicationServices = new ApplicationServicesImpl();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
MaintenanceLock.getInstance().setLock(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownForMaintenance() {
|
||||
final Logger LOGGER = (Logger) LoggerFactory.getLogger(DownForMaintenance.class);
|
||||
|
||||
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||
listAppender.start();
|
||||
LOGGER.addAppender(listAppender);
|
||||
|
||||
MaintenanceLock.getInstance().setLock(true);
|
||||
receipt = applicationServices.loggedInUserPurchase(null, null);
|
||||
receipt.show();
|
||||
|
||||
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||
assertEquals("Down for maintenance", loggingEventList.get(0).getMessage());
|
||||
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidUser() {
|
||||
final Logger LOGGER = (Logger) LoggerFactory.getLogger(InvalidUser.class);
|
||||
|
||||
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||
listAppender.start();
|
||||
LOGGER.addAppender(listAppender);
|
||||
|
||||
receipt = applicationServices.loggedInUserPurchase("a", null);
|
||||
receipt.show();
|
||||
|
||||
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||
assertEquals("Invalid user: a", loggingEventList.get(0).getMessage());
|
||||
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOutOfStock() {
|
||||
final Logger LOGGER = (Logger) LoggerFactory.getLogger(OutOfStock.class);
|
||||
|
||||
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||
listAppender.start();
|
||||
LOGGER.addAppender(listAppender);
|
||||
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv");
|
||||
receipt.show();
|
||||
|
||||
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||
assertEquals("Out of stock: tv for user = ignite1771 to buy"
|
||||
, loggingEventList.get(0).getMessage());
|
||||
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsufficientFunds() {
|
||||
final Logger LOGGER = (Logger) LoggerFactory.getLogger(InsufficientFunds.class);
|
||||
|
||||
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||
listAppender.start();
|
||||
LOGGER.addAppender(listAppender);
|
||||
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "car");
|
||||
receipt.show();
|
||||
|
||||
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||
assertEquals("Insufficient funds: 1000.0 of user: ignite1771 for buying item: car"
|
||||
, loggingEventList.get(0).getMessage());
|
||||
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiptDto() {
|
||||
final Logger LOGGER = (Logger) LoggerFactory.getLogger(ReceiptDto.class);
|
||||
|
||||
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||
listAppender.start();
|
||||
LOGGER.addAppender(listAppender);
|
||||
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer");
|
||||
receipt.show();
|
||||
|
||||
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||
assertEquals("Receipt: 800.0 paid"
|
||||
, loggingEventList.get(0).getMessage());
|
||||
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||
}
|
||||
}
|
42
tr/README.md
42
tr/README.md
@@ -1,42 +0,0 @@
|
||||
<!-- Biçimlendirme nedeniyle bu satır boş bırakılmalıdır böylelikle hoş bir görüntüye sahip olabiliriz. örneğin web sitesi -->
|
||||
|
||||

|
||||
[](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
# Giriş
|
||||
|
||||
Tasarım desenleri, bir yazılım mühendisi bir uygulamayı veya sistemi tasarlarken yaygın sorunları çözmek için kullanabileceği en iyi çözüm prensipleridir.
|
||||
|
||||
Tasarım desenleri, test edilip kanıtlanmış prensipler olduğu için yazılım geliştirme sürecini hızlandırabilir.
|
||||
|
||||
Tasarım desenlerini yeniden kullanmak, büyük sorunlara neden olan ince sorunları önlemeye yardımcı olur ve aynı zamanda tasarım desenlerine alışkın olan yazılım mühendisileri ve yazılım mimarları için kod okunabilirliğini artırır.
|
||||
|
||||
# Başlangıç
|
||||
|
||||
Bu site Java Tasarım Desenlerini sergiliyor. Çözümler, açık kaynak topluluğundan deneyimli yazılım mühendisileri ve yazılım mimarları tarafından geliştirilmiştir. Bu yazılım desenleri, detaylı açıklamalarıyla veya kaynak kodlarına bakılarak göz atılabilir. Kaynak kod örneklerinde iyi derecede açıklayıcı yorum satırlarına sahip olup ve belirli bir tasarım deseninin nasıl uygulanacağına dair programlama dersi olarak düşünülebilir. Savaşta en çok kanıtlanmış açık kaynak Java teknolojilerini kullanıyoruz.
|
||||
|
||||
Bu projeye başlamadan önce çeşitli [Yazılım Tasarım İlkelerine](https://java-design-patterns.com/principles/) aşina olmalısınız.
|
||||
|
||||
Tüm tasarımlar olabildiğince basit olmalıdır. [KISS](https://en.wikipedia.org/wiki/KISS_principle), [YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it) ve [Do The Simplest Thing That Could Possibly Work](https://learning.oreilly.com/library/view/extreme-programming-pocket/9781449399849/ch17.html) ilkeleri ile başlamalısınız.
|
||||
|
||||
Bu kavramlara aşina olduktan sonra, aşağıdaki yaklaşımlardan herhangi birini kullanarak [mevcut tasarım desenlerini](https://java-design-patterns.com/patterns/) derinlemesine incelemeye başlayabilirsiniz.
|
||||
|
||||
- İsme göre belirli bir tasarım deseni arayın. Bulamadınız mı? Lütfen yeni bir tasarım deseni için [bizim ile](https://github.com/iluwatar/java-design-patterns/issues) iletişime geçin.
|
||||
- Performance, Gang of Four ya da Data access gibi etiketleri kullanın.
|
||||
- Creational(Yaratıcı Tasarım Desenleri), Behavioral(Davranışsal Tasarım Desenleri), ve Structural(Yapısal Tasarım Desenleri) gibi tasarım kategorilerini kullanın.
|
||||
|
||||
Umarım bu sitede sunulan nesne yönelimli çözümleri yazılım mimarileriniz için yararlı bulursunuz ve onları geliştirirken bizim kadar eğlenirsiniz.
|
||||
|
||||
# Nasıl Katkı Sağlayabilirim
|
||||
|
||||
Projeye katkıda bulunmaya istekliysen, ilgili bilgileri [geliştirici wiki'mizde](https://github.com/iluwatar/java-design-patterns/wiki) bulabilirsin. [Gitter](https://gitter.im/iluwatar/java-design-patterns) ile size yardımcı olacağız ve sorularınızı cevaplayacağız.
|
||||
|
||||
# Lisans
|
||||
|
||||
Bu proje, MIT lisansı koşulları altında lisanslanmıştır.
|
Reference in New Issue
Block a user