在GO中使用构建器的继承 [英] Using inheritance of builders in GO

查看:96
本文介绍了在GO中使用构建器的继承的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

 例如,我需要为每个构建类型创建构建器(基础)和特定的构建器。 

builder for html项目
builder for node.js项目
builder for python项目
builder for java项目

...。



主要功能如下:

文件:Builder.go



界面

  type Builder interface {
Build(string)error
}

文件:nodebuilder.go

  //这是结构体? ??不知道要在这里放什么... 
类型节点struct {


}


func(n节点)构建(路径字符串)错误{

// eg运行npm install哪个版本的nodejs项目

命令:= exec.Command(npm,install)
command.Dir =../path2dir/

合并,err:= command.CombinedOutput()

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

...
//返回新(错误)
}



主要假设/流程: $ b


  1. 要开始在每个模块上构建,我需要获取它的路径。

  2. 我需要将模块复制到临时文件夹中。

  3. 在其上运行构建(实现构建接口,如 mvn build npm install 等)

  4. 生成完成后用dep将模块压缩

  5. 将其复制到新的目标文件夹中

注意:除构建路径(应该被特殊处理y)所有其他功能都是相同的

like zip copy



  1. 我应该在哪里放置 zip并复制(在结构中),例如,我应该如何实现它们并将它们传递给构建器?


  2. 我应该根据假设?


解决方案

第一个原则 SOLID 表示,一段代码应该只有一项责任。



在上下文中,真正没有意义的是任何 builder 来关心构建过程中的 copy zip 部分内容。它超出了 builder 的责任范围。即使使用组合(嵌入)也不够整齐。

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

如果我们仔细查看,那么autimeually是两个 build 概念。一个是整个建筑过程,从制作临时目录到复制它全部五个步骤;另一个是构建命令,这是该过程的第三步。



这非常鼓舞人心。一个过程是习惯性的,作为一种功能来呈现,就像经典的程序编程会做的那样。所以我们写一个 Build 函数。它将序列化所有5个步骤,简单明了。



代码:

  package main 

导入(
io / ioutil


//构建器是用来构建语言的。它应该能够改变工作目录。
类型生成器接口{
Build()错误// Build在当前目录下生成代码。如果失败,它将返回一个错误。
Path()字符串//路径返回当前工作目录。
ChangePath(newPath字符串)错误// ChangePath将工作目录更改为newPath。
}

// TempDirFunc是生成一个新的临时目录。 Golang在GOPATH中需要它,所以使它变得可变。
类型TempDirFunc func()字符串

var DefualtTempDirFunc = func()字符串{
名称,_:= ioutil.TempDir(,BUILD)
返回名称
}

//构建语言。它将代码复制到由mkTempdir
//生成的临时目录,并调用Builder.ChangePath将工作目录更改为临时目录。在
//复制后,它使用Builder构建代码,然后将其压缩到tempfile中,
//将zip文件复制到`toPath`。
func Build(b Builder,toPath string,mkTempDir TempDirFunc)error {

if mkTempDir == nil {
mkTempDir = DefaultTempDirFunc
}

path,newPath:= b.Path(),mkTempDir()
推迟removeDir(newPath)//清理

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)//我不明白'dep`是什么。
if err!= nil {
return err
}

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


return nil
}

// zipDir拉出路径`dir并返回zip的名称。如果发生错误,它将返回一个空字符串和一个错误。
func zipDir(路径字符串)(字符串,错误){}

//所有其​​他funcs非常简单。

大部分内容都包含在评论中,我真的很懒惰写下所有 copyDir / removeDir 东西。设计部分没有提到的一件事是 mkTempDir func。如果代码位于 / tmp / xxx / 中,Golang会不高兴,因为它位于 GOPATH 之外,它会在更改 GOPATH 时会更麻烦,因为它会中断导入路径的serching,所以golang需要一个独特的函数来在 GOPATH



编辑:

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



编辑2:



您可以重复使用您的npm示例。

 类型Node结构{
路径字符串
}

func(n节点)构建(路径字符串)错误{
// eg运行npm install哪个build的nodejs项目
命令:= exec.Command(npm,install)
command.Dir = n.path
合并,err:= command.CombinedOutput( )
if err!= nil {
log.Println(err)
}
log.Printf(%s,合并)
返回nil


func(n * Node)ChangePath(newPath字符串)错误{
n.path = newPath
}

func(n Node) Path()string {
return n.path
}

它与其他语言一起:

pre $ func main(){
path:= GetPathFromInput()
(& Java {path},targetDirForJava(),无)
caseGo:
构建(& Java {path},targetDirForJava(),nil)getLanguageName(path){
caseJava ; Golang {path,cgoOptions},targetDirForGo(),GoPathTempDir())//你可以禁用cgo编译或类似的东西。
caseNode:
Build(& Node {path},targetDirForNode(),nil)
}
}

一个技巧是获取语言名称。 GetLanguageName 应该返回 path 正在使用的语言的名称。这可以通过使用 ioutil.ReadDir 来检测文件名。



另外请注意,虽然我做了 Node 结构非常简单,只存储路径字段,您可以很容易地扩展它。像 Golang 部分一样,您可以在其中添加构建选项。



编辑3:

关于包装结构:

首先,我认为所有的东西都是: Build 函数,语言构建器和其他util / helper应该放在一个包中。他们都为一项任务工作:建立一种语言。没有必要和几乎没有任何期望将任何一段代码隔离为另一个(子)包。



所以这意味着一个目录。其余的确是一些非常个人化的风格,但我会分享我的:



我会将函数 Build 和界面 Builder 放入一个名为 main.go 的文件中。如果前端代码非常小并且非常易读,我也会把它们放到 main.go 中,但是如果它很长并且有一些UI逻辑,我会把它放到 front-end.go cli.go ui.go ,具体取决于实际代码。

接下来,对于每种语言,我都会创建一个 .go 文件与语言代码。它明确了我可以检查他们的位置。或者,如果代码非常小,将它们放在 builders.go 中并不是一个坏主意。毕竟,现代编辑能够胜任对结构和类型的定义。



最后,所有 copyDir zipDir 函数进入 util.go 。这很简单 - 他们是水电费,大部分时间我们不想打扰他们。


I need to create builder (base) and specific builders for each build type.

e.g.

builder for html project
builder for node.js project
builder for python project
builder for java project

….

The main functionality will be like following:

File:Builder.go

interface

type Builder interface {
    Build(string) error
}

File: 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)
}

Main assumptions/process:

  1. To start build on each module I need to get the path to it
  2. I need to copy the module to a temp folder
  3. I need to run the build on it (implement the build interface like mvn build npm install etc)
  4. After build was finished zip the module with dep
  5. Copy it to new target folder

Note: beside from the build and the path (that should be handled specifically ) all other functionality are identical
like zip copy

  1. Where should I put the the zip and copy (in the structure) and for example how should I implement them and path them to the builder ?

  2. Should I structure the project differently according the assumptions?

解决方案

The first principle of SOLID says that a piece of code should have only one responsibility.

Takes the context in, it really makes no sense that any builder to care about the copy and zip part of the build process. It's beyond builder's responsibility. Even using composition (embedding) is not neat enough.

Narrow it down, the Builder's core responsibility is to build the code, as the name suggests. But more specifically, the Builder's responsibility is to build the code at a path. What path? The most idomatic way is the current path, the working dir. This adds two side methods to the interface: Path() string which returns the current path and ChangePath(newPath string) error to change the current path. The implentation would be simple, preserve a single string field as the current path would mostly do the job. And it can easily be extended to some remote process.

If we looks it carefully, there auctually is two build concepts. One is the whole building process, from making temp dir to copy it back, all five steps; the other is the build command, which is the third step of the process.

That is very inspiring. A process is idomatic to be presented as a function, as classic procedural programing would do. So we write a Build function. It serialize all the 5 steps, plain and simple.

Code:

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.

Most of things are covered in the comments and I am really felling lazy to write all those copyDir/removeDir things. One thing that is not mentioned in the design part is the mkTempDir func. Golang would be unhappy if the code is in /tmp/xxx/ as it is outside the GOPATH, and it would make more trouble to change GOPATH as it will break import path serching, so golang would require a unique function to generate a tempdir inside the GOPATH.

Edit:

Oh, one more thing I forgot to say. It is terribly ugly and irresponsible to handle errors like this. But the idea is there, and more decent error handling mostly requires the usage contents. So do change it yourself, log it, panic or whatever you want.

Edit 2:

You can re-use your npm example as following.

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
}

And to combine it with other language all together:

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

One trick is get language name. GetLanguageName should return the name of the language the code in path is using. This can be done by using ioutil.ReadDir to detect filenames.

Also note that although I made the Node struct very simple and only stores a path field, you can extend it easily. Like in Golang part, you might add build options there.

Edit 3:

About package structure:

First of all, I think literally everything: the Build function, language builders and other util/helpers should be put into a single package. They all work for a single task: build a language. There is no need and hardly any expectation to isolate any piece of code as another (sub)package.

So that means one dir. The remaining is really some very personal style but I will share mine:

I would put the function Build and interface Builder into a file called main.go. If the front-end code is minimal and very readable, I would put them into main.go as well, but if it is long and have some ui-logic, I would put it into a front-end.go or cli.go or ui.go, depending on the auctual code.

Next, for each language, I would create a .go file with the language code. It makes clear where I can check them. Alternatively, if the code is really tiny, it is not a bad idea to put them all together into a builders.go. After all, mordern editors can be more than capable to get definitaion of structs and types.

Finally, all the copyDir, zipDir functions goes to a util.go. That is simple - they are utilities, most time we just don't want to bother them.

这篇关于在GO中使用构建器的继承的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆