However, depending on which stack one is using (VB 6, .NET, MFC, ATL, WRL, WinRT), the amount of boilerplate to deal with the runtime differs.
Kotlin also provides a way similar to those COM variants,
https://kotlinlang.org/docs/delegation.html#overriding-a-mem...
Best support is probably MOP in Common Lisp, I guess.
class F150(@delegate private val underlying: Car) { ... }
class F150(private val underlying: Car) : Car by Underlying { ... }
// etchttps://kotlinlang.org/docs/delegation.html
With it, you F150 can say it implements the "movable" interface, just buy stating which field it contains that implements it, and the you can run "f150.move"
E.g. something like:
class MyClass:
def __init__(self, member_class):
self.member_class = member_class
# Delegate one member
delegate move member_class.position.move
# Delegate all members
delegate * subclass.position.*
Then: a.move == a.member_class.position.move
etc. obj->foo()
will expand into enough -> dereferences until a foo is found. For instance suppose the object returned by obj's operator ->() function doesn't have a foo member, but itself overloads ->. Then that overload will be used, and so on. class Base:
def func(self):
print("In Base.func:", self.name)
class Child:
def __init__(self, name):
self.name = name
func = Base.func
c = Child("Foo")
c.func() #=> In Base.func: FooReally though it's a structure that only makes sense in strongly typed languages, so I probably shouldn't have used Python to illustrate the idea.
Edit: Totally with you on boilerplate though. +1.
"If [some fact] in the code base needs to change, how many places would we have to change it in?"
If the answer is > 1, you have a very good DRY case. Otherwise, when [some fact] changes, it will probably not be changed in one of the places, and the system will be broken.
This often coincides with having an "elegant codebase", but that's not the most important part.
For a living codebase, nowadays my general rule of thumb is to consciusly duplicate code until it covers 3 different cases, and only then refactor (unless the DRY way is as fast and obvious).
It takes more than that to yield spaghetti and a lot of time is saved on premature generalization. Plus the generalization is often way more straightforward once the explicit cases are already implemented.
I follow this too for "style" refactorings.
I followed every principle of good code, except one, DRY. I tried to make generic parts to connectors, because they do have similarities. But this is a work of at least a year, and the price to make it generic was increasingly more complex configuration files (Just the pagination alone added 3 variables for two different APIs, and the number of app i am supposed to interact with should grow to ~40). I decided after a few days of reflexion that the idea was not that dumb in principle, but unworkable in my case, and decided that one connector for one API, even with a lot of repetition.
https://docs.scala-lang.org/scala3/reference/other-new-featu...
You don't want to litter your code with "f150.ford.car.vehicle.object.move(50, 50)". You can and should re-implement "move" so that you only have to call "f150.move(50, 50)", but that still requires boilerplate, just in the "F150" class.
Often you have class containing all of the functionality of another class, except a bit more functionality. You can always use composition but this happens so often you're creating a lot of boilerplate.
You could develop some other "syntax sugar" to replace inheritance. Maybe Haskell's type-classes are better (although they also kind of use inheritance, since there are subclasses). But chances are you'll go back to something like inheritance, because it's very useful very often.