Go-GTKでCUIとGUIをつなぐ的なアプリを書いてみた
ごくたまに、ファイルをドラッグ&ドロップで指定できると便利、ということがあります。
スクリーンショット
インストール
go get github.com/doloopwhile/dap
使い方
ドラッグアンドドロップでファイルをバックアップ・ディレクトリにコピーする
mkdir -p $HOME/backup dap | xargs cp {} $HOME/backup
なぜ作ったか
Debian系OSには(dragbox)https://packages.debian.org/ja/sid/dragboxというツールがあるようです。しかし、どういうわけか私のXubuntuでは動きませんでした。
そこで、(Go-GTKのD&Dの例)https://github.com/mattn/go-gtk/blob/master/example/dnd/dnd.goを元に作ってみました。Go-GTKは、デモが充実しており、実用的に使えそうな段階に入っているようです。
Goライブラリを作ったら、とりあえずgodocdown
小さなGoライブラリをつくった時、ほとんど自分しか使わないような規模なら、わざわざREADMEに「Usage」「Example」「Functions」なんて書くのは面倒ですよね。
そんな時は、godocdownで自動生成してしまうのがオススメです。 godocと同等の内容をGitHub用Markdownで生成してくれます。
例:doloopwhile/go-merror · GitHub
go get github.com/robertkrimen/godocdown/godocdown godocdown > README.md
まぁ、わざわざgodocdownでREADMEを作らなくても、 ほっておいてもgodocは、自動で生成されるのですが、READMEを空っぽにしておくのも寂しいので。
地味に待ち遠しいDockerの新機能
DockerfileのENV
コマンドで、複数の環境変数を一度に指定できるようになるらしい。
Allow ENV to set multiple variables in one layer · Issue #2333 · docker/docker · GitHub
従来は複数の環境変数を設定するには、ENVを繰り返さなければなりませんでした。
ENV GOPATH /root/go ENV CGO_CFLAGS-I/opt/fzero/include ENV CGO_LDFLAGS -L/opt/fzero/lib64 -Wl,-rpath=/opt/fzero/lib64
この場合、各ENV
ごとにレイヤーが生成されてしまい、docker push
の際に通信回数が増えるなど無駄でした。
それが1行で書ける=1レイヤーで済むようになるらしいです。 イメージ:
ENV GOPATH=/root/go CGO_CFLAGS=-I/opt/fzero/include CGO_LDFLAGS="-L/opt/fzero/lib64 -Wl,-rpath=/opt/fzero/lib64"
Goでも独自型をrangeしたい!chanを使う
Pythonでは__iter__
メソッドを定義すれば((Rubyでは.each
メソッドを定義してEnumerable
をincludeすれば、C#ではGetEnumerator()
メソッドを定義すれば、
PHPではIterator
を実装すれば))、
晴れて独自のコレクション型でも、foreach文などでループできるようになるわけですが、でもGoにはそんな仕組みはありません。
まあ、妥協案としては、関数を引数にとる.Each
を作ることはできます。
sum := 0 myCollection.Each(func(x int) { sum += x })
「でも、ぼくがつくったさいきょうの○○型をfor - range
文で使いたいんだ!」
解法:channelを返す.Iter()
知ってる人は知っている、range
にはchannelが渡せます。
A "for" statement with a "range" clause iterates through all entries of an array, slice, string or map, or values received on a channel.
he Go Programming Language Specification - The Go Programming Language
これを利用して、deckarep/golang-setのSet
型は、for
文で使えるように.Iter()
メソッドを定義してます。
package main import ( "fmt" mapset "github.com/deckarep/golang-set" ) func main() { s := mapset.NewSet() s.Add(7) s.Add(11) s.Add(13) s.Add(11) // Setだから、重複は排除される c := s.Iter() fmt.Printf("%V\n", c) // %!V(<-chan interface {}=0xXXXXXXXXXX) for x := range s.Iter() { // range文で使える! fmt.Println(x) } // 7 // 11 // 13 }
やりましたね!
genericsが無いGolangで独自コレクションを作る機会は少ないでしょうけど、
channelがrangeできること自体は、知っていると得かもしれませんよ。
Goによるデザインパターン - Strategy パターン (2)
Strategy設計の失敗
前回で、HTMLとテキストでレポートを出力するコードを書きました。その中でFormatter
interfaceを定義し、その具象型としてPlainTextFormatter
HTMLFormatter
を定義するというStrategyパターンを採用しました。
しかし、気になる点が無いではありません。
package main import ( "fmt" ) type Formatter interface { OutputStart() OutputHead(text string) OutputBodyStart() OutputLine(line string) OutputBodyEnd() OutputEnd() } type Report struct { Title string Text []string Formatter Formatter } func (r *Report) Output() { r.Formatter.OutputStart() r.Formatter.OutputHead(r.Title) r.Formatter.OutputBodyStart() for _, line := range r.Text { r.Formatter.OutputLine(line) } r.Formatter.OutputBodyEnd() r.Formatter.OutputEnd() } type PlainTextFormatter struct{} // (メソッドの実装は中略) type HTMLFormatter struct{} // (メソッドの実装は中略)
Formatter
interface には6つのメソッドが含まれます。しかし、この切り分け方は適切だったのでしょうか? 6つというのは多すぎる気がします。また、今後新しいフォーマットを採用する際に、不備が発覚するかもしれません(フッタ―にもタイトルを出力したくなるかも!)。
Strategyパターンを使う際は、ストラテジの範囲と渡すべきデータを見極める必要があります。
ContextをStrategyに渡す
今のコードのtitle
とtext
を直接渡す方法では、
1. Strategyのどのメソッドがどのデータを必要とするか、覚えなくてはならない
2. 必要ないと思ったデータも実は必要になるかもしれない(フッタ―にもタイトルを出力)
3. 全く新しいデータが登場するかもしれない(日付や提出者も記載したい)
という問題があります。
そんな時は、Context(呼び出し側)をStrategyの引数として渡します。
Contextをそのまま渡すこともできますが(下のコードで言えば*report
)、Contextもinterface
にして渡した方がテストなどで便利になるでしょう(Report
)。
// template_method.4.go package main import ( "fmt" "time" ) type Formatter interface { OutputStart(r Report) OutputHead(r Report) OutputBodyStart(r Report) OutputLine(r Report, line string) OutputBodyEnd(r Report) OutputEnd(r Report) } type Report interface { Title() string Text() []string Date() time.Time } type report struct { title string text []string date time.Time formatter Formatter } func (r *report) Title() string { return r.title } func (r *report) Text() []string { return r.text } func (r *report) Date() time.Time { return r.date } func (r *report) Output() { r.formatter.OutputStart(r) r.formatter.OutputHead(r) r.formatter.OutputBodyStart(r) for _, line := range r.text { r.formatter.OutputLine(r, line) } r.formatter.OutputBodyEnd(r) r.formatter.OutputEnd(r) } type HTMLFormatter struct{} func (*HTMLFormatter) OutputStart(r Report) { fmt.Println("<html>") } func (*HTMLFormatter) OutputHead(r Report) { fmt.Println("<head>") fmt.Printf("<title>%s</title>\n", r.Title()) fmt.Println("</head>") } func (*HTMLFormatter) OutputBodyStart(Report) { fmt.Println("<body>") } func (*HTMLFormatter) OutputLine(_ Report, line string) { fmt.Printf("<p>%s</p>\n", line) } func (*HTMLFormatter) OutputBodyEnd(r Report) { fmt.Printf("<p>Updated: %s</p>", r.Date().Format("2006-01-02 15:04:05")) fmt.Println("</body>") } func (*HTMLFormatter) OutputEnd(Report) { fmt.Println("</html>") } func main() { report := &report{ title: "月次報告", text: []string{"順調", "最高"}, formatter: &HTMLFormatter{}, } report.Output() }
Strategyの実例
標準ライブラリのsortパッケージは、Strategyパターンの一例です。
sort.Sort
はsort.Interface
というStrategyを取るようになっています。
Goのinterface - 使う側で定義する
Goのinterfaceは「使う側」が定義するもの
前回Formatter
interface、Formatter
を使ってレポートを出力するReport
struct、
Formatter
の実装PlainTextFormatter
とHTMLFormatter
を定義しました。
type Formatter interface { OutputStart() OutputHead(text string) OutputBodyStart() OutputLine(line string) OutputBodyEnd() OutputEnd() } type Report struct { Title string Text []string Formatter Formatter } type PlainTextFormatter struct{} // (中略)PlainTextFormatterのメソッド定義 type HTMLFormatter struct{} // (中略)PlainTextFormatterのメソッド定義
Goのinterface
は、Javaなどのインターフェイスや基底クラスとは異なります。
Javaのインターフェイスは、具象型を定義するとき(class 〜 implements 〜
)と、
変数を定義するときの両方に定義する必要があります。
一方、Goでは具象型の定義ではinterface
の指定の必要はありません。
Go | Java | |
---|---|---|
具象型の定義 | なし! |
class PlainTextFormatter implements Formatter {}
|
変数定義 | var fmt Formatter = &PlainTextFormatter{}
| Formatter fmt = new PlainTextFormatter();
|
Goのinterface
は純粋に変数(受け手・使用する側)での型を指定するものです。
その点、interface
はC++のTraitsや関数型言語での型クラスに似ており、
interface
を使ったコードは総称型プログラミングのコードだとと言えます。
interfaceのtips
interface
は「使用する側」のためのものですから、すべてのメソッドを列挙する必要はありません。必要なものだけ指定すればよいのです。
// Formatterの必要なメソッドだけ取り出したinterface type HeadFormatter interface { OutputStart() OutputHead(text string) } type HeadReport struct { Title string HeadFormatter HeadFormatter } r := &Report{} r.HeadFormatter = &PlainTextFormatter{} // 当然、代入できる
また、interface
は「使用する側」のためのものですから、1回しか使わないinterface
には名前を付けずに済ませることができます。
type HeadReport struct { Title string HeadFormatter interface { OutputStart() OutputHead(text string) } }
この、1. 必要なメソッドだけを、2.無名interfaceとして定義する 方法は積極的に使うべきです。たとえば以下のような効能があります。
- 具象型を定義しているパッケージをimportする必要が無くなり、コードが粗結合になる
- メソッド数が最小限になるので、具象型を実装しやすい
- 特にテスト時にモックオブジェクトを作りやすくなる
もちろん、http.ResponseWriter
のような頻出・メソッド数が多いinterface
は型を定義する側が提供した方が良いこともあります。