- 投稿日:2019-07-04T19:24:33+09:00
Goやるなら知っておきたい「Composition over inheritance」
Goを勉強している中で、「Composition over inheritance」という概念が出てきました。ちゃんと理解していなかったので、ここで改めて掘り下げます。
特に、普段 Ruby や Rails を書いている初中級者の方は、Go を勉強する前におさらいしておくことをオススメします。なぜなら、Go では、Rubyで馴染みのある Inheritance(継承)ではなく、Composition(合成)のみが使われるからです。
「Composition over inheritance」とは
「Composition over inheritance」は、日本語だと「継承より合成」と表現されます。
これは、オブジェクト思考プログラミングにおいて、親クラスやベースクラスを「継承」するよりも、「合成」によってコードを共通化・再利用する方が望ましい、という考え方です。
つまり、継承より合成の方が良いということです。
それを理由に、Go では合成のみが採用されており、型の継承はできません。
Composition と Inheritance の比較
Composition と Inheritance、それぞれについておさらいします。
両者の対比は、次のようにまとめられます。
英語 日本語 型の関係 考え方の例 Composition 合成 has-a ソファには綿が入っている Inheritance 継承 is-a ソファは家具である 具体的なコード例
文字だけでは抽象的なので、具体的なコード例で考えてみましょう。
ここでは、挨拶ができる「英語圏の人」と「日本人」をコードで表してみます。
Composition と Inheritance を対比させたいので、どちらも表現しやすい Ruby を用います(独断と偏見)。
Inheritance の例
Inheritance で設計する場合、まず「挨拶ができる」スーパークラスとして
Personクラスを定義し、greetメソッドを持たせます。person.rbclass Person def greet print word end endその
Personクラスを継承するEnglishクラスを定義します。english.rbclass English < Person def word 'Hello!' end end同様に
Japaneseクラスも定義します。japanese.rbclass Japanese < Person def word 'こんにちは!' end end
English・Japanese、どちらでも挨拶できます。e = English.new e.greet # 'Hello!' j = Japanese.new j.greet # 'こんにちは!'Inheritance は、普段 Ruby・Railsを書いてる人にとって、ごく一般的で馴染みのある設計だと思います。
Composition の例
Composition で設計する場合、「挨拶ができる」ことに着目し、そのビジネスロジックを
Greetingクラスとして切り出します。(モジュールをincludeするやり方は、クラス継承ツリーに含まれてしまうので、Composition の純粋な例としては使えません。)greeting.rbclass Greeting def greet(word) print word end endその
GreetingクラスをEnglishクラスに持たせます(合成します)。english.rbclass English attr_reader :greeting def initialize @greeting = Greeting.new end def greet greeting.greet(word) end def word 'Hello!' end end同様に
Japaneseクラスも定義します。japanese.rbclass Japanese attr_reader :greeting def initialize @greeting = Greeting.new end def greet greeting.greet(word) end def word 'こんにちは!' end endこれで先ほどと同様に、
English・Japaneseどちらでも挨拶できます。e = English.new e.greet # 'Hello!' j = Japanese.new j.greet # 'こんにちは!'Composition の例 (Go ver.)
参考として、Go で上の Composition を表すとこのようになります → こちら (Go Playground)
なぜ、Compositionが良いのか?
ここからが本題です。
なぜ「Composition over inheritance」、つまり、継承より合成の方が良いのでしょうか?
Composition のメリット
一般的に考えられているメリットは次の2つです。
1. 考えやすい
Composition の場合、その共通化させるビジネスロジック(上の例では、挨拶できること)に着目してコードを設計します。そのため、Inheritance の場合と違って、抽象化させるためのスーパークラス名や、継承ツリーの構成などに頭を悩まされることはありません。
2. 変更しやすい
コードに変更を加える場合、Composition では、そのビジネスロジックを持っている当事者のみが影響を受けます。また、あるクラスにビジネスロジックを追加する場合も、Composition として共通化していれば簡単に対応できます。
一方 Inheritance では、スーパークラスのコードを変更すると、その継承ツリーの下位クラス全体に影響があります。そのため、スーパークラスに新しい振る舞いを追加することは、意図しないエラーを生むリスクがあります。
例えば、挨拶できるロボットがいた場合...
Composition だと、新たな
RobotクラスにGreetingクラスを持たすだけでOKです。一方 Inheritance では、
RobotはPersonではないので、スーパークラス名や継承ツリーの構成を再検討しないといけません。また、スーパークラスに変更を加えると、その下位クラスで意図しないエラーが起きるリスクがあります。Composition のデメリット
一般的に考えられているデメリットは1つで、合成したメソッドを呼ぶためのメソッドを書かないといけないことです。上の Ruby の例で言う、
English#greetやJapanese#greetですね。一方の Inheritance では、継承したスーパークラスのメソッドをそのまま呼び出せます。したがって、スーパークラスに基本的なビジネスロジックを持たせておけば、Composition よりも少ないコードで同じ振る舞いを実装することが可能です。(ただし、何でもかんでも詰め込むと「神クラス」となって収集がつかなくなるので注意!)
デメリットの回避
この Composition のデメリットを回避するために、Go では Embedding types が用いられます。
先ほどあげた Go Playground のコード例でも
GreetingタイプをEnglish・Japaneseタイプに組み込むことで、それぞれのタイプでgreetメソッドを定義することを回避しています。// 一部抜粋 type Greeting struct{} type English struct { Greeting } type Japanese struct { Greeting }まとめ
継承は、抽象化したコードで効率的な実装ができるので、変更の可能性が少ない部分などにはとても有効な設計手法です。そして、Ruby を使っている人にとっては、とても馴染みがあります。
しかし、Go では、合成のみが採用されており、型の継承はできません。
この言語仕様の違いを、背景にある「Composition over inheritance」と一緒にしっかり理解しておけば、より早く Go の世界に馴染むことができるでしょう。
Sources
- Composition over inheritance - Wikipedia
- Why Go’s structs are superior to class-based inheritance - Ian Macalinao
- オブジェクト指向と10年戦ってわかったこと - @tutinoco
- 継承より合成ってなに? - Mastering Python
- Ruby : Composition over Inheritance because The Force is Strong with Composition - Kartik Jagdale
- I would love to see some composition examples - Ruby Chat
- 投稿日:2019-07-04T18:49:37+09:00
Excelize 2.0.1 is released
Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLSX files. Supports reading and writing XLSX file generated by Microsoft Excel™ 2007 and later. Supports saving a file without losing original charts of XLSX. This library needs Go version 1.8 or later.
GitHub: github.com/360EntSecGroup-Skylar/excelize
We are pleased to announce the release of version 2.0.1. Featured are a handful of new areas of functionality and numerous bug fixes.
A summary of changes is available in the Release Notes. A full list of changes is available in the change log.
Release Notes
The most notable changes in this release are:
Notable Features
- New function
SetHeaderFooter()init set header and footer support, relate issue #394- New function
SetColStyle()support to set style by columns, relate issue #397- New functions
SetDefaultFont()andGetDefaultFont()support to change the default font, relate issue #390- New functions
SetDocProps()andGetDocProps(), support to set and get doc properties, relate issue #415- The function
AddChart()now support to create new 26 types of chart: cone, pyramid and cylinder series chart for column and bar, surface 3D, wireframe Surface 3D, contour and wireframe contour,bubble and 3D bubble chart, unsupported chart type add error prompt- New functions
SetDefinedName()andGetDefinedName()support to set and get defined names- More detailed error information when open the encrypted file
- The function
AddPicture()now support to add TIF and TIFF format imagesBug Fixes
- Fix structs fields definition errors and keep double quotes in data validation formula
- Fix comments duplicate caused by inner counting errors, resolve issue #373
- Fix read file error caused by get sheet map errors, resolve issue #404
- Enhance compatibility with PivotTable, resolve issue #413
- Enhance compatibility with font size and bold style, resolve issue #411, #420 and #425
- Enhance recalculation adjuster to resolve file broken issue, after insert or remove rows in some case, relate issue #421 and #424
- Fix hide sheet does not work in some case, relate issue #418
- Fix multi chart series caused file corrupted by avoid accent theme color index overflow, relate issue #422
Miscellaneous
- Improve unit testing coverage (Line Coverage: 96.05%)
- Optimize code, fix golint issues
- 投稿日:2019-07-04T18:10:12+09:00
Go template で、「can't evaluate field X in type Y」のエラー
エラー内容
range ループ内で、rangeに与えていない変数を利用しようとすると、
「can't evaluate field X in type Y」というエラーが発生するエラーが発生する例
変数 exFlag が true の場合に $v を表示する
{{ range $i, $v := .Values }} {{ if .exFlag }} {{ $v }} {{ end }} {{ end }}なぜか
When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
rangeの中では、先頭に「.」がつく変数は、「$.」でアクセスできるようになるようです。
正しい例
{{ range $i, $v := .Values }} {{ if $.exFlag }} {{ $v }} {{ end }} {{ end }}まとめ
常識なのかもしれませんが、
私は、はまったので誰かの役に立てれば。
- 投稿日:2019-07-04T11:48:24+09:00
Goの基本的な型変換
数値 (int) → 文字列 (string)
var i int = 123 var toString string toString = strconv.Itoa(i) // "123" toString = string(i) // 123がrune(Unicode)として認識され "{" になるので注意!!Unicode 123(10進数)は
{
参考 : https://www.codetable.net/decimal/123文字列 (String) → 数値 (Int)
var s string = "123" var s2 string = "Doraemon" var toInt int toInt, _ = strconv.Atoi(s) fmt.Println(toInt) // 123 var err error toInt, err = strconv.Atoi(s2) // panicにはならないが、toIntにはゼロ値、errにはエラーメッセージを返す。 fmt.Println(toInt) // 0 fmt.Println(err) // strconv.Atoi: parsing "Doraemon": invalid syntax文字列 (string) → 論理型 (bool)
var s string = "true" var toBool bool toBool, _ = strconv.ParseBool(s) fmt.Println(toBool) // true
ParseBoolで変換できる文字列
- true : "1", "t", "T", "true", "TRUE", "True"
- false : "0", "f", "F", "false", "FALSE", "False"
論理型 (bool) → 文字列 (string)
var b bool = false var toString string toString = strconv.FormatBool(b) fmt.Println(toString) // "false"Interface{}の変換
interface{}にはどんな型でも入れるのが可能。
ただし、入れた値そのままでは元の型としての処理はできないので、.(元の型)で明示してからは可能になる。type Robot struct { name string birth int } func main() { dora := Robot{ name: "Doraemon", birth: 2112, } Anything := map[string]interface{}{ "valString": "文字列", "valInt": 1234, "valBool": true, "valStruct": dora, // Robot (struct) } } var stringValue string var intValue int var boolValue bool var structValue Robot // エラーのパターン Anything["valInt"]++ // invalid operation: Anything["valInt"]++ (non-numeric type interface {}) stringValue = Anything["valString"] // cannot use Anything["valString"] (type interface {}) as type string in assignment: need type assertion name := Anything["valStruct"].name // Anything["valStruct"].name undefined (type interface {} is interface with no methods // 正常のパターン Anything["valInt"] = Anything["valInt"].(int) + 1 stringValue = Anything["valString"].(string) name := Anything["valStruct"].(Robot).name // "Doraemon"https://play.golang.org/p/gMAvWMA7oNb
interface{}から型を明示して取り出すときは元の型ではないとエラーになる。(暗黙的な変換はできない)
// panic name := Anything["valInt"].(string) // panic: interface conversion: interface {} is int, not string // 正常 (元の型を明示して出したあとに型変換する。) name := strconv.Itoa(Anything["valInt"].(int)) // "1234"map[string]interface{}の for range
mapの値を
interface{}にしたら、どの型とでもいれられるが、rangeで回すときは内部で適切な処理のためには値の本当の型を判別し、変換が必要になる。
Type switchを使うと便利。(参考 : A Tour of Go - Type switches)var intValue int ... for i, v := range Anything { switch v := v.(type) { // 元の型キャストされる (switchのスコープ内のみ) case int: intValue = v fmt.Printf("%s:%d (%T)\n", i, v, v) // valInt:1234 (int) case string: fmt.Printf("%s:%s (%T)\n", i, v, v) // valString:文字列 (string) case bool: fmt.Printf("%s:%t (%T)\n", i, v, v) // valBool:true (bool) case Robot: fmt.Printf("%s: %+v (%T)\n", i, v, v) // valStruct: {name:Doraemon birth:2112} (main.Robot) ... } }
- 投稿日:2019-07-04T08:19:03+09:00
S3にgolangのSDK経由でファイルアップロードする
します。
以下を参考にさせていただきましたAWS SDK for Go S3バケット基本操作
https://qiita.com/maniju/items/df6050ea8f8f541360f4go version go1.12.4 darwin/amd64事前にパッケージを取得する
go get github.com/aws/aws-sdk-go/aws go get github.com/aws/aws-sdk-go/session go get github.com/aws/aws-sdk-go/credentials go get github.com/aws/aws-sdk-go/service/s3 go get github.com/aws/aws-sdk-go/service/s3/s3managerアップロードする
main.gopackage main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" "fmt" "os" ) func main() { cd := credentials.NewStaticCredentials("<アクセスキー ID>", "<シークレットアクセスキー>", "") session, err := session.NewSession(&aws.Config{ Credentials: cd, Region: aws.String("ap-northeast-1")}, ) s3ss := s3.New(session) bucket := "<バケット名>" filename := "<S3上のファイル名>" file, err := os.Open(<ファイルパス>) defer file.Close() uploader := s3manager.NewUploader(s3ss) _, err = uploader.Upload(&s3manager.UploadInput{ Bucket: aws.String(bucket), Key: aws.String(filename), Body: file, }) }
