Skip to content

Commit 40f2fdc

Browse files
committed
ATM LLD
1 parent 762360b commit 40f2fdc

File tree

14 files changed

+489
-0
lines changed

14 files changed

+489
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ The sixth project in this repository is a Meeting Scheduler, a system that simul
110110
- Room calendar management, tracking when a room is booked or available.
111111
- Concurrency control, preventing scheduling conflicts in real time.
112112

113+
## ATM
114+
115+
The seventh project in this repository is a ATM machine, a system that simulates ATM. It demonstrates the following concepts:
116+
117+
- State design pattern for different state of ATM.
118+
- Chain of responsibility principle for withdrawing cash.
119+
- Template pattern for handling error of each place in one place.
120+
- Concurrency control, preventing atm to conflict.
121+
113122
### Features
114123

115124
- Book and cancel meetings seamlessly.

atm-machine/atm/Account.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package atm
2+
3+
import "errors"
4+
5+
type Account interface {
6+
GetBalance() float64
7+
}
8+
9+
type AccountType string
10+
11+
const (
12+
Saving AccountType = "SAVING"
13+
Current AccountType = "CURRENT"
14+
)
15+
16+
func AccountFactory(accType AccountType) (Account, error) {
17+
switch accType {
18+
case Current:
19+
return &CurrentAccount{}, nil
20+
case Saving:
21+
return &SavingAccount{}, nil
22+
default:
23+
return nil, errors.New("Not a valid account type")
24+
}
25+
}
26+
27+
type SavingAccount struct {
28+
acctNo string
29+
balance float64
30+
}
31+
32+
func (s *SavingAccount) GetBalance() float64 {
33+
return s.balance
34+
}
35+
36+
type CurrentAccount struct {
37+
acctNo string
38+
balance float64
39+
}
40+
41+
func (c *CurrentAccount) GetBalance() float64 {
42+
return c.balance
43+
}

atm-machine/atm/atm.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package atm
2+
3+
import (
4+
"atm-machine/model"
5+
"fmt"
6+
"sync"
7+
)
8+
9+
type WithdrawNote struct {
10+
FiveHundread int
11+
Hundread int
12+
Left int
13+
}
14+
15+
type ATM struct {
16+
countOFNotes map[string]int
17+
card model.Card
18+
account Account
19+
uiOption []string
20+
WithdrawAs *WithdrawNote
21+
insertCard ATMState
22+
readCard ATMState
23+
selectAccount ATMState
24+
dispenserAmount ATMState
25+
currentState ATMState
26+
mu sync.RWMutex
27+
}
28+
29+
func (a *ATM) SetState(s ATMState) {
30+
a.currentState = s
31+
}
32+
33+
func (a *ATM) ResetAtm() {
34+
a.mu.Lock()
35+
defer a.mu.Unlock()
36+
a.WithdrawAs = &WithdrawNote{}
37+
a.card = model.Card{}
38+
a.account = nil
39+
}
40+
41+
func (a *ATM) PrintMoney() {
42+
a.mu.Lock()
43+
defer a.mu.Unlock()
44+
fmt.Printf("\n500 note present:%d, 100 not present:%d", a.countOFNotes["500"], a.countOFNotes["100"])
45+
}
46+
47+
func (a *ATM) StateName() string {
48+
a.mu.Lock()
49+
defer a.mu.Unlock()
50+
return a.currentState.StateName()
51+
}
52+
53+
func (a *ATM) InsertCard() error {
54+
a.mu.Lock()
55+
defer a.mu.Unlock()
56+
return a.currentState.InsertCard()
57+
}
58+
59+
func (a *ATM) GetCardDetail() error {
60+
a.mu.Lock()
61+
defer a.mu.Unlock()
62+
return a.currentState.GetCardDetail()
63+
}
64+
65+
func (a *ATM) DispenserAmount() error {
66+
a.mu.Lock()
67+
defer a.mu.Unlock()
68+
return a.currentState.DispenserAmount()
69+
}
70+
71+
func (a *ATM) SelectAccount() error {
72+
a.mu.Lock()
73+
defer a.mu.Unlock()
74+
return a.currentState.SelectAccount()
75+
}
76+
77+
func (a *ATM) Execute(operation func() error) {
78+
err := operation()
79+
if err != nil {
80+
a.ResetAtm()
81+
a.SetState(a.insertCard)
82+
fmt.Println("All operation will be nil operation:")
83+
fmt.Println("Error while operation:", err.Error())
84+
}
85+
}
86+
87+
func NewATM() *ATM {
88+
atm := &ATM{
89+
countOFNotes: map[string]int{
90+
"500": 1000,
91+
"200": 2000,
92+
"100": 1000,
93+
},
94+
WithdrawAs: &WithdrawNote{},
95+
}
96+
97+
atm.insertCard = &InsertCard{
98+
atm: atm,
99+
}
100+
101+
atm.readCard = &ReadCard{
102+
atm: atm,
103+
}
104+
105+
atm.selectAccount = &SelectAccount{
106+
atm: atm,
107+
}
108+
109+
atm.readCard = &ReadCard{
110+
atm: atm,
111+
}
112+
113+
atm.dispenserAmount = &DispenserAmount{
114+
atm: atm,
115+
}
116+
117+
atm.SetState(atm.insertCard)
118+
119+
return atm
120+
}

atm-machine/atm/atm_state.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package atm
2+
3+
type ATMState interface {
4+
StateName() string
5+
InsertCard() error
6+
InsertPin() error
7+
SelectAccount() error
8+
GetCardDetail() error
9+
DispenserAmount() error
10+
}
11+
12+
type ATMAbstract struct{}
13+
14+
func (s *ATMAbstract) InsertCard() error {
15+
return nil
16+
}
17+
18+
func (s *ATMAbstract) InsertPin() error {
19+
return nil
20+
}
21+
22+
func (s *ATMAbstract) AuthticateCard() error {
23+
return nil
24+
}
25+
26+
func (s *ATMAbstract) DispenserAmount() error {
27+
return nil
28+
}
29+
30+
func (s *ATMAbstract) SelectAccount() error {
31+
return nil
32+
33+
}
34+
35+
func (s *ATMAbstract) GetCardDetail() error {
36+
return nil
37+
38+
}
39+
40+
func (s *ATMAbstract) StateName() string {
41+
return "ATMAbstract"
42+
}

atm-machine/atm/card_reader.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package atm
2+
3+
import "atm-machine/model"
4+
5+
// This will be actuall implementation of card reader // i am returning a card from here
6+
type CardReader struct {
7+
}
8+
9+
func NewCardReader() CardReader {
10+
return CardReader{}
11+
}
12+
13+
// for now it will return some dummy card depend of input for acting as multiple car
14+
func (c CardReader) ReadCard(cardType string) model.Card {
15+
switch cardType {
16+
case "invalid":
17+
return model.Card{
18+
Status: model.NoCard,
19+
}
20+
default:
21+
return model.Card{
22+
BankName: "hdfc",
23+
CardNo: "34567-sd55498459",
24+
AccountNo: "bshy4859-sdhhj66",
25+
Status: model.Active,
26+
UserName: "Himanshu sharma"}
27+
28+
}
29+
30+
}

atm-machine/atm/insert_card.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package atm
2+
3+
type InsertCard struct {
4+
atm *ATM
5+
ATMAbstract
6+
}
7+
8+
func (d *InsertCard) InsertCard() error {
9+
d.atm.SetState(d.atm.readCard)
10+
return nil
11+
}
12+
13+
func (d *InsertCard) StateName() string {
14+
return "InsertCard"
15+
}

atm-machine/atm/insert_pin.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package atm
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
type InsetPin struct {
9+
atm *ATM
10+
ATMAbstract
11+
}
12+
13+
func (s *InsetPin) InsertPin() error {
14+
// logic to valid pin, we can call api client inside it, i will return sucess and error depend on input
15+
fmt.Println("Enter 4 letter pin for card:\n")
16+
17+
var pin string
18+
19+
fmt.Scanf("%s", &pin)
20+
21+
if pin == "1111" {
22+
return nil
23+
} else {
24+
return errors.New("Pin is not valid")
25+
}
26+
}
27+
28+
func (d *InsetPin) StateName() string {
29+
return "InsetPin"
30+
}

atm-machine/atm/read_card.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package atm
2+
3+
type ReadCard struct {
4+
atm *ATM
5+
ATMAbstract
6+
}
7+
8+
func (d *ReadCard) GetCardDetail() error {
9+
card := NewCardReader().ReadCard("valid")
10+
if card.CardNo == "" {
11+
d.atm.SetState(d.atm.insertCard)
12+
}
13+
d.atm.card = card
14+
d.atm.SetState(d.atm.selectAccount)
15+
return nil
16+
}
17+
18+
func (d *ReadCard) StateName() string {
19+
return "ReadCard"
20+
}

atm-machine/atm/select_account.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package atm
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
type SelectAccount struct {
8+
atm *ATM
9+
ATMAbstract
10+
}
11+
12+
func (s *SelectAccount) SelectAccount() error {
13+
14+
var accountType string
15+
var err error
16+
17+
fmt.Println("Select account type SAVING OR CURRENT:\n")
18+
fmt.Scanf("%s", &accountType)
19+
20+
s.atm.account, err = AccountFactory(AccountType(accountType))
21+
22+
s.atm.SetState(s.atm.dispenserAmount)
23+
24+
return err
25+
26+
}
27+
28+
func (s *SelectAccount) StateName() string {
29+
return "SelectAccount"
30+
}

atm-machine/atm/withdraw.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package atm
2+
3+
type Withdraw interface {
4+
ProcessAmount(atm *ATM, atmamount float64)
5+
}
6+
7+
func NewWithDrawPipeline() Withdraw {
8+
return fiveHundreadWithdraw{
9+
next: hundreadHundreadWithdraw{},
10+
}
11+
}
12+
13+
type fiveHundreadWithdraw struct {
14+
next Withdraw
15+
}
16+
17+
func (f fiveHundreadWithdraw) ProcessAmount(atm *ATM, atmamount float64) {
18+
div := atmamount / 500
19+
rem := int(atmamount) % 500
20+
atm.WithdrawAs.FiveHundread = int(div)
21+
f.next.ProcessAmount(atm, float64(rem))
22+
}
23+
24+
type hundreadHundreadWithdraw struct {
25+
next Withdraw
26+
}
27+
28+
func (h hundreadHundreadWithdraw) ProcessAmount(atm *ATM, atmamount float64) {
29+
div := atmamount / 100
30+
rem := int(atmamount) % 100
31+
atm.WithdrawAs.Hundread = int(div)
32+
atm.WithdrawAs.Left = rem
33+
34+
}

0 commit comments

Comments
 (0)