golang代码是否有一个已建立的模式名称,它似乎与mixin相似

问题描述 投票:1回答:2

gist https://gist.github.com/anonymous/68c3b072538ec48c2667f7db276e781b是我在现有代码库中遇到的重复golang代码模式的最小简化示例,我试图将其记录下来。从它的用法来看,它似乎与mixin类似,但是我或我的任何同事在golang之前都没有真正看到过这种模式。谁能告诉我这个模式的适当的既定名称?

要点试图说明:

  • 多种类型(A和B)使用的未修改行为实现代码(M的函数)。
  • 组成包括在主机类型(A和B)中。
  • 通过host.behaviour.method(...)调用导出和访问的行为方法,例如a.Formatter.FormatText(...)。
  • 主机方法受到行为的影响。
  • 该行为保持状态(私有或导出),修改行为的执行。
  • 添加了特定行为的类型可以由需要该行为的函数通过传递行为字段来使用,例如b:= NewB()。Formatter。
  • 许多不同的行为可以组成一个给定的类型(为了简洁起见,实际上没有显示,但你可以想象M1,M2,M3等包含在A或B中)。

它似乎并不严格满足mixin的大部分定义,因为它在编写主机对象时以超出M的知识的方式操作(可能修改主机状态)并且不直接向主机类型添加方法:

它似乎没有像我发现的一些自称为“golang mixins”的模式一样:

go
2个回答
2
投票

什么是mixin?

mixin基本上是一段代码('混入')到另一个代码:

// WARNING: Messy, untested C code ahead
// Illustrates mixins in a non-OO language.
// mixin_hasname.h
    char *name;
// mixin_hasname_impl.h
struct hasname_vtable { // Vtable for storing mixin functions. Saves a lot of memory in the long run, as only one vtable has to be defined per type.
    char *(*getname)(void *self);
};

#define CAT(X,Y) X##Y
#define CATX(X,Y) CAT(X,Y)
#define GEN_HASNAME_VTABLE(TYPENAME,PRENAME)\
static struct hasname_vtable {\
    .getname = CATX(PRENAME,getname)\
} CATX(PRENAME,vtable);\
static char *CATX(PRENAME,getname)(void *self) {\
    return ((TYPENAME *)self)->name;\
}
// main.c
struct my_c_struct {
// include our mixin fields
#include <mixin_hasname.h>
    int x, y, z;
};

// include our mixin implementation
#include <mixin_hasname_impl.h>

// generate an implementation for our struct
GEN_HASNAME_VTABLE(struct my_c_struct, mycstruct_)

int indirect(struct my_c_struct *x, struct hasname_vtable *hasname_vt) {
    printf("%s\n", hasname_vt.getname(x));
}

int main(void) {
    struct my_c_struct x;

    x.name = "Name";
    x.x = 0, x.y = 0, x.z = 0;

    printf("%s\n", mycstruct_getname(&x)); // Notice we never had to define mycstruct_getname ourselves; we avoided code duplication
    indirect(&x, mycstruct_vtable); // Generally, vtables are passed. Some languages pass typeid's or function pointers, however. (Few put their vtable in their data type (in this case `x`) because of the inefficient memory usage. In the case of C, though, it saves you from having to pass your vtable everytime you call an indirect function).

    return 0;
}

对于OOP中的mixin也是如此,但是OOP对mixin增加了很多限制。例如,mixins可能无法访问私有变量,子类成员,类方法等.Golang实际上并不是OO。

这个代码模式可以称为mixin吗?

所以,这样就可以了,这是一个混合的例子吗?不,我不会这么说。我回答这个问题时遇到的最大问题不是你的模式不遵循为OO定义的定义(因为Golang不是OO),但它并没有真正“混合”代码,它是只是将它存储在自己的领域。

struct struct_with_name_field {
    char *name;
}
// main.c
struct my_c_struct {
    struct struct_with_name_field namefield;
    int x, y, z;
}

int main(void) {
    struct my_c_struct x;

    x.namefield.name = "Name";
    x.x = 0, x.y = 0, x.z = 0;

    return 0;
}

但是,我认为将这个模式称为团队或文档中的mixin是完全合理的,特别是因为据我所知,Golang中不可能使用真正的mixin,而且这个模式就像你得到的那样接近。

摘要

Golang不允许你定义mixins(只有接口),所以你的模式不是mixin。但是,考虑到mixin的定义(特别是在OOP之外),以及你的模式可能是最接近golang的mixins这一事实,无论如何称它为mixin是非常合理的。


4
投票

TLDR

  • 这些不是mixins。
  • 是的,Go有mixins。

Longer

您的示例只是具有公开结构字段的组合。虽然这与mixins类似,但它并不相同。你甚至给出了真实混音的例子(在Go中)。

我会给自己一个,然后告诉为什么这个有用。

假设你想对人们大喊大叫。然后你需要一个Yeller界面。此外,在这种情况下,我们想要一个Yell函数,它采用Yeller并用它来大喊大叫。这是代码:

type Yeller interface {
    Yell(message string)
}

func Yell(m Yeller, message string) {
    m.Yell(message)
}

谁能喊叫?好吧,人们大叫,所以我们会创造一个。我们当然可以直接在人身上实现Yeller界面:

type Person struct {}

func (p *Person) Yell(message string) { /* yell */ }

// Let a person yell.
person := &Person{}
Yell(person, "No")

现在Person坚持实施,也许我们不希望这样。所以,你已经给出了一个解决方案:

type Person struct {
    Yeller Yeller
}

person := &Person{ /* Add some yeller to the mix here */ }

但现在,如果我们想让person大喊,我们不能直接使用我们的函数,因为Person没有实现Yeller

// Won't work
Yell(person, "Loud")

相反,我们必须明确告诉Yell使用PersonYeller

// Will work
Yell(person.Yeller, "No")

还有另一种可能性。我们可以让Person通过将调用传递给YellerPerson来实施Yeller

func (p *Person) Yell(message string) {
    p.Yeller.Yell(message)
}

// Will work again!
Yell(person, "Yes")

但是这迫使我们编写了很多样板代码,我们mixin的方法计数乘以“实现”的数量。

我们可以通过使用Go的mixin设施做得更好。

type Person struct {
    Yeller
}

p := &Person { /* Add some Yeller to the mix here */ }
Yell(p, "Hooray")

现在Person是一个Yeller,通过Yell呼吁包裹Yeller。不需要样板。

为什么这有用?想象一下你也想要Whisper,创建一个Whisperer界面,你想要一个RoboticVoice也可以WhisperYell

您可以编写类似的代码,PersonRoboticVoice都可以使用Yellers和Whisperers的不同实现来编写。

最后但并非最不重要的是,您仍然可以通过让结构本身实现方法来覆盖行为。

以下是完整的示例代码和Golang游乐场的链接:

package main

import (
    "fmt"
)

func main() {
    Yell(&Person{ Yeller: yeller("%s!!!\n") }, "Nooooo")
    Yell(&RoboticVoice{Yeller: twiceYeller("*** %s ***")}, "Oh no")
    Whisper(&Person{ Whisperer: whisperer("Sssssh! %s!\n")}, "...")
    Whisper(&RoboticVoice{ Whisperer: whisperer("Sssssh! %s!\n")}, "...")
}

type Yeller interface {
    Yell(message string)
}

func Yell(y Yeller, message string) {
    y.Yell(message)
}

type Whisperer interface {
    Whisper(message string)
}

func Whisper(w Whisperer, message string) {
    w.Whisper(message)
}

type Person struct {
    Yeller
    Whisperer
}

type RoboticVoice struct {
    Yeller
    Whisperer
}

func (voice *RoboticVoice) Yell(message string) {
    fmt.Printf("BEEP! ")
    voice.Yeller.Yell(message)
    fmt.Printf(" BOP!\n")
}

func (voice *RoboticVoice) Whisper(message string) {
    fmt.Printf("Error! Cannot whisper! %s\n", message)
}

type yeller string

func (y yeller) Yell(message string) {
    fmt.Printf(string(y), message)
}

type twiceYeller string

func (twice twiceYeller) Yell(message string) {
    fmt.Printf(string(twice+twice), message, message)
}

type whisperer string

func (w whisperer) Whisper(message string) {
    fmt.Printf(string(w), message)
}

Playground

© www.soinside.com 2019 - 2024. All rights reserved.