Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm not familiar with Go but apparently it has "embedding" which is a little different. Maybe that works. But it's largely playing the same role - a way to extend a class you might not control.

So I'd rephrase that I suppose, perhaps we don't need something called Inheritance, but we do need a way to extend objects we don't control without breaking encapsulation.

As an example if you had a method that outputs XML and you want JSON, you can use an adapter object to call it, read the string, and reformat and pass it through. That works (and I've done things just as bad or worse many times from necessity), but it's definitely more hackish and less efficient than a solution that uses object fields to output JSON natively. Inheritance is my familiar way to do this in cases where I don't control the code (in which case injecting a Writer class of some sort probably makes more sense).



Go's embedding _is_ composition (has-a) in any other OO language but with a little sugar. Embedding allows the compiler to optimize the memory layout for a composed object and allows you to reference the composed object's fields without a qualifier. For example, if a Student class has-a Address {city, state, zip} then you could refer to the city field by coding aStudent.city rather than aStudent.address.city. It's handy but doesn't otherwise change "normal" composition.

I'm not advocating against inheritance. If it works then go for it. I do when I'm coding in languages that have it. This stemmed from a thread discussing whether the lack of inheritance in Go was sufficient to exclude it from being an object-oriented language.


> but we do need a way to extend objects we don't control without breaking encapsulation.

By definition, you can't extend objects you don't control without violating encapsulation. Encapsulation is all about controlling access so that the owner of a class can make changes without breaking downstream code. If downstream code accesses these private APIs, the downstream code may break, regardless of whether the access was via inheritance or composition (as a side note, "protected" makes no sense--who cares if downstream code breaks because it was accessed by inheritance but not by composition?).

> As an example if you had a method that outputs XML and you want JSON, you can use an adapter object to call it, read the string, and reformat and pass it through. That works (and I've done things just as bad or worse many times from necessity), but it's definitely more hackish and less efficient than a solution that uses object fields to output JSON natively. Inheritance is my familiar way to do this in cases where I don't control the code (in which case injecting a Writer class of some sort probably makes more sense).

You can do this just as easily without inheritance. You just need a way to access the member fields--whether you access them via inheritance or otherwise doesn't really matter. E.g.,

    class Foo:
        def __init__(self, x: int, y: int) -> None:
            self.x = x
            self.y = y

        def xml(self) -> str:
            return f"<foo x={self.x} y={self.y} />"

    # Via inheritance
    class JSONFoo(Foo):
        def json(self) -> str:
            return json.dumps({"x": self.x, "y": self.y})


    # Via composition
    class JSONFoo:
        def __init__(self, foo: Foo) -> None:
            self.foo = foo

        def xml(self) -> str:
            return self.foo.xml()

        def json(self) -> str:
            return json.dumps({"x": self.foo.x, "y": self.foo.y})

    # Simple
    def json_foo(foo: Foo) -> str:
        return json.dumps({"x": foo.x, "y": foo.y})
> I'm not familiar with Go but apparently it has "embedding" which is a little different. Maybe that works. But it's largely playing the same role - a way to extend a class you might not control.

Go's embedding is exactly composition. It doesn't let you access private member data of the embedded class. It's just syntax sugar for delegation. In other words, you could create the composition version of the JSONFoo class like this:

    type Foo struct { X: int; Y: int }

    func (f *Foo) XML() string { return fmt.Sprintf("<foo x=%d y=%d />", f.X, f.Y) }

    // The compiler will automatically generate this method:
    //
    // func (jf *JSONFoo) XML() string { return jf.Foo.XML() }
    //
    // And for a given instance of JSONFoo, the compiler will convert all instances
    // of jsonFooInstance.X to jsonFooInstance.Foo.X.
    type JSONFoo struct {Foo}

    func (jf *JSONFoo) JSON() string { return fmt.Sprintf(`{"x": %d, "y": %d}`, jf.X, jf.Y) }
Note that a `JSONFoo` isn't a `Foo`. This is an error:

    func printXML(foo *Foo) { fmt.Println(foo.XML()) }

    func main() {
        jsonFooInstance := JSONFoo{0, 0}
        // printXML(jsonFooInstance) // error
        printXML(jsonFooInstance.Foo) // correct!
    }


Thank you for the examples. I've been in perl too long where a lot of good practice is just a suggestion and not enforced. In particular private vs protected (which is what I was thinking of regarding accessing internals) doesn't really exist.

I tend to lean towards inheritance for functional extensions and composition for everything else, but there are so many ways to skin an OO cat.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: