4 ms·
> "Inheritance and OO design are the distinguishing features of OOP in my mind;" I don't consider inheritance an important feature for a language to be an "Obj
by cy_hauser 6y ago
> "Inheritance and OO design are the distinguishing features of OOP in my mind;"
I don't consider inheritance an important feature for a language to be an "Object Oriented Language." Inheritance: none, single, and multiple have been bandied about in the OO world for quite some time now so I understand where you're coming from.
I break out design from the language. While I wouldn't design very deep without knowing the language I was targeting I do consider Go as having a natural bias towards driving me to OO design over, say, functional or procedural. For me Go lends itself naturally to OO design. Sans inheritance, of course.
> "if you don't have these, then OOP is indistinguishable from FP or DOP. If Go is OOP, then which languages aren't, and why?"
Haskel, some Lisps, some Forths, etc. would be more non-OO languages in my mind. C is also a non-OO language to me even though you can certainly code it in an OO style.
I guess I agree with the blog author, OO is simply part of the language landscape these days and isn't going to be supplanted by functional any more than procedural was supplanted by OO. And with my mind-set then I'll say most all of Algol's recent children can be considered OO languages. So for me Go is definitely an object oriented language. But I now understand why you don't.
- hnick 6y ago> I don't consider inheritance an important feature for a language to be an "Object Oriented Language." I mostly agree except for one catch. How do you extend a class which is encapsulated properly? Often this is the only way I use inheritance because I need to do one or two extra things that rely on the internal state of an object which I can't modify directly. So I'm wondering if there is a good way that doesn't rely on modifying the implementation of the original object to be more flexible.
- cy_hauser 6y agoThis is tough to answer without a language in mind. Depending on what access you have you can use interfaces to get run time method binding or composition to get compile time binding. For either of these you'll have to "wire" the needed types together. This is what Go has you doing. Going back to the "old days" you'd reach for a pattern. Adapter pattern was the most common. Bridge or Facade maybe? But patterns fell out of favor a while back (imo) because they were tough on the coder's mind. A lot of languages added useful bits to create these pattern behaviors more naturally and without reaching for a book. I'd need to see code to be more specific.
- hnick 6y agoI'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).
- cy_hauser 6y agoGo'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.
- throwaway894345 6y ago> 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! }