Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

A novice's thoughts on applying Demeter to extension code

by diotalevi (Canon)
on Nov 22, 2004 at 19:52 UTC ( [id://409709]=perlmeditation: print w/replies, xml ) Need Help??

In reading Re^4: Law of Demeter and Class::DBI, I re-learned that keeping the logic near the data is a good thing. In Re^5: Law of Demeter and Class::DBI I asked how a user is supposed to attach new logic that the author of the class did not provide. There are a few ways to do this and I'm seeking your thoughts on when each alternative is more appropriate. I'm starting with the idea that putting the new logic into my own package is a poor idea because that immediately violates the principal I'm trying to understand.

How do famously popular OO languages like Java, C#, .Net, and VB handle these problems?

The rule of thumb I've learned is: Tell, don't ask. I'll quote from jplindstrom in Re^4: Law of Demeter and Class::DBI for a moment because I don't want you to decide not to read this part because it is on another page.

I agree that accessing the underlying hash is a bad idea, but that's not the point. Among other things, OO is about putting the logic near the data.

Let's say we want to indicate that a logger object should flush itself to disk when it's appropriate (it may take some time, so we can't do it right away). An example of not so good OO design:

#in a time sensitive loop $logger->isFlushPending(1); #later on if($logger->isFlushPending) { $logger->flush(); $logger->isFlushPending(0); }

It's bad OO design to use getters to read the state of an object and then use that information to make decisions about how to use the object. The logic controlling the object is now located outside of the object rather than inside it.

So you avoid the getters (asking the object what state it has) and instead tell the object what you want it to do and it can sort out how to do that itself.

#in a time sensitive loop $logger->setFlushPending(); #later on $logger->flushPending();

and the logic controlling whether to flush the log data to disk is now implemented in the flushPending() method. Inside the object, not outside.

See also: TellDontAsk.

-- jplindstrom

Assume I'm going to use this logger object and that it implements the first interface but I want to write my own code cleanly. This task probably occurs to other objects which implement reasonable APIs but for some reason just don't have the logic that I need them to. Perhaps I have an Employee object but I would like to have some logic about whether the salary is above average. This is a domain for business-level logic to show up all over the place. It won't exist in object to start with and it has to be put somewhere.

Assume also that I have a context of "here" which is some code that is using the object and a Rule of Demeter violation is involved.

Please consider the following alternates or any others you find worthwhile. I'm obviously inexpert regarding the Rule of Demeter so my perspective on what things I can and should be doing is limited. I have ordered this list by things so that the things I wish to mention and dismiss are at the start and the better solutions are at the end.

Ignore Demeter

I will write code like $Employee->Manager->CostCenter and just not worry about it. This is appealing because this is what I already do and I haven't found a practical reason to stop doing this. I've never been bitten by the problems the Rule of Demeter is designed to protect against.

Wrap Demeter in a local function

I will write a locally known function which does the double dereference for me. I've now polluted my local namespace

sub main::ManagersCostCenter::{ my $Employee = shift; return $Employee->Manager->CostCenter; }

Subclass Employee to add the function

Now I have to remember that MyEmployee is just like Employee except that I've added a property or two to it. I don't like this because there may be some other code that subclasses Employee and now I have to remember an entirely new package name. When I have multiple places to solve this problem, I end up with lots of subclassing.

I don't like it because now I have to remember more things.

package MyEmployee; @ISA = 'Employee'; sub ManagersCostCenter { $self->Manager->CostCenter; }

Delegate

This is cleaner in logic than subclassing but doesn't prevent me from having to memorize this additional class and how it relates to Employee. It also means I now have to deal with AUTOLOAD and how much of Employee's interface MyEmployee is going to have to know about.

package MyEmployee; sub ManagersCostCenter { $self->Manager->CostCenter; } sub new { ... } sub can { ... } sub AUTOLOAD { ... }

Add the logic to the class

This is appealing because I won't have to consult with the author of the class, don't have to do complicated logic to redispatch methods, haven't played and ISA games, and it looks transparent to my user-level code. This has a namespace collision problem Employee's interface changes and adds the same function. This also enables polymorphism so if there are other unrelated objects which also implement this method, this class benefits.

sub Employee::ManagersCostCenter { $Employee->Manager->CostCenter; }

Add the logic to a parallel namespace

This is also appealing but it breaks polymorphism because to get this function I have to use a special calling convention and any polymorphism-using code isn't going to know about that.

$Employee->My::Employee::ManagersCostCenter; sub My::Employee::ManagersCostCenter { $Employee->Manager->CostCenter; }

Replies are listed 'Best First'.
Re: A novice's thoughts on applying Demeter to extension code
by pg (Canon) on Nov 22, 2004 at 20:31 UTC

    Before I actually took a serious look at Class::DBI, I always thought that it was something like DAO (Data Access Object) in Java. But when I really looked at it, it was not, it was more like a SQL statement generator, or another way to write a SQL statement without knowing the syntax, but then you have to know the syntax of Class::DBI.

    I agree that Class::DBI will provide certain help in a way, but not in the sense to keep access close to data.

    The DAO's in Java is really what you mentioned, keeping access close to data. The person write DAO needs to know the database table structure, know all the column names etc. But the caller of those DAO's don't need to know those any more, to them, data is data, doesn't matter how it is stored in database.

    Class::DBI clearly lacks those merits.

    To me, Class::DBI is not a way to keep access close to data, or to hide the table strcutures from the user, but mainly just a way to generate SQL statement.

    BTW, in Java, there is nothing similar to this (Even if there is any, it is not popular at all. On the other hand, I doubt that this is a must, and how much benefit it brings to the table.).

    Personally I think that the Java way delivers "keeping access close to data".

      No offense, but it sounds like you haven't looked very closely at Class::DBI. The accessors don't have to match the columns, and mine often don't. You can add logic to your classes, and I usually do. It keeps object state while you edit the data and manages writing it back out to the databases, as well as doing lazy-loading, adding validation routines, etc. It's pretty much exactly like a DAO.

        No, no offense at all. On the contrary, I am glad that different views being presented and discussed. Especially for reply like this, we argue precisely the merit of the issues, and nothing more than that.

        If I don't gain any knowledge from others here, what's the benefit to me? Another example of learning opportunity is that, sporty pointed something new to me in his post above.

        I knew the quality of your posts from the past, and I am glad that you replied.

      pg,
      I meant this question in a more general sense than Class::DBI. Other people originally posed their Demeter problem in CDBI's light. I'm thinking of how to reapply this to YAML.pm, some code at work implemented in a VB dialect, B::Deparse, and perhaps, I suppose, Alzabo or other OO-RDBMS mappers. It wasn't my primary concern.

      Thank you for the amplification on CDBI, however.

      Yes there is. It's EJB using CMP. You define the classes and how they relate to the database and other columns. In fact, I had to debug the sql being generated when I was getting no data back.

      ----
      Then B.I. said, "Hov' remind yourself nobody built like you, you designed yourself"

Re: A novice's thoughts on applying Demeter to extension code
by BrowserUk (Patriarch) on Nov 22, 2004 at 22:08 UTC

    If I understand your example, your saying that when you are dealing with some employee, you need to charge some of their expenses or costs to a cost centre. But the cost centre of a given employee, is that of the this employee's manager. Therefore, to fill in the cost centre, you have to first query the employee's manager, and then query that manager's cost centre, and then fill in that value wherever it is required.

    Where that falls down is that you are filling in the cost centre in relation to the employee. You should not be doing this.

    If the Managers own (have responsibility for) the cost centres, then all his employee's costs, are his costs. He should have oversight of them. Once he approves them, they become a part of his costs. Only at this point should the cost centre be filled in.

    In other words, if you find yourself in the position of needing to aquire information from a a remote source, to complete a task at a given level, you are doing the task at the wrong level.

    When the Employee's complete their costs/expense forms, they should not be attributing them to a cost centre at all. Indeed, their forms should not be fed into the Costing system directly. Their forms should be assign back to their managers, where they should be consolidated and rolled up into a summary screen against which the manager will attribute costs centre numbers. This allocation to a cost centre then becomes his explicit approval of the costs.

    In this way, the employees (flesh & blood) do not have to bypass the online system, by seeking verbal approvals (and getting a nudge and a wink on the correct costs centre number to allocate stuff against). It is these ad hoc arrangements, often as not done whilst standing around the coffee machine or water cooler than break the audit trail, and create huge problems down line.

    Ex.1 ) An employee seeks approval for an expense item, and is given a cost centre number. A few weeks later, he has obtain another expense item. The manager is not around, and it seems similar to the other one, so he fills in the same cost centre number. Audit problems ensure.

    Ex. 2) Three managers up the line, a re-structuring occurs that move this employee's, manager's, manager from one cost group to another. As a consequence, all the costs centre numbers down stream change. Because of the ad hoc "standing approval" arrangements, all the usual expenses continue to be billed to the old cost centres and audit problem's ensue.

    This was all very common in the bad old days before MIS systems where set up properly.

    Any arrangement whereby every employee assigns their own costs to cost centres, effectively invalidates the cost centre mechanism, and that you are encountering this problem within the software design, is direct evidence of that problem.

    That's one of the strengths of OOD. If you find that you are having trouble correctly modelling reality, within your class structure, it can often be because you've hit upon a flaw in the existing system.


    Examine what is said, not who speaks.
    "But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
    "Think for yourself!" - Abigail        "Time is a poor substitute for thought"--theorbtwo         "Efficiency is intelligent laziness." -David Dunham
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon

      BrowserUK,
      Whether it is valid or not to talk about an employee's manager's cost center isn't the point here. I could have use the employee's manager's email address as an example and it wouldn't have changed anything about my concerns regarding the Rule of Demeter. Could you re-read the question and consider in that light?

        Whether it is valid or not to talk about an employee's manager's cost center isn't the point here

        Actually, it is exactly the point. At least as I see it.

        You are in a piece of code where you find yourself needing to access an attribute of an attribute.

        You are aware that by doing so, you are breaking the LoD.

        The question you asked is: How can you legitimise the need to break the LoD, by wrapping it into a method that conceals the fact that you are accessing the attribute of an attribute.

        But, if you re-read your post, you'll see that all of the methods you outlined, still access the attribute of an attribute; and so, still break the Law of Demeter.

        The problem lies not in the implementation that allows you to do it, but in the design that requires you to do it.

        My extended example was an attempt to show how such bad designs come about and why they are wrong.

        An attempt to show that any of the solutions you presented, or any solution thatcould be presented, that continued to allow you to apply the value of a Manager attribute, in the context of an Employee, would continue to break the Law of Demeter.


        Examine what is said, not who speaks.
        "But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
        "Think for yourself!" - Abigail        "Time is a poor substitute for thought"--theorbtwo         "Efficiency is intelligent laziness." -David Dunham
        "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: A novice's thoughts on applying Demeter to extension code
by dragonchild (Archbishop) on Nov 22, 2004 at 20:34 UTC

    Update: Of course, it would help if I actually remembered that sub PACKAGE::name {} is legal syntax. *sighs* It's Monday.

    What about plugging in your new logic into the original namespace?

    package Employee::MyStuff; package Employee; sub ManagersCostCenter { my $self = shift; $self->Manager->CostCenter; } 1; __END__
    Put that into Employee/MyStuff.pm and you're good to go.

    Of course, you're now fiddling with the internals of a class you don't control, which means that you're violating encapsulation. You also, everywhere you use Employee, have to now do:

    use Employee; use Employee::MyStuff;
    In that order would be preferable (though not absolutely required).

    This is a somewhat common thing to do on CPAN. (q.v. URI and URI::QueryParam for a good example of this)

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://409709]
Approved by etcshadow
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (6)
As of 2024-04-23 18:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found