20190704のGoに関する記事は5件です。

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.rb
class Person
  def greet
    print word
  end
end

その Person クラスを継承する English クラスを定義します。

english.rb
class English < Person
  def word
    'Hello!'
  end
end

同様に Japanese クラスも定義します。

japanese.rb
class Japanese < Person
  def word
    'こんにちは!'
  end
end

EnglishJapanese、どちらでも挨拶できます。

e = English.new
e.greet # 'Hello!'

j = Japanese.new
j.greet # 'こんにちは!'

Inheritance は、普段 Ruby・Railsを書いてる人にとって、ごく一般的で馴染みのある設計だと思います。

Composition の例

Composition で設計する場合、「挨拶ができる」ことに着目し、そのビジネスロジックを Greeting クラスとして切り出します。(モジュールを include するやり方は、クラス継承ツリーに含まれてしまうので、Composition の純粋な例としては使えません。)

greeting.rb
class Greeting
  def greet(word)
    print word
  end
end

その Greeting クラスを English クラスに持たせます(合成します)。

english.rb
class English
  attr_reader :greeting

  def initialize
    @greeting = Greeting.new
  end

  def greet
    greeting.greet(word)
  end

  def word
    'Hello!'
  end
end

同様に Japanese クラスも定義します。

japanese.rb
class Japanese
  attr_reader :greeting

  def initialize
    @greeting = Greeting.new
  end

  def greet
    greeting.greet(word)
  end

  def word
    'こんにちは!'
  end
end

これで先ほどと同様に、EnglishJapanese どちらでも挨拶できます。

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 では、RobotPerson ではないので、スーパークラス名や継承ツリーの構成を再検討しないといけません。また、スーパークラスに変更を加えると、その下位クラスで意図しないエラーが起きるリスクがあります。

Composition のデメリット

一般的に考えられているデメリットは1つで、合成したメソッドを呼ぶためのメソッドを書かないといけないことです。上の Ruby の例で言う、English#greetJapanese#greet ですね。

一方の Inheritance では、継承したスーパークラスのメソッドをそのまま呼び出せます。したがって、スーパークラスに基本的なビジネスロジックを持たせておけば、Composition よりも少ないコードで同じ振る舞いを実装することが可能です。(ただし、何でもかんでも詰め込むと「神クラス」となって収集がつかなくなるので注意!)

デメリットの回避

この Composition のデメリットを回避するために、Go では Embedding types が用いられます。

先ほどあげた Go Playground のコード例でも Greeting タイプを EnglishJapanese タイプに組み込むことで、それぞれのタイプで greet メソッドを定義することを回避しています。

// 一部抜粋
type Greeting struct{}

type English struct {
    Greeting
}
type Japanese struct {
    Greeting
}

まとめ

継承は、抽象化したコードで効率的な実装ができるので、変更の可能性が少ない部分などにはとても有効な設計手法です。そして、Ruby を使っている人にとっては、とても馴染みがあります。

しかし、Go では、合成のみが採用されており、型の継承はできません

この言語仕様の違いを、背景にある「Composition over inheritance」と一緒にしっかり理解しておけば、より早く Go の世界に馴染むことができるでしょう。

Sources

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Excelize 2.0.1 is released

Excelize

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() and GetDefaultFont() support to change the default font, relate issue #390
  • New functions SetDocProps() and GetDocProps(), 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() and GetDefinedName() 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 images

Bug 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 }}

まとめ

常識なのかもしれませんが、
私は、はまったので誰かの役に立てれば。

参考:
https://stackoverflow.com/questions/43263280/go-template-cant-evaluate-field-x-in-type-y-x-not-part-of-y-but-stuck-in-a

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)
        ...
        }
    }

https://play.golang.org/p/1bmT13JTA3O

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3にgolangのSDK経由でファイルアップロードする

します。
以下を参考にさせていただきました

AWS SDK for Go S3バケット基本操作
https://qiita.com/maniju/items/df6050ea8f8f541360f4

go 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.go
package 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,
  })
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む