John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

I'm looking at Moose, and plan to use it to implement a simple module to "get into" it. But I'm having trouble with some of the concepts when deciding how to lay out my objects.

First, the new method normally takes parameters that match all my attributes. Well, why is it the caller's (the user of the object) business what my object's internal state is? What is the internal state in abstract, and even if that is pretty much implied by what it does, my specific choices of how to represent it?

Instead, my constructor arguments would be options used to configure the instance. Sure, some of these may be stored directly in attributes to be fetched later. But in general that's not the case. They will be used by the setup code to construct and arrange the various internal machinery of this instance, and may not be needed again ever.

Now I see there are ways to massage the arguments and such, but the framers of the system decided this was the normal thing to do, and the whole idea makes no sense to me. So am I fighting against it because I'm thinking about it wrong? What am I missing?

Second, what is the point of a read-only attribute? If it can't be set, it will never exist. There must be some "but..." in there.

More generally, there is no distinction (in the docs) between internal access by the instance, and access by others using the object. So how do I make attributes that are for internal use? Just giving it a name starting with an underscore is a collision waiting to happen, since the accessors are virtual methods, and my slots should be local to this specific class even if a derived class declares a slot with a name that happens to be the same. Is there a separate slot-access syntax that's not in the tutorials and manual? And why isn't that super prominent instead?!

My native language is C++, and I have looked at the Perl 6 object system and it all makes good sense to me. So what am I missing?

—John

Replies are listed 'Best First'.
Re: Psychic Disconnect and Object Systems
by BrowserUk (Patriarch) on Apr 15, 2011 at 16:46 UTC

    Invariably, encouraged by many OO languages & frameworks and almost all OO teaching texts, people start out to define their objects by defining their attributes. This is, in my opinion as a result of my personal experience of using OO & of working with others OO, completely the wrong way to approach the problem.

    When you write procedural code, you don't start by writing a huge block of variable declarations at the top of the program, subroutine or module. Not even in those lanaguages that require declarations to come at the top of the block. You start by writing the algorithm, and then go back and declare your variables as you need them.

    The right way--I know, I know. bear with me.--is to define the methods first. Ask yourself not: what do these objects contain? But rather: what do these objects need to do?

    The second mistake even very experienced OO programmers and architects seem to make repeatedly, is to subtly alter that (second above) question, and instead ask: What could these objects do? And that leads to all sorts of O'Woe.

    The right way--again--is to write the code that will use the objects first. And I'm not talking about cutesy ok/nok unit tests either. I mean the actual code. Just create an instance of your object by calling the classes new() (or whatever constructor name makes sense) method with no parameters and then do what you need to do with it in the context of the calling code.

    Out of this falls the methods (and sensible names) you need to call, along with many of the arguments those methods will require. In the writing of the calling code, the interface will change, change again, maybe change back again. Some methods will get broken up into two (or more). Others may be combined. You (I anyway) often find that things (values, constants etc.) that I initially thought belonged inside the the class instance, are actually application specific not class/object specific and so don't belong in then instance. And, much more rarely, vice versa.

    Once I'm reasonably happy with the calling code for the class, I can then move on to writing the methods that it uses knowing not just what they should do, but how they will be called. Which makes writing them much easier. And when writing the methods, it becomes clear what attributes are required. And becomes much easier to see which attributes need to be stored in the instance data; and which can be generated on the fly. And you should never store a attribute if it can be reasonably regenerated from other attributes.

    Working top down this way, means that I can concentrate on writing the code that is actually needed rather than trying to second guess what might be needed.

    Only once the first draft compiles clean--and preferably can be exercised, though that is often not possible without expending huge effort trying to mock shit up around it--do I then look at the interface with a 'what if another application might want to reuse this class some day' to see if there is anything that can obviously be made more general without compromising its usability/maintainability/performance for this application too much.

    An interesting side-effect of this is that you rarely end up with externally visible accessors to your attributes--which is a mighty good thing. And if you apply good logic to it, using internal accessors for attributes that have no external visible interface makes no sense at all. Which make auto-generation of accessors a complete waste of time.

    In summary (IMO throughout)

    Sitting down to write a new class definition before you've written and therefore understand--because you do not before--how it will be used, is fundamentally flawed. It just means you are second guessing the real world, and that leads to whatifitis.

    And trying to decide what attributes are needed by an object, before you have a clear idea of both the class interface (methods); and their implementation requirements & costs; is insane.

    But almost none of the OO texts I've seen teach people to work that way, which may or may not give you a clue as to how much weight you should give to my opinion :)

    BTW:Read-only attributes are otherwise know as constants--and are better defined as such. Except for the rarely implemented concept of externally read-only attributes which are internally read-write. That is, an attribute that is modified internally as the object evolves; that can be read directly from external sources.

    But does anyone use them? Does any OO framework support them? Mostly a getter (no setter) is defined to prevent direct access; and most times it is better to simply (re)calculate the value on demand rather than storing it.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      Read-only attributes are otherwise know as constants--and are better defined as such.

      Attributes are per-object. Constants are per-process.

        Attributes are per-object.

        If an instance attribute is to ever have any value, then it is at best: write-once.

        And it is conceivable that a factory constructor could derive from different base classes to provide different constant attributes.

        Constants are per-process.

        Actually, Perl's constants are per-package rather than per process. And it certainly isn't inconceivable that they could be implemented to be block scoped as many of the newer pragmas are.

        And as I understand it, it would be completely possible to have per-instance constants with Perl 6 using Roles. Maybe even in Perl 5 with Perl6::Roles


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

      A real world example of a read-only variable which can't be recalculated on demand is your car's odometer.

        A real world example of a read-only variable which can't be recalculated on demand is your car's odometer.

        Ignoring that in the real world, the DVLA might argue with that assessment. Clocking is still big business, though it is getting harder to do. :)

        That is an example of both "Except for the rarely implemented concept of externally read-only attributes which are internally read-write." and an exception to "most times".

        But even that belies the reality of (at least modern) real world odometers which can display in either miles or kilometres, but internally probably count output shaft revolutions. They might therefore be best modelled as an internal revolutions counter attribute with a pair of methods that apply an appropriate scaling factor to that attribute to produce the miles or kilometres figure.

        Why not just count in miles and convert to kilometres on demand you might say. But back in the real world the same electronics in the dash are used in vehicles with multiple different drive trains. Eg. The BMW 3 series comes with 1.6, 1.8, 2.0, 2.5, 3.0 & 3.5 litres engines. You also have low-reving diesel and high-reving petrol engine options. Whilst the gear ratios will usually account for much of the difference in engine speed to road speed, they do not fit a rear axle capable of withstanding the rigours of 300bhp to the 120bhp 1.6 models, and the final drive ratios will be different. Adding to that, different models have different size rims and tyre profiles so the odometer has also to account for different rolling radii.

        The upshot is, that they do not make a different odometers for each of these combinations of model to allow them to count directly in miles or kilometres, but instead count in some arbitrary revolutions of the input and then write different constants ("appropriate scaling factors") to ROM which are used to convert the counter to real-world measurements.

        In other words, there are no getters or setters for the actual attribute; just methods to obtain calculated derivatives of that private attribute.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        hum? My odometer changes all the time. You wouldn't use is => 'ro' for a car's mileage cause you wouldn't be able to change it as the car moves.
        I think a better example is the "actual time". The actual time, is a constantly changing value, which is read-only. Except for those people who are allowed to insert leap-seconds.

      The thing is this is the right way only for one sort of objects ... for objects that are (mostly) behaviour containers. Objects that exist to do something and happen to need some (mostly internal) state to provide you with a nice interface.

      But there is a totally different sort of objects .... objects that are (mostly) data containers. Objects that exist to store some (mostly externaly accessible) data and provide little behaviour apart from validating the values stored and ensuring the consistency of the data set.

      In this case starting with the methods would be pointless. Instead you need to know what data do you need to store, what are the types and constraints and then you may start adding methods that compute something out of the stored data, that change several attributes at once in some way, ...

      Most times it's easy to say which sort of object are you designing ... a "worker" or an "intelligent data structure", sometimes the distinction is not so clear. Anyway starting with starting with the code that uses the object is IMHO a good idea.

      Jenda
      Enoch was right!
      Enjoy the last years of Rome.

        But there is a totally different sort of objects .... objects that are (mostly) data containers.

        There is a call for this type of object, but I think they are (or rather, should be) far rarer than they tend to be in many code bases. I think this comes about because people, and the texts/courses they learn from, tend to start their design process from the bottom and work up.

        Data has no purpose in isolation of the code that manipulates it. That is, at some point in the application, there is code that instantiates and populates one or more of these "data container" objects and then operates upon the data encapsulated. That code is (should be) the methods for that data.

        But that code often doesn't lend itself--because of other data dependencies; or code structural requirements--to being pushed down into the data object class as methods, so the objects becomes data only. Devoid of methods beyond constructors, destructors and accessors. And the result is that you've created a class to hold the data and access it and nothing more. But if you lifted the data within the container to the level where it is actually manipulated, then you'd save an entire layer of pure overhead.

        That is to say, at level X of the application, instead of storing a reference to an object--a blessed hash in Perl terms; a 'blessed' struct in C++ terms--as a my/auto variable and accessing it contents through methods. You store the hash or struct in a my/auto variable at that level and access it contents directly. And you save the overhead method lookup and subroutine calls; and inconvenience of method call syntax to access the data.

        Ie. Instead of:

        { ## some block at some level my obj = Class->new( initX, initY ); obj->setZ( obj->getX() + obj->getY() ); }

        You have:

        { ## some block at some level my %hash = ( X => initX, Y => initY, Z => 0 ); $hash{Z} = $hash{X} + $hash{Y}; }

        Now someone will say, but what if you have two (or more) places in you code where you need this object. by inlining it you are promoting C&P reuse. And my response is that if these are the same (type of) object, then the manipulations (above represented by: o.z = o.x + O.y) will be the same, and so that code should be a method. The response will be that maybe in the other place, they need to be multiplied, not added. At which point, I say that they are not the same (type of) object, despite that they both have X,Y & Z components. And so, they shoudl not be merged into a single class.

        Now that example is all far too abstract and trivial to make for a good case for my position. But it does demonstrate a point that if you design from the bottom up, attributes first, you will often conflate distinct objects because they are superficially similar in their internal data structure. They both have X, Y & Z components. But of you operate top down--then you probably wouldn't encapsulate such trivial data in the first place:)--but if you did then the object in one place would have an Add() method, and the other a Multiply() method. And you would not therefore conflate the two types.

        Sure, you can probably get away with creating a hybrid class that has both methods. But then you have this complicated class that does more than either usage requires and so won't spot that actually they are two different classes each of which is actually only used in one place. And definitely won;t spot that actually, they are each so simple that they can each be lifted into their call site thereby making the code both simpler and more efficient.

        Again, Ill say that this example is too trivial to make for a convincing argument. So, I'd ask you to outline one of these "data only objects" and its use. Preferably a real world example you have kicking around. Then I could attempt to make my case more strongly; and give you the opportunity to counter based upon real world usage rather than trivial abstract examples.

        One of us might convince the other :)


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        Indeed, in C++ I often augment a "plain" struct with member functions. I wouldn't write a virtual get/set pair for each field though.
      This is, in my opinion as a result of my personal experience of using OO & of working with others OO, completely the wrong way to approach the problem.
      I could not agree more. I've seen discussions on automatically generating reader and writer methods for state variables (in which languages I don't recall), and I join the chorus of those pointing out that this probably isn't a good thing to be doing. It just encourages making the state not encapsulated, and is no different t han just declaring all the fields public.

      The right way--I know, I know. bear with me.--is to define the methods first. Ask yourself not: what do these objects contain? But rather: what do these objects need to do? ... The right way--again--is to write the code that will use the objects first.
      For sure! I start with use-cases and from there describe the object user's view of the object. I've written full documentation before finalizing the underlying design of just how it manages to do all that.

      It sounds like we are on the same page. So, how would you use Moose today? Certainly I can start by writing the methods, and plan that any internal state that I need to implement them are indeed internal implementation details. So how do you declare your has's to create per-instance storage, for internal use only?

        So, how would you use Moose today?

        I probably wouldn't. Mouse maybe, but not Moose. Mostly because it carries a lot of complexity under the covers in order to provide features--like deep introspection--that I do not see the benefit of.

        And even though that complexity is hidden from me, I know it is still there and I must carry its overhead though I'll never benefit from it. And for the type of code I mostly write these days, that overhead is significant.

        So how do you declare your has's to create per-instance storage, for internal use only?

        As I understand it, if you use is bare, then you get no accessors and no warnings.

        I'm not into the bondage and discipline thing. Indeed, in part it was the complexity of the whole private/public/friend thing that caused me to arrive at a considerable distaste for C++. (There are other reasons also, but that was one.) So when I feel the need for OO in Perl, I'm perfectly happy to construct mine around blessed refs manually.

        With no attribute accessors at all--public or private. '_'s to indicate private methods are sufficient for my purposes. And initialisation done by the constructor from whatever form of input makes most sense. Eg. If the data that is used to initialise an object is read from an external source as text, then either that text gets passed to the constructor, or for mass instantiation, an open file handle.

        Declarative syntax is a nice to have, but only if it retains sufficient control to allow me to decide (and only pay for) those features I want/need.

        As I rarely write assessors for my attributes, I've never felt the burden of doing so, so don't need auto-generation. And As I only validate parameters when they transition the public/private boundary--not every time an attribute gets written--the use of declarative validation is also superfluous.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Psychic Disconnect and Object Systems
by chromatic (Archbishop) on Apr 16, 2011 at 00:47 UTC
    First, the new method normally takes parameters that match all my attributes.

    As others have mentioned, the constructor can take any parameters you want to provide. Moose provides the mechanism, not the policy.

    ... why is it the caller's (the user of the object) business what my object's internal state is?

    The constructor API is only the constructor API you declare. I have plenty of objects which take data provided to their constructors and then are effectively immutable. It's a nice pattern, when you can arrange your objects to behave in that fashion. As you say:

    They will be used by the setup code to construct and arrange the various internal machinery of this instance, and may not be needed again ever.

    ... which matches my experience in many—but not all—objects.

    ... the framers of the system decided this was the normal thing to do, and the whole idea makes no sense to me.

    I think you're overthinking it. People who create objects with accessors and mutators and constructor parameters for each and every attribute end up with slightly encapsulated structs and the concomitant risks. Moose allows that. Moose also allows people to create objects with neither accessors nor mutators and purely constructor parameters and various combinations of dependent attributes thereof. Where Moose expresses an opinion on how to design your objects, it offers a preference for declarative syntax and encapsulation. How far you take that encapsulation is up to you.

    Is there a separate slot-access syntax that's not in the tutorials and manual?

    In the sense of private, protected, or friend modifiers, no. Those don't really work in practice, especially in Perl. (I believe you need special hardware memory protection to make them robust. Then good luck writing a performant garbage collector.)

Re: Psychic Disconnect and Object Systems
by ikegami (Patriarch) on Apr 15, 2011 at 16:29 UTC

    but the framers of the system decided this was the normal thing to do

    How so? They didn't do anything to hinder what you want to do whatsoever.

    around BUILDARGS => sub { my $orig = shift; my $class = shift; ... return $class->$orig(...); };

    is slightly more wordy than

    sub new { my $class = shift; ... return $class->SUPER::new(...); }

    But surely you don't have a problem with that??

    (Upd: By the way, you can use the latter, but overriding BUILDARGS allows Moose to do some optimisations. )

    Second, what is the point of a read-only attribute? If it can't be set, it will never exist. There must be some "but..." in there.

    is => 'ro' is simply a shortcut for reader => $name, and is => 'rw' is simply a shortcut for accessor => $name. It's more "write-once" than "read-only" since neither prevents the the constructor from initialising the attribute, and neither prevents defaults (including those that may be determined from other fields or from external data) from initialising the attribute.

    So how do I make attributes that are for internal use?

    has attr => ( reader => '_get_attr', writer => '_set_attr', handles => { get_attr => sub { ... }, set_attr => sub { ... }, }, )

    (Upd: Sorry, I thought you were asking about internal use accessors. )

      It seems like you're being deliberately obtuse in your first two answers.
      How so? They didn't do anything to hinder what you want to do whatsoever.
      The easiest thing to do is to use Moose's automatically-generated new, which does the thing the OP doesn't want. AFAICT "attributes" are the product of confusion between the view that an "object" is a collection of named bits of state, and the view that an "object" is a box to which you send "messages." People want "messages" telling the "object" "give me the value of this named bit of state," so "objects" have "attributes" by default.
      is => 'ro' is simply a shortcut for reader => $name, and is => 'rw' is simply a shortcut for accessor => $name.
      But if you just do
      has 'foo';
      You get read-only attributes by default. The OP's question is "why?" The answer usually leads to a religious war.

      As for internal-use attributes, the OP is wondering what this does:

      package Base; use Moose; has 'x', default => 'Base'; # internal sub foo { print shift->x; } package Child; use Moose; has 'x', default => 'Child'; # also internal package main; Base->new->foo; # "Base" Child->new->foo; # "Base" or "Child"?
      (I would test it myself, but don't have Moose lying around.)

        The easiest thing to do is to use Moose's automatically-generated new,

        So? Unless you are proposing that Moose should automatically build the desired constructor instead (impossible), that's irrelevant. The only thing that matters is how easy it is to build your own constructor, and Moose doesn't hinder that at all.

        But if you just do has 'foo'; You get read-only attributes by default.

        No you don't. You get an error.

        The OP's question is "why?"

        Obviously not. He's asking about is=>'ro'. ro = read-only.

        It seems like you're being deliberately obtuse in your first two answers.

        You have not identified any issues with them.

        As for internal-use attributes, the OP is wondering what this does:

        I did indeed misread that question.

      I don't recall reading about accessor, just reader and writer.

      So, the built-in 'new' logic and the application of default don't go through the normal set function, so lack of one doesn't bother it.

        I don't recall reading about accessor, just reader and writer.

        $o->an_accessor('foo'); $foo = $o->an_accessor(); $o->a_writer('foo'); $foo = $o->a_reader();

        So, the built-in 'new' logic and the application of default don't go through the normal set function, so lack of one doesn't bother it.

        Correct.

Re: Psychic Disconnect and Object Systems
by stvn (Monsignor) on Apr 17, 2011 at 13:30 UTC

    As the original designer of Moose (actually I stole pretty much all of the good stuff from other languages), I feel I should respond.

    First, the new method normally takes parameters that match all my attributes.

    Actually, this is just the default, you can adjust this in a number of ways. First there is BUILDARGS which allows you to pre-mangle the @_ parameters that are sent to the actual underlying constructor mechanism, using this mechanism you can create just about any interface you want for new. But it is also possible to just change the names of the parameters that are accepted by new using the init_arg option in the attribute definition. Here is a simple and contrived example ..

    has 'secret_name' => ( init_arg => 'public_name', ... );
    It is also possible to disallow the assignment of an attribute value through new altogether by doing the following (yeah this is an ugly use of undef here I know).
    has 'secret_name' => ( init_arg => undef, ... );
    In short, by default Moose provides a simple parameter to attribute mapping for new, but this is just the default and it is very easy to change this to create the API you desire.

    Instead, my constructor arguments would be options used to configure the instance. Sure, some of these may be stored directly in attributes to be fetched later. But in general that's not the case. They will be used by the setup code to construct and arrange the various internal machinery of this instance, and may not be needed again ever.

    Most of this is addressed above, but I wanted to address the final sentence in more detail. This is also possible using the BUILD method, since it gets the same set of HASH ref parameters that new gets and can then be used to construct the instance as you see fit.

    package My::Object; use Moose; has foo => ( is => 'rw' ); sub BUILD { my ($self, $params) = @_; $self->foo( $params->{bar} ); }
    You can think of BUILD as being more like a constructor in Java. In Java, new is actually a keyword and the JVM itself takes care of actually constructing the instance, then the constructor that is called is really just a "initializer" in which you can setup the instance.

    Now I see there are ways to massage the arguments and such, but the framers of the system decided this was the normal thing to do, and the whole idea makes no sense to me. So am I fighting against it because I'm thinking about it wrong? What am I missing?

    Much of this feature was actually borrowed completely from Perl 6 and I think that the idea behind the design was really one of the main ideas behind Perl. The idea that you should be able to whip up something simple and useful very quickly, but that you should also be able to grow that into something more complex and well thought out. By making this the default behavior and providing a number of ways to customize this (TIMTOWTDI) I think that Moose is simple just being "Perlish".

    Also, you are not wrong. However I think maybe you are looking at things in a very C++ centric way and it may take some time for you to loosen the restraints on your mind. Perl is a very permissive language, in fact, of all the mainstream languages out there i would say it is the most permissive. This is a good and a bad thing, and while Moose does try and impose some consistency and structure to OO Perl, it also tries hard to stay Perlish and keep that open and permissive core that is honestly why I love Perl so much.

    Second, what is the point of a read-only attribute? If it can't be set, it will never exist. There must be some "but..." in there.

    No, there is no "but". A read-only attribute can only be set through the constructor, after that it cannot be set again. I find this very useful myself, but I suspect you might not since you seem less inclined to expose your attributes in your public API. I think that this again is your C++ maybe showing through, attributes in Moose are not the same as member variables in C++. A Moose attribute is really just a set of behaviors wrapped around the state, it is not the state itself.

    Perhaps the best example of this comes when using the NativeAttribute features. For instance here is a simple Queue implementation using the Native Attribute traits that actually has no accessors at all.

    package Queue; use Moose; has '_items' => ( traits => [ 'Array' ], is => 'bare', init_arg => 'items', isa => 'ArrayRef', lazy => 1, default => sub { [] }, handles => { 'enqueue' => 'shift', 'dequeue' => 'pop' } );
    As you can see this exposes no attributes to the public API (is => 'bare' means do not generate any accessors). It allows you to pass in the initial set of items in the queue through the constructor arg items, but it provides no direct access to them afterwards. If nothing is passed in, it uses lazy/default to create an empty ARRAY ref for you. It then uses delegation and the Native Attributes features to create an enqueue and dequeue method which perform the shift and pop operations on the ARRAY ref.

    So as you can see, simple attributes can just serve to expose a member variable, but with a little more complexity and code you can actually create behaviors rather then just state accessors.

    More generally, there is no distinction (in the docs) between internal access by the instance, and access by others using the object. So how do I make attributes that are for internal use?

    Well, we encourage you to always use methods to access state, both internally and externally. This is not a Moose thing really, this is Perl OOP actually, Moose just makes it easier.

    Just giving it a name starting with an underscore is a collision waiting to happen, since the accessors are virtual methods, and my slots should be local to this specific class even if a derived class declares a slot with a name that happens to be the same.

    Honestly, I don't think I have ever really had a name collision on a private attribute, I think while it is a possibility, it will be waiting a long time to happen. If you are concerned about this though you can certainly name your private attributes using the class name as a prefix or something, this would eliminate the possibility of collision, but really is overkill. My suggestion really is to not worry about it because it is unlikely to happen, and when it does, you can address it. Again though, this is not Moose, this is Perl OOP (and Python and Ruby and any other dynamic language which does not have true private methods).

    Is there a separate slot-access syntax that's not in the tutorials and manual? And why isn't that super prominent instead?!

    There is, but it involves going through the meta-layer and it is really ugly and verbose. Again, you worry too much, sit back and relax, this is Perl.

    My native language is C++, and I have looked at the Perl 6 object system and it all makes good sense to me. So what am I missing?

    Like I said before, I think you are maybe just looking at this with your C++ glasses on. Every programming language has a set of qualities that encourage you to think and write code in a certain (idiomatic) way, some of these things transfer from language to language, but not all. I encourage you to take some time and play around with Moose as Moose and to put aside (at least for the moment) some of your C++ reactions, you might like it.

    ... or you might not, which is also perfectly fine, Moose is not for everyone (see also BrowserUK).

    -stvn
Re: Psychic Disconnect and Object Systems
by locked_user sundialsvc4 (Abbot) on Apr 16, 2011 at 22:06 UTC

    Even if it did get a bit carried away there towards the end, what BrowserUK said here is, in my opinion, excellent wisdom, well thought-out and presented.   I agree with that entire point of view.   It has some pretty subtle points in there, but it’s very clearly the voice of experience.

    In my own experience, an object (in a programming system, albeit not so much in the real world) is, like everything else in a programming system, most of all a functional thing.   Which means, “methods.”   And, the tighter and the more self-contained it is, so much the better.

    When you expose attributes, you run the calculated risk that there will in the future be other code out there somewhere, unrelated to this object but now functionally dependent on this object by virtue of the fact that it examines those attributes and contains “procedural code” of some kind that depends upon their values.   It is a procedural dependency, hidden, as it were, by a data dependency, caused, as it were, by a data exposure.   It can be problematic at times.   To put it another way ... it is a characteristic that might easily be overlooked with regard to its possible negative implications.

    Everything in a programming system ought to have a specific purpose, and you ought to know, insofar as is possible and practical, what that purpose is to be ... before you build it ... and “built for and because-of that purpose.”   That statement is an ideal, of course, and therefore not always achievable and not always even desirable.