> I'm just experiencing something where I am trying
> to get some implementation abstraction (i.e. factoring implementations
> to respect DRY principle).
>
> ... bla-bla-bla, I read a lot of OO books ...
>
> Other "Go-ish" idioms to solve this problem are welcome. Anyone have
> an alternate
> solution?
You totally didn't give us a description of your problem. Having unable
to apply practices that you've read about in OO books in Go isn't a
problem, it's a wrong thing to do in the first place.
Just forget all this object-oriented bullshit you knew and start
thinking in terms of what you want from the code. What qualities are
desirable? (like extensibility, or maintainability, or flexibility,
etc.). How it is possible to achieve these qualities using a particular
language, etc, etc.
When I hear so much abstract nonsense, I ask people to come down back
to earth and start doing things that solve problems. Real problems.
Even if the resulting code isn't super flexible.
If it helps, great. If it's not, then I'm afraid you can only help
yourself and no one can solve your problems (I mean really, _your_
problems, not some real world problems, because that's what OOP does,
introduces a non-existing problems and you're full of them, at least
it looks like that to me).
Sorry for me being honest and direct.
Hi Eric,
You haven't described the problem. You've described the issues you're
having with one particular solution to the problem.
The approach you're describing is one typically taken in class-based,
inheritance-oriented languages - but Go doesn't support inheritance.
If you're mostly accustomed to writing in the traditional "OOP" style,
then writing idiomatic Go code will require a fresh perspective.
Please give a concrete example of what you're trying to do, and then
we can offer some suggestions as to how you might structure your Go
program.
Cheers,
Andrew
AFAIK abstraction != language-ensured encapsulation.
--
regards,
Jakub Piotr Cłapa
By "abstraction of common properties and behaviours" are you referring
to those properties and behaviours of a type/class?
Typically, in OO world you build an abstract base class that does
something, and then write concrete classes that inherit from it that
behave in different ways.
In Go, you would achieve the same effect by using several types that
interact with each other via interface types, and whose
implementations are therefore interchangeable. A safe approach to
writing Go code (or, really, any code) is "make each part as simple as
possible, and communicate through simple interfaces."
I could be more specific if you would give an example of the problem
you're trying to solve.
Cheers,
Andrew
That's a meta-analysis of the description of your problem at best. If
you give a short code example along with a concrete description of
what you are trying to do, we have something to discuss.
> It should not need to be said, but having excess exported data
> definitions and methods
> is STRONG BAD, as it leads to inappropriate strong coupling, as well
> as exposure of
> data to corruption by package-using code.
That sounds well and good but I can't tell WHY you're running up against this.
> protected status allows for strong coupling only where it is arguably
> appropriate: in the
> specialization-by-incremental-addition direction, while conserving
> data safety and loose
> coupling in other directions outside the package.
Go uses abstraction by interfaces and extension by composition, but
since you haven't provided any conrete examples I cannot tell whether
this would help or hinder you.
func WrapControlProgram(ControlProgram) ControlProgramHelper
(or alternatively this could be embedded in the ControlProgramRunner,
if you don't need to have multiple different ControlProgramRunners
defined in different packages)
David
type ControlProgram interface {
AddInstrumentReading(instrumentReading InstrumentReading,
instrumentReadings map[string] *InstrumentReading,
controllerSettings map[string] *ControllerSetting)
AddControllerSetting(controllerSetting ControllerSetting,
instrumentReadings map[string] *InstrumentReading,
controllerSettings map[string] *ControllerSetting)
AssessAndAct()
}
type ControlProgramHelper struct {
instrumentReadings map[string] *InstrumentReading
controlSettings map[string] *ControllerSetting
theProgram ControlProgram
}
func (cp *ControlProgramHelper) AssessAndAct() {
// some stuff all control programs need to do at beginning of their action.
// ...
// and now do the real stuff!
theProgram.AssessAndAct()
}
If the only purpose of the 'loadcntrl' package is to be a base for
other packages, then you may as well put all of it in the same
package. Which fixes all your problems.
Separate the different 'ControlProgram's in to different files within
the same package if you need to.
- jessta
--
=====================
http://jessta.id.au
> Sorry for me being honest and direct.
It came over as rude & dismissive, I'm afraid, whatever
its technical merits.
Chris
--
Chris "allusive" Dollin
They're terse summaries of reasonable principles, at least one of which
is part of the Go phrasebook. Critique them by all means, but frothing
rudeness isn't critique.
Eric here specifically asks for "Go-ish idioms" - that's what I was
addressing in my responses.
>> You haven't described the problem. You've described the issues you're
>> having with one particular solution to the problem.
>
> Actually, he gave a pretty good description of the problem.
A problem he's experiencing because of the approach he's taking to
structure his code. An approach that doesn't work well with Go's
feature set (as you point out, below).
> The short answer is: Go supports just two layers of symbol visibility.
> The first one is "public", the second one is "package private". If X
> needs to access Y, then:
>
> - Y needs to be public, or
> - Y has to be in the same package as X
>
> Any information-hiding pattern or access-control pattern which needs
> to go beyond these rules cannot be expressed in Go directly. Period.
Sure, but is that an issue? Not in my experience.
>> The approach you're describing is one typically taken in class-based,
>> inheritance-oriented languages - but Go doesn't support inheritance.
>> If you're mostly accustomed to writing in the traditional "OOP" style,
>> then writing idiomatic Go code will require a fresh perspective.
>
> I think access-control issues are *not* unique to "OOP style", but it
I didn't say that "access-control issues are unique to OOP style". I
was talking about the approach in general.
> is a generic problem of any programming language in which you denote
> things by their *names* (function names, procedure names, type names,
> field names, package names, parameter names, variable names, ...). In
> short, these issues arise in pretty much all existing programming
> languages - *irrespective* of whether they are imperative, OO,
> functional, whatever.
>
> Note that in the above example I did *not* say what X, Y, Ys, Z are.
> They could denote classes, functions, types, fields, interfaces,
> methods, variables ...
But you did say "members of," which does not apply to all of the above.
Andrew
i don't think this would make sense.
currently, an embedded value is just like an instance
variable except that you don't have to write the
methods that re-call the same methods on the
instance variable.
you're suggesting that "embedding" should somehow
be different from "using". in go there is no distinction,
which is actually useful - i can change from embedding
a value to using that value without any loss of generality,
and i have done so in the past.
> In particular, it is DANGEROUS to have AssessAndAct() exported,
> because the ONLY valid and appropriate way it can be
> called (by anyone other than a subclass extension-implementation of
> the method) is inside some kind of control loop managed by the
> ControlProgramRunner.
"by anyone other than a subclass extension-implementation of the method"
implies that subclasses (sic) have some special privilege.
but assuming subclasses are implemented by external code,
why should they be trusted any more than any other user
of the package?
what's to stop a subclass calling AssessAndAct from within
AddControllerSetting? indeed, what's to stop it spawning
a goroutine that calls AssessAndAct some arbitrary time later?
every langage has limits on the behaviour that the compiler
can enforce. at some point you have to move from compiler-enforced to
convention-enforced behaviour.
PS go doesn't have subclasses or subtypes.
one way to go about this might be to pass AssessAndAct
a context parameter of a type that's private to the
loadcntrl package.
type Context struct {
// private fields
}
type ControlProgram interface {
AddInstrumentReading(instrumentReading InstrumentReading)
AddControllerSetting(controllerSetting ControllerSetting)
AssessAndAct(ctxt *Context)
}
then only the ControlProgramRunner is able to create
instances of Context, so it becomes harder to call
ControlProgramBase.AssessAndAct outside of the
ControlProgramRunner control loop. of course, it's still possible,
by using goroutines or storing ctxt in a global variable, but that's
equally possible with a protected subclass.
at least this way the user of the package has
to make an effort to use the AssessAndAct function
in the wrong way.
> import . "loadcntrl"
this is a bad sign BTW - it's almost never a good idea to import into "."
Not true. I'm one case, and I'm sure there are others:
http://blog.labix.org/2009/05/15/class-member-access-control-enforcement-vs-convention
That said, unlike the present thread, that post is mostly about public
vs. private. Protected is a special case of public, because it
doesn't really *hide* away anything from users. It just forces users
to access the method in a given way, and so API stability and the such
continue to apply equally to protected members.
(...)
> If the features you mention were available in Go, then it would kind
> of defeat the purpose, since you would just have reinvented C++ (and
> all the associated unnecessary and counterproductive complexity).
i don't really care about protected either, but dismissing features
because they are in a given language is as bad as desiring them for
the same reason.
--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/blog
http://niemeyer.net/twitter
No, types and classes are just abstractions. It may be that all
problems can be abstracted into types and classes, but they aren't
inherent in the problem nor the solution
as the person who made that not-so-astute observation,
i beg to differ.
it is possible to analyse problem and solution domains in
such terms, but there are many other ways of doing so.
personally, i prefer to analyse problems
in terms of objects which can use or be used,
and independent processes which communicate
with one another. where you'd subclass, use factorization instead.
i'd suggest that this usually results in a much more satisfactory
program structure .
i'm biased, i'll freely admit - i have drunk deeply of the go Kool-Aid.
> Would it make sense to add a protected visibility,
The problem is that any OO book written since 1995 or so tells you
that from a design perspective, "protected" methods are still part of
the public interface of a class.
> Therefore I have a package which exports an interface and also
> exports a "base" implementation struct type which provides a full
> implementation of some of the methods of the interface. The base
> implementation struct type also provides partial implementation (the
> general part) of one of the other methods of the interface.
I suppose you could create a factory function which implements the
interface in terms of the more basic interface.
There are other ways of doing this, too, using closures. (Closures are
amazingly powerful things.)
-- a.go
package a
type Base struct{}
func (b *Base) PublicMethod() {
println("Public Method")
}
func (b *Base) protectedMethod() {
println("Protected Method")
}
func NewBase() (*Base, func()) {
b := new(Base)
return b, func() { b.protectedMethod() }
}
-- x.go
package x
import "./a"
type Thing struct {
*a.Base
protectedBaseMethod func()
}
func NewThing() *Thing {
t := new(Thing)
t.Base, t.protectedBaseMethod = a.NewBase()
return t
}
func (t *Thing) UseProtectedMethod() {
t.protectedBaseMethod()
}
-- main.go
package main
import "./x"
func main() {
t := x.NewThing()
t.Base.PublicMethod()
t.UseProtectedMethod()
}
--
Of course, the second return value from a.NewBase() could be another
public type (instead of a func()), which has several methods
implemented on it that are - in essence - 'protected'.
There are much simpler ways of structuring software, though. If you
simply must do it this way, the capacity is there.
Andrew
The third incarnation of a series of modules written by a good
programmer is likely to be reusable and it has nothing to do with
compiler assisted proof-checking. (who do you want to cheat when you
access private members? who do you want to cheat when you extend a class
for which you have neither good documentation nor source code?)
IMHO you are exagerating minor problems. I think it would be best to ask
Smalltalk, Python, Ruby, C or Objective-C programmers whether they feel
their implementation details are too "exposed".
--
regards,
Jakub Piotr Cłapa
This argument is getting old. You'll find a lot of people that won't
care, and will find a lot of people that care. Caring or not may be
easily mapped to use cases the developer has had experience with.
Some input specific about Python, which was mentioned earlier in this thread:
http://blog.labix.org/2009/05/15/class-member-access-control-enforcement-vs-convention
The comments received are very interesting to get a feeling of
different perspectives.
The current public + package private logic available in Go suits me well, FWIW.
> THE PROBLEM is that to use this pattern effectively, I am needing to
> define too many of the methods of the interface as exported, and also
> to define many of the data members of the base struct type as
> exported,
> so that I can use these general method implementations and general
> data members in subclass struct types.
As others have said, posing the problem in abstract terms pushes you in
a particular direction. It's quite true that Go does not support
protected visibility. There are various ways around this. The most
obvious is for your child package to use a private type and a public
interface value, which gives you complete control over the set of
exposed methods. The interface exported by the child package can, if
you choose, inherit from the interface exported by the base package.
The main reason I'm replying is to mention another approach, which
requires an extra pointer per object, like this:
==================================================
package p1
type PublicInterface interface {
F1()
}
type ProtectedInterface interface {
F2()
}
type Base struct {
i int
}
func (p *Base) F1() {
}
func (p *Base) F2() {
}
==================================================
package p2
import "./p1"
type Child struct {
p1.PublicInterface
base *p1.Base
}
func (p *Child) F3() {
p.F1()
p.base.F2()
}
func NewChild() *Child {
p := new(p1.Base)
return &Child{p, p}
}
==================================================
package main
import "./p2"
func main() {
p := p2.NewChild()
p.F1()
p.F2() // INVALID: p.F2 undefined.
}
==================================================