- 投稿日:2020-06-28T22:22:15+09:00
【DDD】ドメイン駆動設計ってなんじゃらほい
ドメイン駆動設計についての読書記録です。
主に下記書籍を参考にしています。
ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 | 成瀬 允宣 |本 | 通販 | Amazon
ドメイン駆動設計 モデリング/実装ガイド - little-hands - BOOTH用語の整理
まずはDDDに出てくる用語の整理をしていきます。
ドメイン
プログラムを適応する対象となる領域のことを指します。
利用者に焦点を当てる
利用者が何に課題を感じていて、何を解決したいのかを考える必要があります。
この2つを理解してソフトウェアに反映していきます。
これが大原則です。モデル
現実の事業もしくは概念を抽象化したものです。
モデリングは、このモデルを得るまでの過程を指しています。ドメインオブジェクト
上であげたモデルをソフトウェア上で動作するようにしたものです。
値オブジェクト(ValueObject)
性質
- 不変
- 交換可能
- 等価性による比較
エンティティ
データベースのエンティティとは異なる概念です。
性質
- 可変
- 同じ属性でも区別
- 同一性により区別
値オブジェクトとの違い
どこまでを値オブジェクトにして、どこまでをエンティティにするかは非常に難しいです。
その一つの解決策として、 ライフサイクルがあります。
対象が仮に「ユーザー」だった場合、アプリケーションで会員登録したり、ログインしたり、退会したりとアプリケーションの中でライフサイクルが存在する場合は、エンティティになります。
なので、可変か不変かで判断できそうです。
ただしこの判断は何を題材するかによって大きく変わるのでサービスによってしっかり考えるようにします。ドメインサービス
オブジェクトに持たせる振る舞いとして正しくない場合にドメインサービスを使用します。
メソッドを実装した場合、オブジェクトのメソッドを日本語にした場合に日本がおかしい場合は、ドメインサービスに持たせれば良い。
ただ気をつけなければいけないのは、なんでもかんでもドメインサービスにいれてはいけないということです。リポジトリ
ドメインオブジェクトの永続化や再構築を行います。
集約ごとに一つのリポジトリが存在し、リポジトリを使用する理由
ソフトウェアに柔軟性を持たせるのが一番だと思います。
さらに具体的にいうと、テストが容易に実行できるようになります。アーキテクチャ
ビジネスロジックが正しい場所にかき続けられることを助けるのがアーキテクチャです。
アーキテクチャはあくまで方針であることを忘れてはいけません。集約
必ず守りたい強い整合性を持ったオブジェクトのまとまりのことをさします。
境界
集約する場合、必ず付いて回ります。
依存関係のコントロール
具象が抽象に依存するようにしなければいけません。
そうすることで変更に強いソフトウェアを作りことはsが可能になります。依存関係逆転の法則(Dependency Inversion Principle)
抽象に依存させることが推奨されています。
より具体的に言うと、ドメイン層で定義したインターフェースに依存することをさします。
依存関係逆転の原則の重要性について - Eureka Engineering - Medium
1分でわかる依存関係逆転の原則(DIP) - Qiita責務
このクラスが何をするクラスなのかを示します。
設計
DDDから実際の設計に落とすのは下記が非常に参考になりました。
「実践ドメイン駆動設計」を読んだので、実際にDDDで設計して作ってみた! - Qiitaコンテキストモデル
要求モデル(要望の洗い出し) → プロダクトマネージャーからビジネスで実現したいことヒアリング、洗い出し
ドメインモデル
ユースケースモデリング(ユースケース図)
画面設計(画面遷移図)
画面設計(ワイヤーフレーム)
ユースケースモデリング(ユースケース記述)
ロバストネス分析
クラス図
データモデリング(ER図)
UI設計
実装に入る...
「実践ドメイン駆動設計」を読んだので、実際にDDDで設計して作ってみた! - Qiita より引用徐々にやっていこう。。。。
ユースケース
ユーザーストーリーを作成し、それに基づいたユースケースを洗い出します。
ここの洗い出しに関しては、下記書籍を参考に掘り下げていきたいと思っています。
ユースケース駆動開発実践ガイド (OOP Foundations)
良いユースケースを書くための発想法
ユースケースをモデリングする - Qiita
若手エンジニア必読!超絶分かるユースケース図-全知識と書き方5ステップGoでの実装
アーキテクチャ
Goで,レイヤードアーキテクチャ - Qiita
ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か[DDD] - little hands' lab
【GO/DDD】レイヤードアーキテクチャの整理 - Qiita全体図のフォルダ構成は下記の通りです。
├── app │ ├── application │ │ └── usecase │ │ ├── survey.go │ ├── domain │ │ ├── model │ │ │ ├── survey.go │ │ ├── repository │ │ │ ├── survey.go │ │ └── service │ │ ├── survey.go │ ├── infrastructure │ │ ├── database.go │ │ └── persistence │ │ ├── survey.go │ └── presentation │ ├── handler │ │ ├── survey.go │ ├── middleware │ │ └── middleware.go │ └── router.go ├── dbファイルの分割
集約の境界については、ファイルが境界になっています。
また、値オブジェクトやエンティティは domain層で表現されることになります。application
ドメインロジックを呼び出し、その結果をただ返すことに特化します。
基本薄い層になります。domain
model
,repository
,service
で構成されます。model
使用するデータ構造とドメインロジックを書きます。
// データ構造の部分 package model type Survey struct { SurveyID uint `json:"survey_id" db:"survey_id"` Question string `json:"question" db:"question"` SurveyAnswerType string `json:"survey_answer_type" db:"survey_answer_type"` } // ドメインロジック ....repository
ここが依存させるべき抽象の箇所です。
依存関係逆転の法則では、抽象に依存させることが推奨されていました。
理由は、上位モジュールが下位モジュールに依存しません。
つまりこれは、インターフェースを満たすモジュールであればどんなものでも使用できることを指しましす。
上位モジュールが使いたい下位モジュールを選択することができるのです。package repository import "github.com/app/domain/model" type SurveyAccessor interface { ListSurveyByCampaignID(campaignID uint) (*[]model.Survey, error) }service
ドメインサービスに書くべき実装を書きます。
package service import ( "github.com/app/domain/model" "github.com/app/domain/repository" ) // repository経由でアクセスするためのAccessor作成 type SurveyService struct { serviceAccesor repository.SurveyAccessor } func NewSurveyService(sa repository.SurveyAccessor) *SurveyService { return &SurveyService{ serviceAccesor: sa, } } func (ss *SurveyService) ListSurveyByCampaignID(campaignID uint) (*[]model.Survey, error) { surveys, err := ss.serviceAccesor.ListSurveyByCampaignID(campaignID) if err != nil { return nil, err } return surveys, nil }infrastracture
データベース(データストア)関連の処理(SQL)などを書きます。
presentation
http などの入出力のプロトコルを定義します。
その他気になったところ
Go言語での実装
Goのpackage構成と開発のベタープラクティス - Tech Blog - Recruit Lifestyle Engineer
Go言語でClean Architectureを実現して、gomockでテストしてみた - Qiita
Goにおけるドメインオブジェクト設計の考察 - 日記マン
【Go+DDD】エンティティと値オブジェクトの実装方法(自己流) - yyh-gl's Tech Blogドメインモデルからデータベースの設計をする順番
DDDのモデリングとは何なのか、 そしてどうコードに落とすのか
プロダクトでの活用
競合他社との差別化要因にリソースを割きたい自社プロダクトでは、
グロースさせていきたいビジネスの概念を型(クラス)に落とし込むこと がドメインモデリングになる、と考えています。
Goにおけるドメインオブジェクト設計の考察 - 日記マン より引用フロントエンドとの融合
このDDDとフロントエンドの関係については、次回調べていきたいと思います。
ドメイン駆動Vuexで複雑さに立ち向かう - スタディスト開発ブログ - Medium
Full-Stack JavaScript meets DDD. - Qiita
- 投稿日:2020-06-28T19:45:16+09:00
go修行12日目 channelなど
channel
package main import "fmt" // SlieceとChannelを扱う関数 func gorooutine1(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } // <- Channelに送信 c <- sum } func main() { s := []int{1, 2, 3, 4, 5} // channel integerを扱う c := make(chan int) go gorooutine1(s, c) // <- c でChannelから受け取るまで待機 x := <-c fmt.Println(x) }15キューのような使い方
- 2つのチャンネルを並列実行しても2つの処理の実行を待つ
package main import "fmt" // SlieceとChannelを扱う関数 func gorooutine1(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } // <- Channelに送信 c <- sum } func gorooutine2(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } // <- Channelに送信 c <- sum } func main() { s := []int{1, 2, 3, 4, 5} // channel integerを扱う c := make(chan int) go gorooutine1(s, c) go gorooutine2(s, c) // <- c でChannelから受け取るまで待機 x := <-c fmt.Println(x) y := <-c fmt.Println(y) }15 15Bufferd channel
package main import "fmt" func main() { ch := make(chan int, 2) ch <- 100 fmt.Println(len(ch)) ch <- 200 fmt.Println(len(ch)) // rangeで呼び出す場合はcloseしないと次のChannelを呼ぼうとしてエラーになる close(ch) // forで1つづつ呼び出す for c := range ch { fmt.Println(c) } }1 2 100 200channelのrange、Close
package main import "fmt" func goroutine1(s []int, c chan int) { sum := 0 for _, v := range s { sum += v c <- sum } // 処理完了後はcloseを入れる close(c) } func main() { s := []int{1, 2, 3, 4, 5} c := make(chan int, len(s)) go goroutine1(s, c) // channelを1回ずつ呼び出す for i := range c { fmt.Println(i) } }1 3 6 10 15Producer Consumer
package main import ( "fmt" "sync" "time" ) func producer(ch chan int, i int) { // Something ch <- i * 2 } func consumer(ch chan int, wg *sync.WaitGroup) { for i := range ch { func() { fmt.Println("process", i*1000) wg.Done() }() } fmt.Println("#################################") } func main() { var wg sync.WaitGroup ch := make(chan int) // Producer for i := 0; i < 10; i++ { wg.Add(1) go producer(ch, i) } // Consumer go consumer(ch, &wg) wg.Wait() close(ch) time.Sleep(2 * time.Second) fmt.Println("Done") }process 0 process 4000 process 2000 process 6000 process 8000 process 10000 process 12000 process 14000 process 16000 process 18000 ################################# Donefan-out fan-in
package main import "fmt" func producer(first chan int) { defer close(first) for i := 0; i < 10; i++ { first <- i } } func multi2(first chan int, second chan int) { defer close(second) for i := range first { // 1 * 2 = 2 // 2 * 2 = 4 second <- i * 2 } } func multi4(second chan int, third chan int) { defer close(third) for i := range second { // 2 * 4 = 8 // 4 * 4 = 4 third <- i * 4 } } func main() { first := make(chan int) second := make(chan int) third := make(chan int) go producer(first) go multi2(first, second) go multi4(second, third) for result := range third { fmt.Println(result) } }0 8 16 24 32 40 48 56 64 72select
package main import ( "fmt" "time" ) func goroutine1(ch chan string) { for { ch <- "packat from 1" time.Sleep(3 * time.Second) } } func goroutine2(ch chan string) { for { ch <- "packat from 2" time.Sleep(1 * time.Second) } } func main() { c1 := make(chan string) c2 := make(chan string) go goroutine1(c1) go goroutine2(c2) for { select { case msg1 := <-c1: fmt.Println(msg1) case msg2 := <-c2: fmt.Println(msg2) } } }packat from 1 packat from 2 packat from 2 packat from 2 packat from 1 packat from 2 packat from 2 packat from 2Default Section
package main import ( "fmt" "time" ) func main() { tick := time.Tick(100 * time.Millisecond) boom := time.After(500 * time.Millisecond) for { select { // 100Millisocond毎 case <-tick: fmt.Println("tick,") // 500Millisocond毎 case <-boom: fmt.Println("BOOM") // loop終了 return // それ以外はピリオド default: fmt.Println(" .") time.Sleep(50 * time.Millisecond) } } }. . tick, . . tick, . . tick, . . tick, . . tick, BOOM
- 投稿日:2020-06-28T15:29:37+09:00
【Go言語】Goのテストでキャッシュを利用しない方法
Go言語のテストにおいては、Go1.10から結果がキャッシュされるようになってしまいます。
キャッシュを利用せずにテストを行うには以下のオプションを付与する必要があります。-count=1また、キャッシュを削除するには以下のコマンドが必要です。
go clean -testcache
- 投稿日:2020-06-28T13:33:22+09:00
競プロメモ | 約数カウント
最近、競技プログラミングを始めたのですが、才能はなく、要領も悪く、解法を覚えるのも苦手でどうしようもない弱者なのだと思い知る日々です。でも、それでも、強くなりたいので、よく出てきそうなアルゴリズムやデータ構造や細かい Tips など何でも、メモできることはして、少しずつ知識と経験を蓄えていきたいと思って書いています。
今日は正の約数の個数の数え上げです。
最後にこれを使う問題を挙げます。こちらは、共通点のある問題に出会ったら随時追加していきたいと思います。知っている方はコメントで教えていただけると嬉しいです。
また、アドバイスなどもあったらコメントで教えていただけるととても嬉しいです。
やること
$1$ からある正の整数 $n$ までの整数のそれぞれについて、正の約数の個数を数え上げる。
方法
- 長さ $n + 1$ の整数配列 $A$ を用意する。(つまり $n$ までのインデックスを持つ配列)
- $1$ から $n$ までの整数について、配列 $A$ 内でその整数の倍数をインデックスとする要素をインクリメントしていく。
コード
divisorCounts := make([]int, n+1, n+1) for i := 1; i <= n; i++ { for j := i; j <= n; j += i { divisorCounts[j]++ } }これを使う問題
- 投稿日:2020-06-28T13:33:22+09:00
Goで競プロメモ | 約数カウント
最近、競技プログラミングを始めたのですが、才能はなく、要領も悪く、解法を覚えるのも苦手でどうしようもない弱者なのだと思い知る日々です。それでも、強くなりたいので、よく出てきそうなアルゴリズムやデータ構造や細かい Tips など何でも、メモできることはして、少しずつ知識と経験を蓄えていきたいと思って書いています。
今回は「約数カウント」についてメモします。
「約数カウント」
何をやるか
$1$ からある正の整数 $n$ までの整数のそれぞれについて、正の約数の個数を数え上げる。
どうやるか
- 長さ $n + 1$ の整数配列 $A$ を用意する。(つまり $n$ までのインデックスを持つ配列)
- $1$ から $n$ までの整数について、配列 $A$ 内でその整数の倍数をインデックスとする要素をインクリメントしていく。
コード
divisorCounts := make([]int, n+1, n+1) // 1 から n までの i について for i := 1; i <= n; i++ { // i の倍数を処理 for j := i; j <= n; j += i { divisorCounts[j]++ } }「約数カウント」を使う問題
AtCoder ABC172 D Sum of Divisors
私の解答はこちら: https://atcoder.jp/contests/abc172/submissions/14795780
上の説明で示した通りのコードを使います。
func Solve(n int) int { divisorCounts := make([]int, n+1, n+1) // 1 から n までの整数 i について for i := 1; i <= n; i++ { // i の倍数を処理 for j := i; j <= n; j += i { divisorCounts[j]++ } } ans := 0 for i, v := range divisorCounts { ans += i * v } return ans }AtCoder ABC134 D Preparing Boxes
私の解答はこちら: https://atcoder.jp/contests/abc134/submissions/14843714
この問題では、「約数カウント」を少し変形した形で使います。
「約数カウント」では $n$ の約数は $n$ 以下の範囲にしか存在しない ことを利用して、小さい $i$ から順番にその倍数を加算していくことで効率的に約数の個数を数え上げていきました。配列において インデックスの小さい要素から値を確定させていく イメージです。
それに対して以下では、これを逆転して $n$ の倍数は $n$ 以上の範囲にしか存在しない ことを利用します。配列において インデックスの大きな要素から値を確定させていく イメージです。
したがって、「約数カウント」とは対照的に、配列の後方 から要素を処理します。
func Solve(a []int) []int { inOrNotList := make([]int, len(a)+1, len(a)+1) var ans []int // n から 1 までの整数 i について for i := len(a); i >= 1; i-- { sum := a[i-1] // i の倍数を処理 for j := i * 2; j <= len(a); j += i { sum += inOrNotList[j] } if sum%2 == 1 { inOrNotList[i] = 1 ans = append(ans, i) } } return ans }AtCoder ABC170 D Not Divisible
私の解答はこちら: https://atcoder.jp/contests/abc170/submissions/14849721
この問題は、 配列において他の要素のどれでも割り切れない要素を数える というものです。
これは言い換えれば、 配列において他の要素の倍数になっていない要素を数える ということですから、これは配列の各要素について、そのすべての倍数に対して(その数の約数が配列に含まれること示す)印をつけていくことで確かめられます。
func Solve(A []int) int { sort.Sort(sort.IntSlice(A)) max := A[len(A)-1] divisible := make([]int, max+1, max+1) // 配列内の要素それぞれについて for _, n := range A { if divisible[n] != 0 { divisible[n]++ continue } // 要素の倍数を処理 for multiple := n; multiple <= max; multiple += n { divisible[multiple]++ } } notDivisibleCount := 0 for _, n := range A { if divisible[n] == 1 { notDivisibleCount++ } } return notDivisibleCount }最後に
私は記事を書くのも初心者ですから、分かりにくいところや説明不足なところがあったらどんどん厳しく教えていただけると幸いです。
また、自分で過去問に取り組む中で同系統の問題に遭遇したら、随時ここへ追加していきたいと思いますが、知っている方はコメントで教えていただけるととても嬉しいです。
最後まで読んで下さってありがとうございました。
- 投稿日:2020-06-28T13:33:22+09:00
競プロメモ | 正の約数の個数の数え上げ
最近、競技プログラミングを始めたのですが、才能はなく、要領も悪く、解法を覚えるのも苦手でどうしようもない弱者なのだと思い知る日々です。でも、それでも、強くなりたいので、よく出てきそうなアルゴリズムやデータ構造や細かい Tips など何でも、メモできることはして、少しずつ知識と経験を蓄えていきたいと思って書いています。
今日は正の約数の個数の数え上げです。
最後にこれを使う問題を挙げます。こちらは、共通点のある問題に出会ったら随時追加していきたいと思います。知っている方はコメントで教えていただけると嬉しいです。
また、アドバイスなどもあったらコメントで教えていただけるととても嬉しいです。
やること
$1$ からある正の整数 $n$ までの整数のそれぞれについて、正の約数の個数を数え上げる。
方法
- 長さ $n + 1$ の整数配列 $A$ を用意する。(つまり $n$ までのインデックスを持つ配列)
- $1$ から $n$ までの整数について、配列 $A$ 内でその整数の倍数をインデックスとする要素をインクリメントしていく。
コード
divisorCounts := make([]int, n+1, n+1) for i := 1; i <= n; i++ { for j := i; j <= n; j += i { divisorCounts[j]++ } }これを使う問題
- 投稿日:2020-06-28T13:04:14+09:00
Goのインターフェースについて
参考文献
丸々引用している箇所が多いので最初に紹介いたします。
Goのinterfaceがわからない人へ投稿までの背景
インターフェースがよう分からんかった~
(Tour of Goで関数をMとかIとか1文字で定義されてもややこしい)~
Tour of Goに出てくるStringerの使い方が謎すぎた
間違っているところが確実にあるので、コメント等いただけたら幸いです。インターフェスの使い方
- インターフェースの定義
- インターフェースを使った関数
- インターフェースの実装
- インターフェースの使い方
1.インターフェースの定義
A:参考にしたコード(Tour of Goより食べる動作を例にしているので非常にわかやすいです)
B:Tour of Gotype Eater interface{ PutIn() Chew() Swallow() } type Human struct{ Height int }B
type Stringer interface { String() string } type Person struct { Name string Age int }違いといえば
type hoge interface{ fuga() }のところでfuga()の後に型を定義しているか
2.インターフェースを使った関数
A
func EatAll(e Eater){ e.PutIn() e.Chew() e.Swallow() }B
なし(fmtパッケージだから必要ない?すみませんここよくわかってないです)
3.インターフェースの実装
A
func (h Human) PutIn(){ fmt.Println("道具を使って丁寧に口に運ぶ") } func (h Human) Chew(){ fmt.Println("歯でしっかり噛む") } func (h Human) Swallow(){ fmt.Println("よく噛んだら飲み込む") }B
func (p Person) String() string { return fmt.Sprintf("%v (%v years)", p.Name, p.Age) }BはSprintfを使用しているのでreturnで値を返しています
4.インターフェースの使い方
A
func main() { var man Human = Human{Height: 300} var eat Eater eat = man EatAll(eat) }B
func main() { a := Person{"Arthur Dent", 42} fmt.Println(a) }まとめ
インターフェースについては理解することができましたが、今度はStringerを学習せねば、、、
最後まで読んでいただきありがとうございます!
- 投稿日:2020-06-28T07:01:30+09:00
Goで簡単なオーダーシステムを作る
こんにちは。そうはぶです。
A Tour of GoとスターティングGo言語でGoの基本を勉強したのでアウトプットとしてProgateのRuby講座で作ったオーダーシステムに似たようなものをGoで実装してみました!
ポインタのやりとりも少しですが入っているので、A Tour of Go終わったけど、なにから作ろうと困っている人の参考になれば幸いです。コード
main.gopackage main import "fmt" //Menu型を定義 type Menu struct { Name string Price int } //商品番号(orderNumber)と注文数(count)を定義 var orderNumber, count int //料金を計算する func Calc(totalPricePtr *int, menuPricePtr *int, count int) { *totalPricePtr += *menuPricePtr * count } func main() { fmt.Printf("商品番号を選択してください。\n\n") //スライスに全メニューを入れる menus := []Menu{ {Name: "カレーライス", Price: 500}, {Name: "ラーメン", Price: 700}, {Name: "牛丼", Price: 300}, } //menusをrangeで回して各々出力させる for i, m := range menus { fmt.Printf("%d:%s(%d円)\n", i, m.Name, m.Price) } //合計金額を初期値0円として定義 totalPrice := 0 //注文処理 for { //商品番号の入力を受け付ける fmt.Scan(&orderNumber) //9が入力されたら合計金額を出力して終了 if orderNumber == 9 { fmt.Printf("合計%d円です。", totalPrice) break } //選択された商品番号の商品の個数を入力し、追加注文の受付を続ける fmt.Printf("%sが選択されました。\n", menus[orderNumber].Name) fmt.Printf("注文個数を入力してください。\n") fmt.Scan(&count) Calc(&totalPrice, &menus[orderNumber].Price, count) fmt.Printf("合計%d円です。", totalPrice) fmt.Println("他にご注文はございますか?") fmt.Println("なければ9を、ある場合は商品番号を入力してください。") } }参考
- 投稿日:2020-06-28T02:42:28+09:00
go修行11日目 タイプアサーションとか
タイプアサーション
- 型変換をすること
- interface型で受け取ると型の判断ができないため
intger型
package main import "fmt" func do(i interface{}){ // タイプアサーション interface型だとそのまま利用できないのでintergerへ変換する必要がある ii := i.(int) ii *= 2 fmt.Println(ii) } func main(){ var i interface{} = 10 do(i) }出力
20string型
package main import "fmt" func do(s interface{}){ ss := s.(string) fmt.Println(ss + "!") } func main(){ do("Mike") }Mike!Switch type
package main import "fmt" func do(i interface{}){ // いろいろな型に対応できるようにできるswitch type switch v := i.(type){ // intgerの場合 case int: fmt.Println(v * 2) // stringの場合 case string: fmt.Println(v + "!") // それ以外 default: fmt.Println("I don't know %T%n", v) } } func main(){ // string do("Mike") // integer do(10) // boolean do(true) }Mike! 20 I don't know %T%n trueStringer
package main import "fmt" type Person struct{ Name string Age int } // String()で出力方法を変えられる func (p Person) String() string{ // 名前だけ返す(年齢は返さない) return fmt.Sprintf("My name is %v", p.Name) } func main() { mike := Person{"Mike", 22} fmt.Println(mike) }My name is Mikeカスタムエラー
package main import ( "fmt" ) type UserNotFound struct{ Username string } func (e *UserNotFound) Error() string{ return fmt.Sprintf("User not found: %v", e.Username) } func myFunc() error{ // 何かエラーが出たら ok := false // okならnil if ok { return nil } // ユーザーが見つからなかったら return &UserNotFound{Username: "Mike"} } func main() { if err := myFunc(); err != nil { fmt.Println(err) } }User not found: Mikegoroutine
- 並列処理
package main import ( "fmt" "sync" ) func goroutine(s string, wg *sync.WaitGroup){ for i := 0; i < 5; i++{ // time.Sleep(100 * time.Millisecond) fmt.Println(s) } wg.Done() } func normal(s string){ for i := 0; i < 5; i++{ // time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main(){ var wg sync.WaitGroup wg.Add(1) // 並列処理 go goroutine("world", &wg) normal("hello") // time.Sleep(2000 * time.Millisecond) wg.Wait() }hello hello hello hello hello world world world world world