On Thursday, 22 October 2015 at 16:13:09 UTC, Idan Arye wrote:
On Wednesday, 21 October 2015 at 14:17:15 UTC, Jeffery wrote:
*snip*
I think you are looking at it wrong. Object composition should
either be public or private. If it's public, it should be
perfectly fine for user code to be fully aware that a `Yours`
has a `Mine` named `m`. Or, if we look at a more concrete
example:
class Name {
public string first;
public string last;
}
class Person {
public Name name;
}
I see no harm done in user code calling `person.name.first`,
because a `Name` is just a type, just like `string` or `int` -
the only difference is that `Name` is user defined.
In these cases, I find it ridiculous for a library wrap all the
functionality of another library instead of just passing it on.
A real life example: Java used to have a really crappy datetime
module in it's standard library(Java 8 got an improved datetime
module). Someone made a third party datetime library called
"Joda Time"(http://www.joda.org/joda-time/), which is
considerably better, and many libraries require it. If these
libraries followed your rule, they would have to wrap the
functions of library, so instead of
`someEvent.getTime().getMonth()` they'll have to implement in
the `Event` class a `getMonth` method so you could
`someEvent.getMonth()`, and the same with all the methods in
the rich interface provided by Joda Time. Does that seem
reasonable to you?
Now, while it's true that the fact that it comes from a third
party library may make it more prune to bugs and breaking
changes(which I assume are what you mean by "flaws"), these
flaws don't really grow exponentially on the chain of
indirection. The flaw *potential* might grow exponentially,
since the number of possible chains can grow exponentially, but
the number of chains actually used if far smaller!
By insisting on a single level of indirection, you are actually
making things worse:
- Since you need to wrap methods for the user code, you
needlessly materialize many possible chains into existence,
triggering many possible flaws that no one would have to deal
with otherwise.
- You make it impossible for users to deal with flaws in
libraries you depend on. Even if your library should not be
affected by that flaw, your users now depend on you to deal
with it even if they should have been able to deal with it
themselves.
So, a public composition is a public dependency and should not
be hidden by the Law of Demeter. How about private composition?
If a composition is private, you should not be able to access
it via `y.m.foo()` - but not because it's too long an
indirection chain, but because it's a private member field! The
outside world should not care that `Yours` has a `Mine` named
`m` - this composition is supposed to be encapsulated.
The thing is - just like automatically defining getters and
setters for all member fields breaks encapsulation, so does
automatically defining proxy wrappers for all the methods of
the member field. It might solve other problems(like lifetime
and ownership problems), but it will not achieve the basic
purposes of encapsulation, like allowing you to change the
internal fields without affecting users of the outer object. If
you change a method of the internal object, the methods of the
outer object will also change.
So, this type of wrapping is not good here either. `Yours`
shouldn't just have a method for invoking `m`'s `foo()`. If
`Yours` has a functionality that requires invoking `m.foo()`,
the implementation of that functionality can call `m.foo()`
directly. Otherwise, there is no reason for any method of
`Yours` to call `m.foo()` - certainly not as automatic,
thoughtless means to allow users - that shouldn't even be aware
of `m`'s existence - to have access to it.
I'll have to re-read this again when I get some more time but it
seems your main argument is the work required to "wrap".(your
Joda example) (I don't see how wrapping breaks encapsulation, in
fact, it adds another layer of encapsulation, which isn't
breaking it, is it?)
The work argument was my whole point though. If the compiler
internally wrapped all unwrapped members(easy to do as it is just
a simple forwarding proxy) and D itself can do this with just a
few lines of code and opDispatch, there is little work the
programmer actually has to do.
One could talk about the performance hit of all the wrapping
indirections, but I imagine these could all be optimized away.
Ultimately the issue is with the human element as a perfectly
intelligent with infinite memory and speed could manage all the
dependency issues easy.
The issue about private wrapping is moot as I mentioned about all
members being public in the examples. I should have stated that
in general. Obviously wrapping private members wouldn't render
the "private" meaningless.
I'm simply talking about reducing many levels of indirection to
one or two by allowing the compiler/language do deal with the
internals for us.
In a since, all public interface methods and field's methods are
implemented automatically and, by default, are proxies. It
probably sums up what I'm talking about if not a bit terse.
(everything public)
class X
{
void foo();
}
class Y
{
X x;
}
in this case, to access foo through Y we have two levels of
indirection:
y.x.foo();
This creates dependencies. What if X is external? foo is
changed(e.g., renamed)? Now everything that depends on y and foo
will be broken(e.g., a.b.y.x.foo())! As we add more layers(more
complexity) it becomes unmanageable. This is the standard state
of modern programming. Refectoring is king but all in the name of
fixing dependencies.
Instead, what if we have
class Y
{
private X x;
void foo() { x.foo(); }
}
In this case, Anything nothing on outside see's x. Hence there
are no dependencies on X!! X can change in any way and only Y
will be broken. We fix Y, we've fixed all the dependencies on Y!
(which, only works well, if all dependencies do the same thing we
have done above)
Hence
class B
{
private Y y;
void foo() { y. foo(); }
}
A doesn't know about Y, only about B. So fixing Y also fixes A
and B!
Of course, we can only have one level of indirection using this
technique. a.b, b.y, y.x, *not* a.b.y.x.
It's very similar to the chain analogy. In a single connection
chain. If one link breaks we only have to fix it with the two
adjacent ones. In chain mail. If one link breaks, we have to fix
multiple ones(e.g., 4). In the real world, it's like having 3D's
of chain mail(instead of 2).
The only real downside I can see is identifier explosion. (if Y
has to wrap all the methods we will surely have other issues to
deal with)
Anyways, I'll be back! ;)