Less APIs or more encapsulation?

  • Thread starter 一首诗
  • Start date
Ä

一首诗

2 class, B contains C. When user want to use some service of C,
there are two choice:

First, more encapsulation:

=============================
class B:
def newMethod(self):
self.c.newMethod()

class C:
def newMethod(self):

#do something
pass

b.newMethod()


==============================

Sencond : Call seice of f c directly:


=============================

class B:
pass

class C:
def newMethod(self):
# somethining
pass

b.c.newMethod()

=============================


Generally, what I learned from books told me that 1st choice is
better.

But when C has many many methods to expose to outer user, 2nd choice
seems to be more reasonable I In the first design, B.newMethod did
nothing really useful.

ctaully, , there are D/E/F, etc. in B we methodhod has to be exposed
to user code, which makes encapsulation more tedious.


In fact, these classes, C/D/E/F all have different jobs, but they all
belongs to a real world object "B", B is only a container of C/D/E/F.

What do you think about it?
 
D

Diez B. Roggisch

一首诗 said:
2 class, B contains C. When user want to use some service of C,
there are two choice:

First, more encapsulation:

=============================
class B:
def newMethod(self):
self.c.newMethod()

class C:
def newMethod(self):

#do something
pass

b.newMethod()


==============================

Sencond : Call seice of f c directly:


=============================

class B:
pass

class C:
def newMethod(self):
# somethining
pass

b.c.newMethod()

=============================


Generally, what I learned from books told me that 1st choice is
better.

But when C has many many methods to expose to outer user, 2nd choice
seems to be more reasonable I In the first design, B.newMethod did
nothing really useful.

ctaully, , there are D/E/F, etc. in B we methodhod has to be exposed
to user code, which makes encapsulation more tedious.


In fact, these classes, C/D/E/F all have different jobs, but they all
belongs to a real world object "B", B is only a container of C/D/E/F.

What do you think about it?

This is a problem the Law of Demeter is concerned with:

http://en.wikipedia.org/wiki/Law_of_Demeter

Generally speaking, you should favor solution a over b. If you really have
so many instances that expose their services through a container, you might
consider creating code that makes declaration of pure delegates easier.

Or maybe even redesign your code - if it would be ok for other code to work
like this:

work_with_c(b.c)

without intermittently referring to b, I think it's ok. If not, you should
delegate, because then functionality obviously depends on some new
behavior.

Diez
 
T

Tycho Andersen

But when C has many many methods to expose to outer user, 2nd choice
seems to be more reasonable I In the first design, B.newMethod did
nothing really useful.

Is there any reason you can't do something like the following?

class B(object):
def __init__(self, c):
self.__c = c;
def __getattr__(self, name):
return self.__c.__getattribute__(name)

class C(object):
def sayHi(self):
print "c says hi"

So that when you call B and it doesn't have the attribute, it looks at
it's instance of C?

\t
 
B

Bruno Desthuilliers

Tycho Andersen a écrit :
Is there any reason you can't do something like the following?

class B(object):
def __init__(self, c):
self.__c = c;

There are very few real use case for the name-mangling '__name' scheme,
and this is probably not one. A single leading underscore should be enough.
def __getattr__(self, name):
return self.__c.__getattribute__(name)

__magic_methods__ are implementation support for operators and
operator-like generic functions (len() etc). The good practice is to use
the operator or generic function, not to directly call the
implementation __method__.

Also, since it's about encapsulation, it would be better to also hide
the delegation:

def __getattr__(self, name):
try:
return getattr(self._c, name)
except AttributeError:
msg = "'%s' object as no attribute '%s'"
raise AttributeError(msg % (type(self), name)
 
B

Bruno Desthuilliers

一首诗 a écrit :
2 class, B contains C.

Given your code snippet, I assume you meant that B instances have an
instance of C as attribute.
When user want to use some service of C,

s/C/instances of C/


Python's classes are objects by themselves, so it's really important to
make clear whether you're talking about the class object or instances of
the class.
there are two choice:

First, more encapsulation:

=============================
class B:
def newMethod(self):
self.c.newMethod()

Don't expose 'c' as part of the API if you're after "more
encapsulation". Reminder : by convention (and it's a really strong
convention), attribute names starting with an underscore are
implementation ('private' if you want to stick to mainstream jargon).
class C:
def newMethod(self):

#do something
pass

b.newMethod()

yeps, composition/delegation. Classic scheme.
Sencond : Call seice of f c directly: (snip)
b.c.newMethod()



Generally, what I learned from books told me that 1st choice is
better.

I _usually_ is, unless it's B's responsability to provide access to a C
instance. But then, it's usually written as:

b = B()
c = b.get_c()
c.some_method()

This scheme is usually found when there's a need to connect to an
external resource. A canonical example can be found the DB API, where
you first open a connection to the database, then ask the connection for
one (or more) cursor(s).
But when C has many many methods to expose to outer user, 2nd choice
seems to be more reasonable

Until you need to modify your implementation, or to have some code
around the calls to methods of c, etc... The (potential) problem with
the second scheme is that it may expose a bit too much of the
implementation.

Hopefully, this may not be _that_ tragic with Python, since you can
easily replace a direct attribute access with a computed one without
impacting the client code - but not without paying some overhead (which
FWIW you would have from the start using proper delegation).
I In the first design, B.newMethod did
nothing really useful.

Nope, or at least not directly. But then, why use the obj.method()
syntax when C.method(c) would work too ?-)

More seriously: in lower-level, more static languages where you don't
have the hand on attribute lookup, getting "proper encapsulation" right
from the start is _really_ important. Python is more forgiving here
since you have ways to customize attribute resolution. *But* it doesn't
mean you should not pay attention to proper encapsulation.
ctaully, , there are D/E/F, etc. in B we methodhod has to be exposed
to user code, which makes encapsulation more tedious.

In most languages, yeps. Good news, Python exposes enough of it's
implementation to allow easy automation of the delegation. Read about
the __getattr__ magic method.

Also and FWIW, writing dummy getters and setters for a dozen attributes
is just as boring, and that's still the commonly accepted "best
practice" in Java, C++ and most other mainstream "OO" languages.
In fact, these classes, C/D/E/F all have different jobs, but they all
belongs to a real world object "B", B is only a container of C/D/E/F.

What is a "real world" object ?

And if B is only here to provide (almost direct) access to instances of
C/D/E/F, do you really need B ? (open question - you may actually need
this level of indirection for pretty good and legitimate reasons... but
one can't tell without knowing way more about your concrete use case)
What do you think about it?

There are GoldenRules(tm) and BestPractices(tm). And then there's
concrete application of all these things, and that's where the fun begin.

Blindly applying GoldenRules and BestPractices would border on cargo
cult thinking. The real point behind GoldenRules etc is to draw your
attention to known problems and the possible drawbacks of some
conception/implementation choices, so you can make use of your own
*informed* judgement wrt/ the concrete problem you're trying to solve.

IOW : there's no silver bullet, just a set of sound advices based on
experience. Then it's up to you to decide which solution you think is
the more appropriate here and now.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top