Re: Abstract public member variales?

This is a discussion on Re: Abstract public member variales? within the Theory and Concepts forums in category; Responding to Guild... >>You don't have to use a different subsystem (as I suggested later >>in the message, though, that is usually a very good idea). All >>you have to do is avoid composition where one puts all the >>responsibilities in a single object. You can easily provide a >>object that is a surrogate object for the Xxxx-able service and >>let it extract the attribute values it needs. >> >>Note that such an object is essentially a sort of factory object >>in reverse. If you employ factory objects to instantiate the >>object and initialize the attribute values (which is a ...

Go Back   Application Development Forum > Theory and Concepts

Object Mix

Register FAQ Calendar Search Today's Posts Mark Forums Read
  #1  
Old 09-09-2006, 09:49 PM
H. S. Lahman
Guest
 
Default Re: Abstract public member variales?

Responding to Guild...

>>You don't have to use a different subsystem (as I suggested later
>>in the message, though, that is usually a very good idea). All
>>you have to do is avoid composition where one puts all the
>>responsibilities in a single object. You can easily provide a
>>object that is a surrogate object for the Xxxx-able service and
>>let it extract the attribute values it needs.
>>
>>Note that such an object is essentially a sort of factory object
>>in reverse. If you employ factory objects to instantiate the
>>object and initialize the attribute values (which is a real good
>>OO practice), surely you can use another object to extract the
>>attribute value for persistence, display, etc. It just doesn't
>>look as elegant.

>
>
> I am not sure of that at all. I like to make heavy use of composition
> and polymorphism in the factory for my objects. I feel that gives me
> great flexibility and simplicity in providing a solution for the
> problem in the domain. For example, the Decorator pattern and the
> Composite pattern.
>
> I cannot see a way to do a reverse factory for such objects, short of
> many many dynamic casts, but I have no doubt that is not what you
> were thinking of.


What does a factory object do? It creates the object and then writes
values to initialize the attributes, which includes addresses for
referential attributes to instantiate <unconditional> relationships.
The OOPL makes that convenient by combining object creation with
initialization in a single constructor syntax element. One might model
that in OOA/D via:

* invokes 1 1 creates *
[Client] -------------- [ServiceFactory] ---------------- [Service]

What do you need to do to persist an object? You need to read its
attributes, convert referential attributes to identity, and write them
to the persistence mechanism. That is pretty symmetric with a
constructor except you don't delete the object when done and there is no
convenient language construct like a constructor. One might model that
in OOA/D via:

* invokes 1 1 saves *
[Client] -------------- [ServicePersistor] -------------- [Service]

To obtain the values to save the [ServicePersistor] method navigates the
relationship to the [Service] object and reads its attributes just like
any other collaboration. Converting references to object identifiers is
trickier but it is essentially the same activity you would have to do in
the protocol method in [Service] if [Service] was composed from
[Persistable]. Then the [ServicePersistor] method writes the values to
to the stream file. IOW, [ServicePersistor] has exactly the same code
in it that would have been in the composed [Service] protocol methods.
All you need to do is move it to another object.

The [ServicePersistor] approach has two clear advantages. One is that
the persistence is completely encapsulated so that there is no chance
that [Service] will be affected by any of the activities. So to change
persistence mechanisms all you need to do is substitute
[ServicePersistor] implementations without touching anything else.

The second advantage is that if the problem solution needs multiple
objects to be saved, you can provide a single [ServicePersistor]
instance to do that. Then the referential attribute conversions and
persistence access are only done once rather than repeated in every
object to be saved. (Multiple objects still need to be read, though.)

A corollary to the second advantage is that you don't have to save every
object. The Serializer approach is intended for saving /all/ the
objects. That's because it converts referential attributes literally.
Since every object in the application will be connected by referential
attributes (or collection objects containing references), you have to
save every object. Otherwise when the data store is read back you would
have dangling referential attributes.

Superficially it might seem like in a game saving everything is exactly
what you want to do when the user wants to take a sleep break. But in
reality that might be quite inefficient. That's because a lot of
objects are likely to already be stored on an as-needed basis for
particular contexts (e.g., artifacts in a particular room). Such groups
of objects need to be loaded as a group and saved as a group, but not
necessarily with everything else. (And most likely they don't have to
be saved at all.) So you will already have custom code for setting up
relationships with your other critters when they are read in. So you
don't need to save those relationships at all.

Of course you can deal with that in the Serializer protocol methods by
not convert converting and saving certain relationships. Then you can
isolate groups of objects for saving and ignore the rest. But wouldn't
it be safer and more convenient to encapsulate all those rules in a
single SaveRoom object?

>>You are correct that one way or another one has to implement the
>>same requirements.

>
>
> I am not correct unless one has to implement the same requirements
> within the classes of the objects that have the requirements. An
> object that needs to persist must implement persistence, one way or
> another. A persistence subsystem will go a very long way to turning a
> nightmarishly complicated job of persistence into a simple matter,
> but it will not allow me to get away without implementing even the
> most basic persistence within the persistent object.


A persistence subsystem completely decouples the persistence mechanisms
from other subsystems. What one needs in the problem solution is an
object that understands the context of What and When stuff needs to be
saved. That object just reads the attributes, constructs a data packet
with their values, and sends a message to the persistence subsystem.

That object serves exactly the same function as a factory object that
reads data from an external data store and instantiates objects. When
the context for saving prevails, it is invoked just like a factory
object is invoked when the context for instantiation prevails. It is
just going in the other direction, so let's call it an "interface
object" in deference to Ivar Jacobson.

[BTW, note that the persistence subsystem can manage the reference
conversion if you want to get somewhat exotic. All you need is a lookup
table that maps address with object identity. When a constructor is
invoked on the problem side, it can send its own message to the
persistence subsystem with the {address, identity} pair to update that
table.]

>>I submit, though, that the application will be much more robust in
>>the face of volatile requirements (including during the initial
>>development) if one avoids complex objects with disparate
>>properties. IME, the solutions to two small problems will almost
>>always be simpler in tot than the solution to a single larger
>>problem. [That's one way or another. Sometimes the gain will be
>>in performance (e.g., fewer condition checks); sometimes in size
>>(better invariants); sometimes just easier to grok.]

>
>
> I agree with all of this. I can see that our goals in good design are
> the same, but you know how to achieve them while I do not.


I suspect you are overthinking the solution because the actual
implementation is too simple. B-) All you need is an object that has
persistence responsibilities instead of instantiation responsibilities.
Composition effectively combines that object with the object being
persisted. I am just arguing that it is better to keep them separated
in an OO context.

>>>If the Honda Accord is not Streamable or at the very least
>>>Persistent, then it might not have the interface I need to feed
>>>it to the persistence subsystem. It might actually have hidden
>>>state that makes it completely impossible to make it persistent.

>>
>>I'm afraid that argument doesn't fly. Persistence represents a
>>suite of requirements on the solution that make the object's state
>>important to an external entity (the database). Therefore any
>>state that needs to be persisted is not private with respect to
>>persistence. The fact that the composition merges the protocol
>>from another object into the implementation of the object in hand
>>doesn't change that; one is just making the object more complex to
>>hide the collaboration.
>>
>>This is no different than the discussion in the other post about
>>limiting access to certain properties. So long as one external
>>client needs access, the state is public and your only choice is
>>to limit the access of everyone else to it.

>
>
> There needs to be more explanation of how to make state public. Of
> course, it is not as simple as replacing a 'private' keyword with
> 'public'. When I am using polymorphism I have many different objects
> from many different classes all working under one common interface.
> Each object has its own state that it needs to perform its role in
> the problem.


I think public vs. private is something of a red herring. That is
primarily an OOP issue when dealing with 3GL type systems. For example,
in the UML MDA profile I use for OOA modeling there is no public or
private designation. The context of dynamic access speaks for itself.
(There are no getter/setter accessors either because the attributes are
defined as ADTs so the implementation of access is deferred to OOP.) So...

>
> If I ignore persistence for a moment, there is no reason to provide
> accessors to get at the state of each object in the common interface.
> Since the state will have a different form from object to object, it
> would require many accessors and much thinking to see how the
> heterogeneous collection of states could be accessed homogeneously.
> Fortunately, I do not have to make that state public and that is a
> huge simplification on the common interface.


That's fine. The requirements do not require that all attributes be
exposed externally and you can enforce that with multiple interfaces
that control access. My point in this context is that persistence
/does/ introduce requirements that all attributes be exposed externally.
So if you want to restrict access _in the solution_ you can still do
that by providing multiple interfaces (or the C++ friend kludge) the
same way you would do it for the non-persistence case.

IOW, once persistence enters the picture one is in an Orwellian
situation where all attributes are public but some are more public than
others. One limits access to the less public ones the same way one
would limit access to private ones: by providing an interface to suit
the context.

>
> I think I understand what you mean when you say that because I want
> persistence I need to make the state of all my relevant objects
> public, but when I see how daunting the task of actually doing it is,
> I wonder if there is some extra trick that you have not explicitly
> mentioned.
>
> For example, if I could keep a separate data structure that somehow
> parallels the structure of the domain but avoids polymorphism to keep
> direct access to the state of all objects at all times, then I could
> easily make the state public for persistence purposes.


I don't see it as a polymorphic issue. It is simply a matter of
limiting access to attributes and one does that by providing multiple
interfaces. That, of course, gets a little tricky in an OOPL that does
not support multiple interfaces for an object. Then one needs to
provide infrastructure like privilege bit strings to emulate different
interface rights (or the C++ friend). If that is the issue, then I
would suggest seriously considering switching OOPLs. B-)

[One could make it a polymorphic issue in a brain dead OOPL by using an
artificial generalization structure to define various levels of
interfaces and then "choosing the interface" by the level of access in
the tree. But that will tend to become combinatorial.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl{}pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info{}pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



Reply With Quote
  #2  
Old 09-10-2006, 12:32 AM
Brendan Guild
Guest
 
Default Re: Abstract public member variales?

H. S. Lahman wrote in news:laKMg.904$FS.903{}trnddc04:

> Responding to Guild...
>
>> I agree with all of this. I can see that our goals in good design
>> are the same, but you know how to achieve them while I do not.

>
> I suspect you are overthinking the solution because the actual
> implementation is too simple. B-) All you need is an object that
> has persistence responsibilities instead of instantiation
> responsibilities.


Unfortunately, I suspect that you are overestimating me in some way.
Most solutions are simple once you see them, but they are sometimes
harder to see than you expect.

I may have a solution that allows me to do persistence in the way you
describe, but it is not simple and it has roots that sink into the
rest of the design in ways that I have not thoroughly investigated,
complicating all of the design in unforeseen ways much like garbage
collection complicates software using manual memory management. I
refered to it towards the bottom of the post you quoted, but I am
sure the technique you intend is simpler so I will not go into it any
further.

> Composition effectively combines that object with the object
> being persisted. I am just arguing that it is better to keep them
> separated in an OO context.


I appreciate that argument and I itch to put the advice it contains
into practice.

I had hoped that I had made my difficulty clear in my previous post
because I am very eager to discover the trick that I am missing, but
I can see that I was not clear. I will not make that mistake again,
so I will bring in an example from GoF to help me.

The Composite pattern allows factories to build complex objects out
of simple components, and vast objects from merely complex ones.

Drawing software might use this to create pictures out of simple
geometric components something like this:

* contains
[Graphic]---------------------+
A |
| | R1
+------+-+-------+-------+ |
| | | | 1 |
[Line][Rectangle][Text][Picture]----+

The Graphic class represents both primitive drawing objects and
collections of drawing objects so that more and more complex objects
can be constructed out of objects from only these few classes.

Each class contains whatever it needs to draw the appropriate image
and to be friendly to persistence, it also provides accessors to
everything. The Picture objects each contain a collection of Graphics
that it would not normally need to offer to the client, but for
persistence it will expose R1.

The Graphic class has a pure virtual method that each subclass
overrides to do that drawing. So you can see that with one method
call any complicated image can be drawn, and the trick is simple
enough: The implementation of the 'draw' method in Picture navigates
R1 to call the draw method of each graphic.

It is just that easy to do the drawing, but it is not so easy to do
the persistence, despite the similarities between the operations and
the fact that every object is completely open with everything it
contains.

It is time to encode a Graphic object for the persistence layer. I
have the Graphics object in the form of a reference. As I understand
it, the immediate next step should be obvious and simple.

Here is what I resort to for lack of a better idea:

I do dynamic casts on that reference, once for each possible class
that derives from Graphic. If I find a Picture object, I navigate R1
and repeat the process. If I find one of the primitives, I extract
the details easily and write them.

If I did not know better, I would swear that this is what you would
want me to do. I am expect that what I actually should do is
something so obvious that it has not been mentioned yet, but I will
still comb through everything that has been said in hope that I am
wrong.

I have never seen a persistence technique that does not either
resemble Serializer or resort to something as crude as dynamic casts.

>> If I ignore persistence for a moment, there is no reason to
>> provide accessors to get at the state of each object in the
>> common interface. Since the state will have a different form from
>> object to object, it would require many accessors and much
>> thinking to see how the heterogeneous collection of states could
>> be accessed homogeneously. Fortunately, I do not have to make
>> that state public and that is a huge simplification on the common
>> interface.

>
> That's fine. The requirements do not require that all attributes
> be exposed externally and you can enforce that with multiple
> interfaces that control access. My point in this context is that
> persistence /does/ introduce requirements that all attributes be
> exposed externally.
> So if you want to restrict access _in the solution_ you can still
> do that by providing multiple interfaces (or the C++ friend kludge)
> the same way you would do it for the non-persistence case.


I can see now how poorly I was expression my thoughts. I would be
very happy to not restrict access at all. I would expose every last
implementation detail that I could to every aspect of my design if I
thought it would in some way make doing persistence easier.

I did not mean to suggest that I do not want to make things public
because of how it might interfere with maintenance or any of the
usual reasons for not making state public. I was trying to say that
the very act of making state public was a task that is beyond my
abilities.

For example, how would I make the end-points of a Line public in the
Graphic example? Naturally, the attributes themselves have no access
restrictions in the definition of the Line class, but that does not
make the end-points any more accessible to someone with a reference
to a Graphic object. I would need some sort of accessors in the
Graphic interface and they would have to be meaningful not only for
Lines, but for Text as well and any unforeseen subclass of Graphic.

I hope you see the difficulty that I am facing.

I await your reply on the edge of my seat, because if it contains a
simple solution to this issue it will be revolutionary for me. Until
now I had assumed that Serializer of something like it was the best
that there was!
Reply With Quote
  #3  
Old 09-10-2006, 02:41 PM
H. S. Lahman
Guest
 
Default Re: Abstract public member variales?

Responding to Guild...

> Drawing software might use this to create pictures out of simple
> geometric components something like this:
>
> * contains
> [Graphic]---------------------+
> A |
> | | R1
> +------+-+-------+-------+ |
> | | | | 1 |
> [Line][Rectangle][Text][Picture]----+
>
> <snip>
>
> Here is what I resort to for lack of a better idea:
>
> I do dynamic casts on that reference, once for each possible class
> that derives from Graphic. If I find a Picture object, I navigate R1
> and repeat the process. If I find one of the primitives, I extract
> the details easily and write them.


OK, I understand the problem you want addressed. BUt first let's look
at what a single factory object does when construction a Composite tree.
Let's assume it is reading from something like an XML hierarchical
text specification and it needs to create the individual objects and
relationships.

To do that it accepts an element specification from the text file and
parses it. In so doing the element is identified as, say, a Rectangle.
The factory creates a Rectangle object using the proper constructor,
initializes it with the data values from the file, and instantiates the
R1 relationship because it was smart enough to keep track of the Picture
it is decomposing.

To do that the factory objects needs to know two things about what it is
building: the what the current object is (a Rectangle) and it must keep
track of Pictures separately. That understanding of what the element in
hand is irrelevant to navigating the tree during collaborations in the
problem solution because the semantics is accessed polymorphically. But
to /construct/ the tree the factory must understand what it is
constructing (i.e., to invoke the proper constructor).

I submit that writing the Composite tree back out to persistence is a
symmetrical operation with the factory (deconstruction, if you will).
To do that it must provide text-based identity for the elements. So the
deconstructor needs to understand what the element is. I'm sure you are
with me so far; I'm just making the point that deconstructing the
Composite is orthogonal to the way the Composite is navigated in the
problem solution. So...

>
> If I did not know better, I would swear that this is what you would
> want me to do. I am expect that what I actually should do is
> something so obvious that it has not been mentioned yet, but I will
> still comb through everything that has been said in hope that I am
> wrong.
>
> I have never seen a persistence technique that does not either
> resemble Serializer or resort to something as crude as dynamic casts.


Right. If you don't use Serializer composition that allows you to
"walk" the structure object-by-object using polymorphic access, you need
to know what you are deconstructing. Just as a factory object needs to
know what the object is to invoke the correct constructor, the
persistence object needs to know what the object is to invoke the right
deconstruction method.

Note that in this context the Composite is essentially an arbitrary
stream of objects that must be processed by the persistence object. As
I mentioned early, most OOPLs have no elegant mechanism for dealing with
such streams. So one is at the mercy of kludges like _dynamic_cast,
which can easily be abused.

The key thing to remember here is that this sort of deconstruction is
orthogonal to the reason you have the Composite in the first place.
That is, it has nothing to do with the problem the application is
solving. (More precisely, the relationship is quite indirect; there are
probably persistence requirements that need to be resolved.) What you
are really doing is processing a stream of arbitrary objects and you
need to dispatch to the correct processing for each object.

In that context it is fair to look at the type of the object, just as
the factory object looks at the type of the object in its input stream
to decide what constructor to invoke. So this is a context (object
stream processing) where _dynamic_cast is justified -- simply because
the language doesn't provide anything better.

[As it happens, I would probably opt to embed the text file identity in
the Composite objects when they are built and dispatch on that. There
are a couple of advantages. If one is clever about identity, it will be
more efficient because one can do a single table lookup for the
dispatch. Much more important, though, is that it provides a cleaner
mechanism for synchronization. Now the identity mapping for what to
build/process is synchronized through the persistence data store.]

I would also point out is there there is a dispatch to the right
processing for persistence of a particular object in both Serializer and
my "deconstructor" approach. In Serializer it is elegantly polymorphic
because a common interface to the grunt code was provided. In fact, you
can do exactly the same thing by providing the same common interface for
the deconstruction methods:

* parses R1 1
[ObjectA] --------------------- [Deconstructor]
+ saveIt()
A
| R2
+----------------+-----------...
| |
[ConcreteA] [ConcreteB]

When you read the object type you can instantiate the R1 relationship so
that ObjectA is processed by the ConcreteA deconstructor when saveIt()
is invoked. Essentially what you have done is moved the polymorphic
dispatch from the object to be saved into a GoF Strategy pattern.

So the difference comes down to how one identifies the object in hand
and the indirection of the R1 polymorphic dispatch. For the reasons
above I don't see the identity check as a major problem because of the
orthogonal context to the problem solution. Overall I think it is a
pretty small price to pay for better cohesion, separation of concerns,
and isolation of persistence mechanisms.

>>>If I ignore persistence for a moment, there is no reason to
>>>provide accessors to get at the state of each object in the
>>>common interface. Since the state will have a different form from
>>>object to object, it would require many accessors and much
>>>thinking to see how the heterogeneous collection of states could
>>>be accessed homogeneously. Fortunately, I do not have to make
>>>that state public and that is a huge simplification on the common
>>>interface.

>>
>>That's fine. The requirements do not require that all attributes
>>be exposed externally and you can enforce that with multiple
>>interfaces that control access. My point in this context is that
>>persistence /does/ introduce requirements that all attributes be
>>exposed externally.
>> So if you want to restrict access _in the solution_ you can still
>>do that by providing multiple interfaces (or the C++ friend kludge)
>>the same way you would do it for the non-persistence case.

>
>
> I can see now how poorly I was expression my thoughts. I would be
> very happy to not restrict access at all. I would expose every last
> implementation detail that I could to every aspect of my design if I
> thought it would in some way make doing persistence easier.
>
> I did not mean to suggest that I do not want to make things public
> because of how it might interfere with maintenance or any of the
> usual reasons for not making state public. I was trying to say that
> the very act of making state public was a task that is beyond my
> abilities.
>
> For example, how would I make the end-points of a Line public in the
> Graphic example? Naturally, the attributes themselves have no access
> restrictions in the definition of the Line class, but that does not
> make the end-points any more accessible to someone with a reference
> to a Graphic object. I would need some sort of accessors in the
> Graphic interface and they would have to be meaningful not only for
> Lines, but for Text as well and any unforeseen subclass of Graphic.


Ah. Let's assume the Strategy implementation in my example above.
ObjectA has three attributes: Attr1, Attr2, and Attr3. In the
Serializer there is no problem because saveIt() has access to all member
attributes. However, ConcreteA can only access the attributes for which
there are accessors in the interface. (Let's ignore things like making
ConcreteA a friend of ObjectA in C++ because that's pretty unique.)

My point here was simply that persistence creates a need-to-know outside
ObjectA. Therefore, one needs access to all the attributes. That
access is cleverly hidden in Serializer, but it exists nonetheless.
Therefore you need to provide read access in the interface of ObjectA.
So you Just Do It.

However, if there was a reason why they needed to be private with
respect to other clients in the problem solution, you need to deal with
that as well. One way is to restrict access to the now "public"
attributes that solution objects shouldn't access via multiple
interfaces, etc..

But apropos of my comments in my other post here today, I am not
convinced you really need to worry about that most of the time. For one
thing, all you need to expose is getter access since the Deconstructor
doesn't need to modify the attributes. It is hard to justify preventing
read access unless there are explicit problem requirements like salary
information for employees.

As I reasoned in the other post, I think information hiding is overrated
except when explicit requirement restrictions on access exist and then I
would already have put "hard" mechanisms in place. IOW, I usually
provide read access of all attributes as a matter of course in defining
interfaces unless there are specific requirements to the contrary.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl{}pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info{}pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



Reply With Quote
  #4  
Old 09-10-2006, 09:08 PM
Brendan Guild
Guest
 
Default Re: Abstract public member variales?

H. S. Lahman wrote in news:4%YMg.5654$xC3.2846{}trnddc06:

> Responding to Guild...
>
> In that context it is fair to look at the type of the object, just
> as the factory object looks at the type of the object in its input
> stream to decide what constructor to invoke. So this is a context
> (object stream processing) where _dynamic_cast is justified --
> simply because the language doesn't provide anything better.


It seems counter-intuitive that using dynamic cast would be superior
to the Serializer pattern, but I suppose it makes sense!

> I would also point out is there there is a dispatch to the right
> processing for persistence of a particular object in both
> Serializer and my "deconstructor" approach. In Serializer it is
> elegantly polymorphic because a common interface to the grunt code
> was provided. In fact, you can do exactly the same thing by
> providing the same common interface for the deconstruction
> methods:
>
> * parses R1 1
> [ObjectA] --------------------- [Deconstructor]
> + saveIt()
> A
> | R2
> +----------------+-----------...
> | |
> [ConcreteA] [ConcreteB]
>
> When you read the object type you can instantiate the R1
> relationship so that ObjectA is processed by the ConcreteA
> deconstructor when saveIt() is invoked. Essentially what you have
> done is moved the polymorphic dispatch from the object to be saved
> into a GoF Strategy pattern.


This is something I had not thought of. So each persistence object
contains a reference to a strategy object for encoding itself for the
persistence subsystem. That very nicely does the job without any
dynamic casts, though the relationship R1 is perhaps unfortunate.

Similar relations would of course be present for similar tasks, such
as displaying. And there will always be classes of objects beyond my
control, so for them I might to do something like this:

1 R1 contains *
[MyObject] --------------------- [TheirObject]
| 1 | *
| R2 * | 1
+---------------------------- [Deconstructor]

Where objects of class MyObject is in a relationship with objects of
class TheirObject, so when it comes time to encode MyObject, I need
to encode all the related TheirObjects. Since I do not control
TheirObject, I cannot install a strategy inside TheirObject, but I
can use the R2 relationship to find all the relevant Dconstructors.

> As I reasoned in the other post, I think information hiding is
> overrated except when explicit requirement restrictions on access
> exist and then I would already have put "hard" mechanisms in
> place. IOW, I usually provide read access of all attributes as a
> matter of course in defining interfaces unless there are specific
> requirements to the contrary.


Information hiding is also useful for proving correctness. Some
objects have much more complicated internal state than the external
state, and one great benefit of it is that you can be sure that the
behaviour of other objects will not depend on the complicated
internal state, only on the simple external state.

That could save a lot of time in black-box testing, for example, and
it just allows for fewer possible interactions between objects which
makes thinking about the possible ways for things to go wrong easier.

Naturally, you do not really give access to all attributes, only
those which are conceptually part of the object. For example, if you
were implementing a String object you would conceptually have a
'length' attribute and a 'charAt' index for getting the characters.
You can imagine that on the inside the string might be implement
using a null-terminator, so there is no actual 'length' attribute,
but there would be a 'length' accessor in the interface even so. Even
more strange, there could be an identifier attribute for the null-
terminator so that the actual value of 'null' might vary from object
to object, but that would surely not have an accessor. What the
interface suggests about the implementation and the actual
implementation can be quite different.
Reply With Quote
  #5  
Old 09-11-2006, 05:14 PM
H. S. Lahman
Guest
 
Default Re: Abstract public member variales?

Responding to Guild...

>>In that context it is fair to look at the type of the object, just
>>as the factory object looks at the type of the object in its input
>>stream to decide what constructor to invoke. So this is a context
>>(object stream processing) where _dynamic_cast is justified --
>>simply because the language doesn't provide anything better.

>
>
> It seems counter-intuitive that using dynamic cast would be superior
> to the Serializer pattern, but I suppose it makes sense!


I see it as lesser of evils. The OOPL doesn't provide an elegant way to
deal with streams of arbitrary objects vs. lack of object cohesion.

>>I would also point out is there there is a dispatch to the right
>>processing for persistence of a particular object in both
>>Serializer and my "deconstructor" approach. In Serializer it is
>>elegantly polymorphic because a common interface to the grunt code
>>was provided. In fact, you can do exactly the same thing by
>>providing the same common interface for the deconstruction
>>methods:
>>
>> * parses R1 1
>>[ObjectA] --------------------- [Deconstructor]
>> + saveIt()
>> A
>> | R2
>> +----------------+-----------...
>> | |
>> [ConcreteA] [ConcreteB]
>>
>>When you read the object type you can instantiate the R1
>>relationship so that ObjectA is processed by the ConcreteA
>>deconstructor when saveIt() is invoked. Essentially what you have
>>done is moved the polymorphic dispatch from the object to be saved
>>into a GoF Strategy pattern.

>
>
> This is something I had not thought of. So each persistence object
> contains a reference to a strategy object for encoding itself for the
> persistence subsystem. That very nicely does the job without any
> dynamic casts, though the relationship R1 is perhaps unfortunate.


Alas, you still need to know what the type is of ObjectA to assign the
R1 relationship. B-(

Also, the [Deconstructor] is essentially the persistence object that
corresponds to the problem object (i.e., the ConcreteA object has
exactly the same code to support the protocol as the local methods in a
serialized ObjectA). Some other object, which I didn't show in the
diagram, manages the overall process of saving objects. It "walks" the
objects to be saved, instantiates R1, and invokes saveIt() for each one.
But you would have a similar functionality for Serializer to "walk"
those objects.

There are variations on the theme. The [Deconstructor] above might do
nothing except read the attributes and pass them back to the caller as a
value list. Then the caller would encode the values in the persistence
format and write them out. That has the advantage that the code of
encoding the persistence format and actually writing it out is only done
once for all objects. (Of course if the Serializer implementation is
clever it will do the same thing by providing a callback for the local
methods to use.)

>
> Similar relations would of course be present for similar tasks, such
> as displaying. And there will always be classes of objects beyond my
> control, so for them I might to do something like this:
>
> 1 R1 contains *
> [MyObject] --------------------- [TheirObject]
> | 1 | *
> | R2 * | 1
> +---------------------------- [Deconstructor]
>
> Where objects of class MyObject is in a relationship with objects of
> class TheirObject, so when it comes time to encode MyObject, I need
> to encode all the related TheirObjects. Since I do not control
> TheirObject, I cannot install a strategy inside TheirObject, but I
> can use the R2 relationship to find all the relevant Dconstructors.


Yes, display works pretty much the same way; just a different target for
the output messages.

Actually you have pointed out an advantage I didn't think of. If you
need to save, say, third party library instances, you can't really do
that with Serializer because they didn't compose with the protocol. But
there is nothing to prevent you from using the separate deconstructor
approach because you know what the attributes are that must be read. So
you can write a [Deconstructor] for it. (Doesn't work, though, if their
instance has private state data; but then you are screwed anyway.)

>>As I reasoned in the other post, I think information hiding is
>>overrated except when explicit requirement restrictions on access
>>exist and then I would already have put "hard" mechanisms in
>>place. IOW, I usually provide read access of all attributes as a
>>matter of course in defining interfaces unless there are specific
>>requirements to the contrary.

>
>
> Information hiding is also useful for proving correctness. Some
> objects have much more complicated internal state than the external
> state, and one great benefit of it is that you can be sure that the
> behaviour of other objects will not depend on the complicated
> internal state, only on the simple external state.


I have I disagree with that. Internal state data does affect
interactions with other objects whenever the internal state results from
multiple interactions. Consider a stack. It had better have the right
items enqueued and the right internal item count before the object you
are testing starts popping from it in the context the test case assumes.
Lacking direct access to initialize that internal state properly one
often faces some tedious setup work to initialize the stack before the
test case can execute.

Unit testing is the only context where I think the C++ friend is useful
for exactly that reason. For unit test you need to access the private
state data.

> That could save a lot of time in black-box testing, for example, and
> it just allows for fewer possible interactions between objects which
> makes thinking about the possible ways for things to go wrong easier.


It makes test case specification much easier but it makes test case
implementation much harder. B-) I have seen systems where one had to
run hundreds of thousands of stimuli events just to get the system into
an internal state where a particular test case could be run. Somebody
had to figure out what stimuli were needed and then encode them in the
test case. Great fun. (It's also one of the reasons black box system
testing usually has atrocious fault coverage.)


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl{}pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info{}pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



Reply With Quote
  #6  
Old 09-11-2006, 08:56 PM
Brendan Guild
Guest
 
Default Re: Abstract public member variales?

H. S. Lahman wrote in news:KkkNg.167$cf2.119{}trndny07:

> Responding to Guild...
>>>
>>> * parses R1 1
>>>[ObjectA] --------------------- [Deconstructor]
>>> + saveIt()
>>> A
>>> | R2
>>> +----------------+-----------...
>>> | |
>>> [ConcreteA] [ConcreteB]
>>>
>>>When you read the object type you can instantiate the R1
>>>relationship so that ObjectA is processed by the ConcreteA
>>>deconstructor when saveIt() is invoked. Essentially what you
>>>have done is moved the polymorphic dispatch from the object to be
>>>saved into a GoF Strategy pattern.

>>
>>
>> This is something I had not thought of. So each persistence
>> object contains a reference to a strategy object for encoding
>> itself for the persistence subsystem. That very nicely does the
>> job without any dynamic casts, though the relationship R1 is
>> perhaps unfortunate.

>
> Alas, you still need to know what the type is of ObjectA to assign
> the R1 relationship. B-(
>
> Also, the [Deconstructor] is essentially the persistence object
> that corresponds to the problem object (i.e., the ConcreteA object
> has exactly the same code to support the protocol as the local
> methods in a serialized ObjectA). Some other object, which I
> didn't show in the diagram, manages the overall process of saving
> objects. It "walks" the objects to be saved, instantiates R1, and
> invokes saveIt() for each one.


Perhaps this is a terminology failure again, but I wonder if
'instantiates' means what I think it means. To me instantiation is a
kind of creation that makes a specific thing from a generalization,
such as a specific R1 relationship between two specific objects.

But surely there is no point in instantiating a relationship if you
know that it is only going to be used once, by saveIt(). The work I
expected you to use was 'navigates', as in: It walks the objects to
be save, navigates R1, and invokes saveIt() for each one. Surely the
factory instantiates R1, at the only time it could be done with
dynamic casts.

I has assumed that each ObjectA contained a reference to some
ConcreteA, because then the relationship could be created by the
factory and navigated by the persistence procedure.

>> Information hiding is also useful for proving correctness. Some
>> objects have much more complicated internal state than the
>> external state, and one great benefit of it is that you can be
>> sure that the behaviour of other objects will not depend on the
>> complicated internal state, only on the simple external state.

>
> I have I disagree with that. Internal state data does affect
> interactions with other objects whenever the internal state
> results from multiple interactions. Consider a stack. It had
> better have the right items enqueued and the right internal item
> count before the object you are testing starts popping from it in
> the context the test case assumes. Lacking direct access to
> initialize that internal state properly one often faces some
> tedious setup work to initialize the stack before the test case can
> execute.


That is a good point, but making the internal state of a stack
accessible does not simplify analyzing the correctness of the
program. It might make implementing the test easier, but that is an
implementation detail for 'friend' keywords and similar tricks. With
an accessible internal state you have all the same complexities, plus
a lot of new possible interactions between the stack and the client.
Reply With Quote
  #7  
Old 09-11-2006, 10:43 PM
Brendan Guild
Guest
 
Default Re: Abstract public member variales?

Forgive me, but I must try this again. I was writing a bit too fast
the first time.

H. S. Lahman wrote in news:KkkNg.167$cf2.119{}trndny07:
> Responding to Guild...
>>>
>>> * parses R1 1
>>>[ObjectA] --------------------- [Deconstructor]
>>> + saveIt()
>>> A
>>> | R2
>>> +----------------+-----------...
>>> | |
>>> [ConcreteA] [ConcreteB]
>>>
>>>When you read the object type you can instantiate the R1
>>>relationship so that ObjectA is processed by the ConcreteA
>>>deconstructor when saveIt() is invoked. Essentially what you
>>>have done is moved the polymorphic dispatch from the object to be
>>>saved into a GoF Strategy pattern.

>>
>>
>> This is something I had not thought of. So each persistence
>> object contains a reference to a strategy object for encoding
>> itself for the persistence subsystem. That very nicely does the
>> job without any dynamic casts, though the relationship R1 is
>> perhaps unfortunate.

>
> Alas, you still need to know what the type is of ObjectA to assign
> the R1 relationship. B-(
>
> Also, the [Deconstructor] is essentially the persistence object
> that corresponds to the problem object (i.e., the ConcreteA object
> has exactly the same code to support the protocol as the local
> methods in a serialized ObjectA). Some other object, which I
> didn't show in the diagram, manages the overall process of saving
> objects. It "walks" the objects to be saved, instantiates R1, and
> invokes saveIt() for each one.


Perhaps this is a terminology failure again, but I wonder if
'instantiates' means what I think it means. To me instantiation is a
kind of creation that makes a specific thing from a generalization,
such as a specific R1 relationship between two specific objects.

But surely there is no point in instantiating a relationship if you
know that it is only going to be used once, by saveIt(). The word I
expected you to use was 'navigates', as in: It walks the objects to
be saved, navigates R1, and invokes saveIt() for each one. Surely the
factory instantiates R1, at the only time it could be done without
dynamic casts.

I has assumed that each ObjectA contained a reference to some
ConcreteA, because then the relationship could be created by the
factory and navigated by the persistence procedure.

>> Information hiding is also useful for proving correctness. Some
>> objects have much more complicated internal state than the
>> external state, and one great benefit of it is that you can be
>> sure that the behaviour of other objects will not depend on the
>> complicated internal state, only on the simple external state.

>
> I have I disagree with that. Internal state data does affect
> interactions with other objects whenever the internal state
> results from multiple interactions. Consider a stack. It had
> better have the right items enqueued and the right internal item
> count before the object you are testing starts popping from it in
> the context the test case assumes. Lacking direct access to
> initialize that internal state properly one often faces some
> tedious setup work to initialize the stack before the test case can
> execute.


That is a good point, but making the internal state of a stack
accessible does not simplify analyzing the correctness of the
program. It might make implementing the test easier, but that is an
implementation detail for 'friend' keywords and similar tricks. With
an accessible internal state you have all the same complexities, plus
a lot of new possible interactions between the stack and the client.


Reply With Quote
  #8  
Old 09-12-2006, 05:06 PM
H. S. Lahman
Guest
 
Default Re: Abstract public member variales?

Responding to Guild...

>>>> * parses R1 1
>>>>[ObjectA] --------------------- [Deconstructor]
>>>> + saveIt()
>>>> A
>>>> | R2
>>>> +----------------+-----------...
>>>> | |
>>>> [ConcreteA] [ConcreteB]
>>>>
>>>>When you read the object type you can instantiate the R1
>>>>relationship so that ObjectA is processed by the ConcreteA
>>>>deconstructor when saveIt() is invoked. Essentially what you
>>>>have done is moved the polymorphic dispatch from the object to be
>>>>saved into a GoF Strategy pattern.
>>>
>>>
>>>This is something I had not thought of. So each persistence
>>>object contains a reference to a strategy object for encoding
>>>itself for the persistence subsystem. That very nicely does the
>>>job without any dynamic casts, though the relationship R1 is
>>>perhaps unfortunate.

>>
>>Alas, you still need to know what the type is of ObjectA to assign
>>the R1 relationship. B-(
>>
>>Also, the [Deconstructor] is essentially the persistence object
>>that corresponds to the problem object (i.e., the ConcreteA object
>>has exactly the same code to support the protocol as the local
>>methods in a serialized ObjectA). Some other object, which I
>>didn't show in the diagram, manages the overall process of saving
>>objects. It "walks" the objects to be saved, instantiates R1, and
>>invokes saveIt() for each one.

>
>
> Perhaps this is a terminology failure again, but I wonder if
> 'instantiates' means what I think it means. To me instantiation is a
> kind of creation that makes a specific thing from a generalization,
> such as a specific R1 relationship between two specific objects.


Yes, instantiation essentially means creation. But relationship
instantiation is different than object instantiation. To instantiate
the R1 relationship, one must have the two participating objects in hand.

The generalization in this case is the R2 relation. The relation here
is more like a type relation in that it describes how an object is
created. When one instantiates R2 one is really instantiating a single
object whose properties are resolved by the rules of inheritance (i.e.,
a union of all properties of classes in a direct line of descent from
the root superclass to a leaf subclass).

> But surely there is no point in instantiating a relationship if you
> know that it is only going to be used once, by saveIt(). The work I
> expected you to use was 'navigates', as in: It walks the objects to
> be save, navigates R1, and invokes saveIt() for each one. Surely the
> factory instantiates R1, at the only time it could be done with
> dynamic casts.


<aside for clarification>
There are three aspects to relationships: implementation, instantiation,
and navigation. There are basically three ways to implement a
relationship: (A) a referential attribute like a pointer; (B) an
explicit identity attribute, as in RDBs, combined with a search; and (C)
passing an object reference as an argument to another object's method
(that isn't a referential attribute setter). One instantiates the
relationship by, respectively: (A) assigning an object reference to the
referential attribute; (B) assigning an object identifier to the
referential attribute; or (C) providing the right object reference as an
argument. One navigates the relationship by "reading" the relationship
to get a reference and sending a message to that address.

The (B) implementation is rarely seen in OO applications because it it
inherently inefficient compared to the others in a memory resident
application. The (C) implementation is generally frowned upon because
it requires the caller to know what object the receiver needs to
collaborate with. Essentially one has:

1 R1 1 1 R2 *
[A] -------------- [b] --------------- [C]

When A passes a reference to C, A is essentially instantiating the R2
relationship temporarily. But the R2 relationship is a personal matter
between [b] and [C]. The [A] object should not even know that
relationship exists, much less who the participants should be in this
collaboration context. If you recall, I said a few messages ago that
one should encapsulate the rules an policies of instantiation away from
the rules and policies of collaboration. A needs to collaborate with B,
which is does by invoking B's method. But A doesn't need to know about
instantiating R2 to do that.
</aside for clarification>

So...

> I has assumed that each ObjectA contained a reference to some
> ConcreteA, because then the relationship could be created by the
> factory and navigated by the persistence procedure.


Not quite. Exactly the same protocol will be defined in [Deconstructor]
as is provided by Serializer. The ConcreteX methods will implement that
protocol exactly like the Serializer stubs filled in for each ObjectX.
The problem is that the single ConcreteA method applies to /every/
instance of ObjectA. In Serializer that was taken care of by the
implicit 'this' pointer. But when we delegate [Deconstructor] to a peer
object the R1 relationship is *:1 and we have to instantiate it to write
a particular ObjectA out.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl{}pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info{}pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



Reply With Quote
  #9  
Old 09-13-2006, 01:22 PM
Brendan Guild
Guest
 
Default Re: Abstract public member variales?

H. S. Lahman wrote in news:gjFNg.6716$wj2.3572{}trndny06:

> Responding to Guild...
>
>> I has assumed that each ObjectA contained a reference to some
>> ConcreteA, because then the relationship could be created by the
>> factory and navigated by the persistence procedure.

>
> Not quite. Exactly the same protocol will be defined in
> [Deconstructor] as is provided by Serializer. The ConcreteX
> methods will implement that protocol exactly like the Serializer
> stubs filled in for each ObjectX. The problem is that the single
> ConcreteA method applies to /every/ instance of ObjectA. In
> Serializer that was taken care of by the implicit 'this' pointer.
> But when we delegate [Deconstructor] to a peer object the R1
> relationship is *:1 and we have to instantiate it to write a
> particular ObjectA out.


Could you expand upon that slightly? I am not entirely sure of the
intended role of each object in the collaborations. If I understand
correctly the Deconstructor class implements methods to give
primitive values to the persistence system. The ConcreteX subclasses
inherit those methods and then use them when the call to saveIt() is
made.

Just before saveIt() is called, a relationship is instantiated to
show saveIt() what to save. You have said that instantiating
relationships by providing an object as an argument is not good
because it couples the instantiation of the relationship to the
caller of the method. But it seems as though the caller and the
instantiator will be the same in the case either way.

However, I am thinking that it would be good idea to have multiple
ConcreteA objects, one for each ObjectA, so that the ConcreteA
object's R1 relationship can be instantiated in the factory where it
can be done without a dynamic cast. I suspect that would require a
reverse pointer from ObjectA to ConcreteA as well, just to keep track
of the ConcreteA objects.
Reply With Quote
  #10  
Old 09-14-2006, 05:12 PM
H. S. Lahman
Guest
 
Default Re: Abstract public member variales?

Responding to Guild...

>>>I has assumed that each ObjectA contained a reference to some
>>>ConcreteA, because then the relationship could be created by the
>>>factory and navigated by the persistence procedure.

>>
>>Not quite. Exactly the same protocol will be defined in
>>[Deconstructor] as is provided by Serializer. The ConcreteX
>>methods will implement that protocol exactly like the Serializer
>>stubs filled in for each ObjectX. The problem is that the single
>>ConcreteA method applies to /every/ instance of ObjectA. In
>>Serializer that was taken care of by the implicit 'this' pointer.
>>But when we delegate [Deconstructor] to a peer object the R1
>>relationship is *:1 and we have to instantiate it to write a
>>particular ObjectA out.

>
>
> Could you expand upon that slightly? I am not entirely sure of the
> intended role of each object in the collaborations. If I understand
> correctly the Deconstructor class implements methods to give
> primitive values to the persistence system. The ConcreteX subclasses
> inherit those methods and then use them when the call to saveIt() is
> made.


Just like Serializer, the superclass just defines an abstract interface
to methods like saveIt() that each subclass must implement. In the
Serializer case those methods were in the object itself so the object's
attributes were unambiguously visible via the implied 'this' pointer
when the method puts their values in a message to the persistence domain.

In [Deconstructor] each subclass instance must implement the superclass
interface as well. The implementation of that interface in each
[ConcreteX] is exactly the same as the implementation of the Serializer
interface. It reads the attributes, formats them into a message data
packet, and then ships it off to the persistence domain.

The problem is reading the attributes because they are not in
[ConcreteA], they are in [ObjectA]. So the saveIt() method in ConcreteA
must navigate the R1 relationship to get to them rather than the 'this'
pointer. If R1 is implemented as a pointer, then the address of that
reference with be exactly the same as the address provided by the 'this'
pointer. The only difference is that the 'this' pointer in Serializer
is hidden while the R1 pointer in the deconstructor approach must be
explicitly implemented, instantiated, and navigated.

In the Serializer case somebody had to "walk" the objects in [ObjectA]
set and invoke the Serializer's saveIT() (or whatever) protocol method
for each object. Somebody has to do the same thing for the
deconstructor approach. When they do so, they must instantiate the R1
pointer to the current ObjectA in hand before invoking saveIt() to be
sure saveIt() gets the right attribute values.

>
> Just before saveIt() is called, a relationship is instantiated to
> show saveIt() what to save. You have said that instantiating
> relationships by providing an object as an argument is not good
> because it couples the instantiation of the relationship to the
> caller of the method. But it seems as though the caller and the
> instantiator will be the same in the case either way.


No, I think I said that passing object references around in
/collaborations/ is not a good idea. Assuming one implements
relationships with pointers, then they have to be instantiated by
assigning the right address to them. One can assign unconditional
relationships via constructor arguments, but there is no choice about
passing an object reference as an argument to a setter for the
referential attribute when relationships are conditional.

This gets back to the notion of separating instantiation from
collaboration. If one uses pointers to implement relationships, then
the pointer is just a referential attribute and assigning it is simply a
matter of passing the object address in a setter (or constructor)
invocation. In that context there is not expectation that anything will
actually be done with the object yet. Moreover, the context for
instantiation is likely to be quite different than that of collaboration.

But when one passes an object reference as an argument in a
collaboration, there is every expectation that it will be accessed by
the method; otherwise why pass it? That introduces a higher degree of
coupling in the collaboration because the caller needs to know that the
receiver will also collaborate with that particular object.

In this case you are correct that the caller is likely to both
instantiate R1 and invoke saveIt() because of the nature of "walking"
the [ObjectA] set. Note that I was usually careful to use qualifiers
like "likely" or "usually" when saying the instantiation context will be
different. B-) There are times when that isn't true.

However, that is not necessarily the case, even here. For example,
suppose one decides to implement that "walk" with a static class method
in [ObjectA] that will give up the instances one at a time. Now the
relationship is instantiated by that class method by returning a new
reference each time it is invoked. If ConcreteA.SaveIt() has an
internal loop over invoking that class method, then the "somebody else"
I mentioned above becomes [ObjectA] itself. Then saveIt() is invoked by
somebody else who "walks" entire classes. B-)

>
> However, I am thinking that it would be good idea to have multiple
> ConcreteA objects, one for each ObjectA, so that the ConcreteA
> object's R1 relationship can be instantiated in the factory where it
> can be done without a dynamic cast. I suspect that would require a
> reverse pointer from ObjectA to ConcreteA as well, just to keep track
> of the ConcreteA objects.


I think that would be inefficient in both space and performance because
of the heap operations. As described, there is no reason for
[ConcreteA] to have any state data. (In fact, the GoF Strategy objects
rarely have state data.) All the state data is in the [ObjectA] objects
and saveIt() is a pure behavior that eats that data.

--

*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl{}pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info{}pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



Reply With Quote
Reply


Thread Tools
Display Modes


All times are GMT -5. The time now is 09:32 AM.


Powered by vBulletin® Version 3.7.2
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
vB Ad Management by =RedTyger=

In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.