草庐IT

inheritance - 使用设置保护值的 Golang 继承

coder 2023-06-27 原文

我试图更好地理解如何在 Go 中使用 protected 空间。我来自 java,这意味着我可以通过 protected 继承访问值,因为这里只有组合,我想确保我走在正确的道路上。

问题:我想在子实现中设置一个值,但不在通用接口(interface)上公开一个 setter 。

当确实没有层次结构时,为“子类”提供 setter 的最佳方法是什么?

这意味着我想要:

type Bottom interface {
    GetYouSome()

    // rote things
    SetSpeed(int)
    DeliveryMechanism() chan string
}

请注意,没有 SetDeliveryMechanism(chan string) 方法。

我想我会从一个实现死记硬背的基础开始,这意味着实际的实现将提供“GetYouSome”方法。我也希望它们存在于不同的包中。将有许多实现,我希望命名空间被沙盒化(即它们都可以使用常量 DefaultPort)。

为了说明这个问题,我做了一个小项目。它的布局如下:

.
└── src
    ├── main.go
    └── parent
        ├── child
        │   └── child.go
        └── parent.go

child.go 中,我们实际上创建了一些类型的 Bottom,但在 parent.go 中,我们实际上定义了样板代码( setter / getter )。问题是我无法在任何地方实例化 channel !

理想的实现应该是这样的:

//------------- parent.go -------------
package parent

type Bottom interface {
    GetYouSome()

    // rote things
    SetSpeed(int)
    DeliveryMechanism() chan string
}

// Intended to implement the boring things
type GenericBottom struct {
    speed        int
    deliveryChan chan string
}

func (bot *GenericBottom) SetSpeed(speed int) {
    bot.speed = speed
}

func (bot GenericBottom) DeliveryMechanism() chan string {
    return bot.deliveryChan
}

//------------- child.go -------------
package child

import "parent"

func New(speed int) parent.Bottom {
    impl := new(Composite)
    impl.name = "simple"
    impl.SetSpeed(speed)

    // illegal! not exported
    // impl.deliveryChannel = make(chan string)
    return impl
}
// intended so that we can seamlessly treat the Composite
// as a Bottom
type Composite struct {
    parent.GenericBottom
    name string
}

func (a Composite) GetYouSome() {
    fmt.Println("Inside the actual implementation")
}

我认为有两种方法可以解决这个问题。

(1) 创建一个子类来包装 GenericBottom 类,传递所有方法。真令人难过,它仍然存在我无法直接访问 deliveryChannel 类的问题。我必须在父包中构建一个 new 构造函数,然后在子类中显式设置实例。

//------------- parent.go -------------
func NewGenericBottom() GenericBottom {
    return GenericBottom{0, make(chan string)}
}

//------------- child.go -------------
func New(speed int) parent.Bottom {
    impl := new(ExplicitComposite)
    impl.name = "explicit"

    // now I can set the channel? Nope
    // impl.gb = parent.GenericBottom{speed, make(chan string)}

    impl.gb = parent.NewGenericBottom()
    impl.SetSpeed(speed)
    return impl
}
// this has to pass through each method
type ExplicitComposite struct {
    gb   parent.GenericBottom
    name string
}

func (e ExplicitComposite) GetYouSome() {
    fmt.Println("Inside the explicit implementation")
}

func (e ExplicitComposite) DeliveryMechanism() chan string {
    return e.gb.DeliveryMechanism()
}

func (e *ExplicitComposite) SetSpeed(speed int) {
    e.gb.SetSpeed(speed)
}

或者! (2) 我可以在 GenericBottom 上添加一个 setter 方法。但是任何使用 GenericBottom 的人都可以转换为访问权限吗?我不会真的受到“保护”。

像这样:

//------------- parent.go -------------

func (bot *GenericBottom) SetChannel(c chan string) {
    bot.deliveryChan = c
}

//------------- child.go -------------

type CheatersBottom struct {
    parent.GenericBottom
    name string
}

func (a CheatersBottom) GetYouSome() {
    fmt.Println("Inside the cheaters bottom")
}

func NewCheatersBottom(speed int) parent.Bottom {
    impl := new(CheatersBottom)
    impl.SetChannel(make(chan string))
    impl.SetSpeed(speed)
    return impl
}

当确实没有层次结构时,为“子类”提供 setter 的最佳方法是什么?

最佳答案

您的主要问题是您是一名 Java 程序员。我的意思不是刻薄或中伤 Java 程序员;我的意思是,Java 中的通用思维方式与您在 Go 中的设计思维方式完全不同。也许不如尝试用 C 编写 Haskell 代码那么多,但它们仍然是完全不同的思维方式。

我已经写了很多草稿试图“修复”你的代码,但你设计它的方式与没有真正概念继承的语言根本不一致。如果你发现自己曾经在 Go 中使用过“base”或“parent”这些词,甚至在某种程度上使用过“generic”这个词,你可能会与类型系统大打出手。我认为这是大多数从 OO 语言转向 Go 的人必须经历的阶段。

我建议查看 Go 标准库并查看它们如何布置包。通常,您会发现以下内容: 一个包将定义一个或多个接口(interface),以及在这些接口(interface)上运行的函数。然而,接口(interface)的实际具体实现的数量要么不存在,要么非常少。然后,在另一个包中,他们提供实用程序的具体实现。最引人注目的是,与您的代码相比,永远不会有任何部分实现,除非实现一个接口(interface)自动部分实现另一个接口(interface)。

您的 GenericBottom 不一定是“错误”的,我当然自己制作了隐藏的伪抽象类,但在 Go 中,您想要的是将它们不导出为 genericBottom,并且在与所有具体实现相同的包中。那或在internal package从 Go 1.4 开始。

您现在可能会说,“但是如果我需要在别处定义我的接口(interface)的其他具体实现怎么办?”好吧,您将复制代码。这可以被视为(并且可能是)Go 类型系统的弱点,但在不同的包中实现接口(interface)时重复一些代码绝对是惯用的和常见的。这就是像 internal 这样的东西是为了减轻一些。

另外请注意,不要将包视为类。一开始我也有这个问题。大中型的包里面有10个以上的文件是比较不起眼的。只要它们是一个单一的概念单元,那就是它们的用途。这就是通过让类型共享一个包来在 Go 中实现“ protected ”功能的方式(实际上更像是默认/无修饰符)。他们都可以访问彼此未导出的字段。

最后一条建议是不要从 NewX 方法返回接口(interface)。你应该(几乎)总是返回一个指向结构的指针。通常你想要构造一个类型,并将它作为接口(interface)传递给另一个方法,而不是首先接收接口(interface)。

我建议完全重写。挑战自己;每次你想到“ child 类”这样的词时,停下来做点别的事情。看看你能想出什么。我认为您会发现,虽然一开始有点困难,但通过以与类型系统协作的方式编写代码,您最终会完成更多工作,即使这会导致一些代码卡顿。

关于inheritance - 使用设置保护值的 Golang 继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32533992/

有关inheritance - 使用设置保护值的 Golang 继承的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐