2.0 Account + NewAccoun
지난번에 했던 실습 코드는 일단 주석처리를 하던지 지우고, 새로운 프로젝트 시작.
accounts라는 폴더를 만들고 accounts.go 라는 메인 파일을 만든다. 여기서는 컴파일을 진행하지 않을 것이기 때문에 이름이 main.go 가 아니더라도 상관 없다. 여기서 account라는 struct를 선언하고 다시 main.go에서 그 struct를 생성할 것이다.
가장 간단하고 직관적으로 생각한 방법은 accounts.go 안의 account struct와 그 안의 owner, balance 같은 속성을 모두 대문자로 시작하도록 작성해서 public으로 선언하는 것이다. 하지만 이렇게 하면 누구나 접근할 수 있어 보안적인 측면에서 좋지 않기때문에 constructor 함수를 만들어 사용하도록 한다. 이때 만드는 constructor 함수는 당연히 외부에서 접근이 가능해야 하므로 대문자로 선언한다.
// /accounts/accounts.go
package accounts //컴파일 안할거라서 이게 메인
// Account struct
type Account struct {
owner string
balance int
} // 대문자로 작성해야 다른 곳에서 접근 가능.
// NewAccount creates Account
func NewAccount(owner string) *Account {
account := Account{owner: owner, balance: 0}
return &account // 복사본을 리턴하는 것이 아니라 실제 우리가 만든 memory address 리턴
}
//-------------------------------------------------------------------------------------------
// main.go
package main
import (
"fmt"
"github.com/Yoon-Suji/learngo/accounts"
)
func main() {
account := accounts.NewAccount("suji")
fmt.Println(account)
}
2.1 Methods part One
methods를 선언하는 방법은 func와 func 이름 사이에 receiver를 추가하는 것이다.
account의 balance를 증가시키는 Deposit 메소드를 다음과 같이 선언해보자.
// Deposit x amount on your account
func (a Account /*receiver*/) Deposit(amount int) {
a.balance += amount
}
하지만 main 에서 해당 함수를 실행시키고 다시 account를 출력해보면 balance가 변하지 않은 것을 확인할 수 있다. 이는 해당 메소드가 account의 복사본을 만들고 그 복사본을 증가시키기 때문이다.
2.2 Methods part Two
그래서 상태를 변경하고 싶을 때는 다음과 같이 pointer receiver를 사용해야 한다.
// Deposit x amount on your account
func (a *Account /*receiver*/) Deposit(amount int) {
a.balance += amount
}
error handling
go 에는 exception이나 try-catch 가 없다. 직접 error를 리턴하고 핸들링해주어야 한다.
balance에서 돈을 빼는 Withdraw method를 선언해보자. 만약 내 balance가 출금하고자 하는 돈보다 적은 경우에는 출금을 하지 못하게 error를 발생시켜야 할 것이다.
var errNoMoney = errors.New("Can't withdraw you are ppoor")
// Withdraw x amount from your account
func (a *Account) Withdraw(amount int) error {
if a.balance < amount {
return errNoMoney
}
a.balance -= amount
return nil
}
위와 같이 error를 리턴타입에 적고, err가 발생하면 return error를 하고 발생하지 않는 경우에는 return nil을 한다. 여기서 nil은 null이나 None 같은 의미라고 생각하면 된다. error variable을 만들어서 사용하는 편이 더 가독성이 좋다고 한다.
그러면 이제 실행하는 쪽에서 error handling을 해야 한다.
func main() {
account := accounts.NewAccount("suji")
account.Deposit(10)
err := account.Withdraw(20)
if err != nil {
log.Fatalln(err) // program 종료 해줌
}
fmt.Println(account.Balance())
}
2.3 Finishing Up
struct를 출력할 때 Go 에서 내부적으로 호출하는 String 메소드가 있는데 이를 사용해서 원하는 형식으로 출력되도록 할 수 있다.
// Go가 내부적으로 호출하는 메소드를 사용
func (a Account) String() string {
return fmt.Sprint(a.Owner(), "'s account. \nBalance: ", a.Balance())
}
fmt.Println(accuont)
최종 코드
아니 뭐.. 계속 main.go 다 지우고.. 코드를 안 남겨놓네.. 여기에라도 남겨야지..
/accounts/accounts.go
package accounts //컴파일 안할거라서 이게 메인
import (
"errors" // Account struct
"fmt"
)
type Account struct {
owner string
balance int
} // 대문자로 작성해야 다른 곳에서 접근 가능.
// NewAccount creates Account
func NewAccount(owner string) *Account {
account := Account{owner: owner, balance: 0}
return &account // 복사본을 리턴하는 것이 아니라 실제 우리가 만든 memory address 리턴
}
// Deposit x amount on your account
func (a *Account /*receiver*/) Deposit(amount int) {
a.balance += amount
}
var errNoMoney = errors.New("Can't withdraw you are ppoor")
// Withdraw x amount from your account
func (a *Account) Withdraw(amount int) error {
if a.balance < amount {
return errNoMoney
}
a.balance -= amount
return nil
}
// ChangeOwner of the account
func (a *Account) ChangeOwner(newOwner string) {
a.owner = newOwner
}
// Balance of your account
func (a Account) Balance() int {
return a.balance
}
// Owner of your account
func (a Account) Owner() string {
return a.owner
}
// Go가 내부적으로 호출하는 메소드를 사용
func (a Account) String() string {
return fmt.Sprint(a.Owner(), "'s account. \nBalance: ", a.Balance())
}
main.go
package main
import (
"fmt"
"github.com/Yoon-Suji/learngo/accounts"
)
func main() {
account := accounts.NewAccount("suji")
account.Deposit(10)
err := account.Withdraw(20)
if err != nil {
//log.Fatalln(err) // program 종료 해줌
fmt.Println(err)
}
fmt.Println(account)
}
2.4 Dictionary part One
이번에는 struct가 아닌 map type을 사용해 볼 것이다.
Go에서는 type에 alias를 붙여서 다음과 같이 사용할 수 있다.
type Money int
Money(1)
우리는 map[string]string 타입의 Dictionary라는 타입을 선언해서 사용할 것이다.
또한 type은 메소드를 가질 수 있기 때문에 타입 안에서 메소드로 Search 를 구현해서 사용해보자.
// /mydict/mydict.go
// Dictionary type
type Dictionary map[string]string //Dictionary == alias
// types can get method
var errNotFound = errors.New("Not Found")
// Search for a word
func (d Dictionary) Search(word string) (string, error) {
value, exits := d[word]
if exits {
return value, nil
}
return "", errNotFound
}
// -----------------------------------------------------------
// main.go
func main() {
dictionary := mydict.Dictionary{"first": "First word"}
definition, err := dictionary.Search("first")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(definition)
}
}
2.5 Add Method
다음은 Add Method를 구현해보자.
var errWordExists = errors.New("That word already exists")
// Add a word to the dictionary
func (d Dictionary) Add(word, def string) error {
_, err := d.Search(word)
// if err == errNotFound {
// d[word] = def
// } else if err == nil {
// return errWordExists
// }
// return nil
switch err {
case errNotFound:
d[word] = def
case nil:
return errWordExists
}
return nil
}
2.6 Update Delete
마지막으로 Update와 Delete 메소드도 만들어보자.
var (
errNotFound = errors.New("Not Found")
errWordExists = errors.New("That word already exists")
errCantUpdate = errors.New("Cant update non-existing word")
)
// Update
func (d Dictionary) Update(word, definition string) error {
_, err := d.Search(word)
switch err {
case nil:
d[word] = definition
case errNotFound:
return errCantUpdate
}
return nil
}
// Delete a word
func (d Dictionary) Delete(word string) {
delete(d, word)
}
'스터디📖 > Go' 카테고리의 다른 글
쉽고 빠른 Go 시작하기 - #1 THEORY (0) | 2022.03.25 |
---|---|
WSL2 에서 GO 설치하기 (0) | 2022.03.22 |