Code duplication in similar structs

1,237 views
Skip to first unread message

beatgammit

unread,
Sep 11, 2012, 9:21:35 PM9/11/12
to golan...@googlegroups.com
I am working on a module system, so I've defined an interface for common functionality, like so:

type Module interface {
    Id() string
    Format(io.Reader) io.Reader
    Settings() map[string]string
    Set(string, string)
}

There are a bunch more methods defined, but this should serve for the example. For example, these modules fit into some kind of data formatter. Each module formats data differently, but they each have an id, settings (and a way to set them), and a format function that takes an io.Reader and returns a Reader which does a kind of translation in between (for example encryption, annotation, etc).

Each module will have a different Format() and Id(), but the settings may just be duplication of code. For example, most of the modules store/retrieve their settings in a database, but some just store them in memory and have to be set each time. For the majority of these modules, I have redundant code that can easily be abstracted out, but the functions must be there for the few that don't work that way.

My actual implementation has a lot of functions, many of which can be abstracted away.

In languages with polymorphic classes, I could override an inherited method, or leave it as is. However, I couldn't find an easy way of doing this in Go without duplicating code or making a nasty hack.

What I've done so far is created another type, Shell, that implements all of the functions, but still allows for some flexibility:

type Shell struct {
    ModFormat(io.Reader) io.Reader
    ModSettings() map[string]string
    ModSet(string, string)
}

If Mod* != nil, then it calls it, otherwise it does it's default handling. Values that don't change (like Id()) are just set once in the constructor of Shell (can't be overridden).

Is there a better way of doing this? This doesn't seem very idiomatic, especially with the Mod* functions... Have any of you run into a similar problem? I've thought about how to re-architect it to avoid the problem, but I haven't been able to come up with anything that doesn't require a lot of code duplication or global functions.

Thanks!

Jesse McNelis

unread,
Sep 11, 2012, 9:48:22 PM9/11/12
to beatgammit, golan...@googlegroups.com
On Wed, Sep 12, 2012 at 11:21 AM, beatgammit <beatg...@gmail.com> wrote:
> I've thought about how to re-architect it to avoid the problem, but
> I haven't been able to come up with anything that doesn't require a lot of
> code duplication or global functions.

When people say "a lot of code duplication" they often mean a few lines.
Go doesn't have global functions, it just has package level functions.

Functions are one of the major ways to reuse code in Go. You define
some types that satisfy
some interface and then you write functions that act on that interface.

If you have some similar fields in structs that you need to access, embedding
(http://golang.org/doc/effective_go.html#embedding) can be helpful in
reducing duplication
of Get/Set style methods.

I'm still not sure what you're trying to do, but your format() method
seems like it would be better if your type was an io.Reader and
format() was instead a function that took an io.Reader and returned
your type.





--
=====================
http://jessta.id.au

beatgammit

unread,
Sep 11, 2012, 10:01:39 PM9/11/12
to golan...@googlegroups.com, beatgammit, jes...@jessta.id.au
Huh, I didn't know that embedding could be done for types to bring their implementations along.

Could I do something like this then:

type DBModule struct {}
func (DBModule) Settings() map[string]string
func (DBModule) Set(string, string)

type ModA struct {
    *DBModule
}
func (a *ModA) Id() string {return "ModA"}
func (a *ModA) Format(r io.Reader) io.Reader { return r }

Would that still implement the Module interface (i.e. pull in DBModule's methods)? If so, that would save on the code reuse problem that I'm having. My modules are pretty easy to categorize into types (some use the DB and some don't), so this means I can get rid of my hacky way of doing things...

BTW, it's more like 100+ lines of duplication without my hack, so it is substantial.

Thanks!!

Kamil Kisiel

unread,
Sep 11, 2012, 10:17:15 PM9/11/12
to golan...@googlegroups.com, beatgammit
That's exactly what you can do. Here's an example that illustrates what I think you're trying to achieve:

roger peppe

unread,
Sep 12, 2012, 5:04:27 AM9/12/12
to beatgammit, golan...@googlegroups.com, jes...@jessta.id.au
It's worth setting aside an hour and having a good
read of the relevant sections of the Go specification.
It's one of the more complex bits of the spec, but it's
still not hard and you'll get a much better idea of the
things you can do with embedding.

http://golang.org/ref/spec#Selectors
http://golang.org/ref/spec#Struct_types

Brandon Busby

unread,
Nov 9, 2020, 10:11:41 PM11/9/20
to golang-nuts
I have structs with duplicate fields and duplicate code to go along with them.  The problem is that the duplicate code does make use of non-common fields.  Here is an example of the code.

https://play.golang.org/p/IkoNJTdoVLD

I could extract the duplicate code sections into a method, eliminating the duplication.  The problem is that not all of the fields are the same.

If I had two separate function to deal with the non-common field usage and I could pass the respective helper into the extracted function (with the common-code) for it to invoke when it's time for the non-common code to execute, then that would be ideal.  Is there some way to do this?

Robert Engels

unread,
Nov 9, 2020, 11:27:16 PM11/9/20
to Brandon Busby, golang-nuts
If the logic is the same, wrap the field in an accessor method and use an interface. 

On Nov 9, 2020, at 9:11 PM, Brandon Busby <brandon.j...@gmail.com> wrote:

I have structs with duplicate fields and duplicate code to go along with them.  The problem is that the duplicate code does make use of non-common fields.  Here is an example of the code.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/caadc8cd-fb05-4b71-9a4c-ab7a649eaa84n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages