> 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!
}