在GO中使用构造函数的继承

7 go

我需要为每个构建类型创建构建器(基础)和特定构建器.

e.g.

builder for html project
builder for node.js project
builder for python project
builder for java project
Run Code Online (Sandbox Code Playgroud)

....

主要功能如下:

文件:Builder.go

接口

type Builder interface {
    Build(string) error
}
Run Code Online (Sandbox Code Playgroud)

文件:nodebuilder.go

//This is the struct ???? not sure what to put here...
type Node struct {


}


func (n Node) Build(path string) error {

//e.g. Run npm install which build's nodejs projects

        command := exec.Command("npm", "install")
        command.Dir = “../path2dir/“

        Combined, err := command.CombinedOutput()

        if err != nil {
            log.Println(err)
        }
        log.Printf("%s", Combined)
    }

    ...
    //return new(error)
}
Run Code Online (Sandbox Code Playgroud)

主要假设/过程:

  1. 要开始构建每个模块,我需要获取它的路径
  2. 我需要将模块复制到临时文件夹
  3. 我需要在它上运行构建(实现构建接口,如mvn build npm install等)
  4. 构建完成后用dep压缩模块
  5. 将其复制到新的目标文件夹

注意:旁边从buildpath(应具体处理)的所有其它功能是相同的
zip copy

  1. 我应该在哪里放置zip and copy(在结构中),例如我应该如何实现它们并将它们路由到构建器?

  2. 我应该根据假设不同地构建项目吗?

lea*_*bop 4

SOLID的第一个原则是一段代码应该只承担一个职责。

考虑到上下文,任何人builder关心构建过程的一部分copy确实没有意义。zip这超出了builder他的责任范围。即使使用组合(嵌入)也不够简洁。

缩小范围,Builder顾名思义,其核心职责是构建代码。但更具体地说, 的Builder职责是在路径上构建代码。什么路径?最惯用的方式是当前路径,即工作目录。这向接口添加了两个辅助方法:Path() string返回当前路径ChangePath(newPath string) error更改当前路径。实现很简单,保留单个字符串字段,因为当前路径主要完成这项工作。并且它可以很容易地扩展到一些远程进程。

如果我们仔细看的话,实际上有两个构建概念。一是整个构建过程,从创建临时目录到复制回来,总共五个步骤;另一个是构建命令,这是该过程的第三步。

这非常鼓舞人心。进程通常被表示为函数,就像经典的过程式编程所做的那样。所以我们写一个Build函数。它将所有 5 个步骤序列化,简单明了。

代码:

package main

import (
    "io/ioutil"
)

//A builder is what used to build the language. It should be able to change working dir.
type Builder interface {
    Build() error //Build builds the code at current dir. It returns an error if failed.
    Path() string //Path returns the current working dir.
    ChangePath(newPath string) error //ChangePath changes the working dir to newPath.
}

//TempDirFunc is what generates a new temp dir. Golang woould requires it in GOPATH, so make it changable.
type TempDirFunc func() string

var DefualtTempDirFunc = func() string {
    name,_ := ioutil.TempDir("","BUILD")
    return name
}

//Build builds a language. It copies the code to a temp dir generated by mkTempdir
//and call the Builder.ChangePath to change the working dir to the temp dir. After
//the copy, it use the Builder to build the code, and then zip it in the tempfile,
//copying the zip file to `toPath`.
func Build(b Builder, toPath string, mkTempDir TempDirFunc) error {

    if mkTempDir == nil {
        mkTempDir = DefaultTempDirFunc
    }

    path,newPath:=b.Path(),mkTempDir()
    defer removeDir(newPath) //clean-up

    if err:=copyDir(path,newPath); err!=nil {
        return err
    }
    if err:=b.ChangePath(newPath) !=nil {
        return err
    }

    if err:=b.Build(); err!=nil {
        return err
    }

    zipName,err:=zipDir(newPath) // I don't understand what is `dep`.
    if err!=nil { 
        return err
    }

    zipPath:=filepath.Join(newPath,zipName)
    if err:=copyFile(zipPath,toPath); err!=nil {
        return err
    }


    return nil
}

//zipDir zips the `path` dir and returns the name of zip. If an error occured, it returns an empty string and an error.
func zipDir(path string) (string,error) {}

//All other funcs is very trivial.
Run Code Online (Sandbox Code Playgroud)

大多数内容都在评论中涵盖了,我真的很懒惰写所有这些copyDir/removeDir东西。设计部分没有提到的一件事是mkTempDir功能。如果代码在 中,Golang 会不高兴,因为/tmp/xxx/它在 之外GOPATH,而且更改起来会更麻烦,GOPATH因为它会破坏导入路径搜索,因此 golang 需要一个独特的函数来在GOPATH.

编辑:

哦,还有一件事我忘了说。处理这样的错误是非常丑陋和不负责任的。但想法是有的,更体面的错误处理主要需要使用内容。所以一定要自己改变它,记录它,恐慌或者任何你想要的。

编辑2:

您可以按如下方式重复使用您的 npm 示例。

type Node struct {
    path string
}

func (n Node) Build(path string) error {
    //e.g. Run npm install which build's nodejs project
    command := exec.Command("npm", "install")
    command.Dir = n.path
    Combined, err := command.CombinedOutput()
    if err != nil {
        log.Println(err)
    }
    log.Printf("%s", Combined)
    return nil
}

func (n *Node) ChangePath(newPath string) error {
    n.path = newPath
}

func (n Node) Path() string {
    return n.path
}
Run Code Online (Sandbox Code Playgroud)

并将其与其他语言结合在一起:

func main() {
    path := GetPathFromInput()
    switch GetLanguageName(path) {
    case "Java":
        Build(&Java{path},targetDirForJava(),nil)
    case "Go":
        Build(&Golang{path,cgoOptions},targetDirForGo(),GoPathTempDir()) //You can disable cgo compile or something like that.
    case "Node":
        Build(&Node{path},targetDirForNode(),nil)
    }
}
Run Code Online (Sandbox Code Playgroud)

技巧之一是获取语言名称。GetLanguageName应该返回代码正在使用的语言的名称pathioutil.ReadDir这可以通过使用检测文件名来完成。

另请注意,虽然我使Node结构非常简单并且仅存储一个path字段,但您可以轻松扩展它。就像Golang部分一样,您可以在那里添加构建选项。

编辑3:

关于包结构:

首先,我认为实际上所有的东西:Build函数、语言构建器和其他 util/helpers 都应该放入一个包中。他们都致力于同一个任务:构建一种语言。没有必要也几乎没有期望将任何代码片段隔离为另一个(子)包。

这意味着一个目录。剩下的确实是一些非常个人的风格,但我会分享我的:

我会将函数Build和接口Builder放入一个名为main.go. 如果前端代码很小并且非常可读,我也会将它们放入main.go,但如果它很长并且有一些 ui 逻辑,我会将其放入 afront-end.gocli.goui.go,具体取决于实际代码。

接下来,对于每种语言,我将创建一个.go包含语言代码的文件。它清楚地表明我可以在哪里检查它们。或者,如果代码确实很小,将它们全部放在一个builders.go. 毕竟,现代编辑器完全有能力获得结构和类型的定义。

最后,所有copyDir,zipDir函数都转到util.go. 这很简单 - 它们是实用程序,大多数时候我们只是不想打扰它们。