Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

High level OOP query

by packetstormer (Monk)
on Oct 24, 2013 at 11:24 UTC ( [id://1059430]=perlquestion: print w/replies, xml ) Need Help??

packetstormer has asked for the wisdom of the Perl Monks concerning the following question:

Morning Monks

This isn't really a detailed technical question, it's more a "accepted norm" query. As I have thought myself Perl without any immediate "go-to" person (there is nobody in my work environment that knows anything about coding!) I find I that I am struggling with coding practices and principles a lot.

I have recently moved from small (under 1000 lines) scripts to larger, modular, OOP projects. I won't lie, I am finding the concept a little difficult, especially coming from a non-coding background! Anyway, my question involves the creation of an instance and specifically how acceptable it is to modify it. Example:

Suppose this simple code:
#!/usr/bin/perl use strict; use warnings; use Storm; use Data::Dumper; my $config = { storm_server => '172.16.0.55', storm_port => '9001', storm_auth_login => '0', storm_login => '1', storm_user => 'stormuser', storm_pass => 'pass', }; my $self = Storm->new($config); print Dumper $self; package Storm; use IO::Socket; sub new { my $class = shift; my $self = shift; # The next lines, should this be here or in another # method if( $self->{storm_auth_login} ) { $self->{append_text} = "X12-" } bless $self, $class; return $self; }
As you can see I am creating an instance with a $config hash ref with certain information. However, is it the "norm / acceptable", as above, to check the argument and based on a value then modify it.
OR, is it better/normal to create a "blank" instance and then pass that into another method to change/modify?
OR, the third option: I am getting way to bogged down on what is the "norm" and I should worry more about my apparent lack of understanding of how OOP works!

Replies are listed 'Best First'.
Re: High level OOP query
by kcott (Archbishop) on Oct 24, 2013 at 12:57 UTC

    G'day packetstormer,

    Superficially, what you're doing there is probably fine. You wrote "Suppose this simple code:" — unfortunately, I don't know to what extent you've simplified your original code for posting here.

    Here's a few pointers (they're guidelines and recommendations; not rules and regulations):

    • You should put your modules in files that are separate from your scripts.
    • There should only be one module per file.
    • The standard filename extension for a Perl module is ".pm".
    • Typically, a module is not executed: you only need read and write permissions (cf. scripts, which also have execute permission).
    • A module needs to return a TRUE value: usually implemented by making the last line just "1;".

    So, Storm.pm would have this general structure:

    package Storm; ... # use statements, etc. sub new { ... } ... # other methods 1;

    Accessing instance variables (e.g. $self->{varname}) from your scripts is generally a very bad idea which you should avoid: it breaks encapsulation, causes all sorts of maintenance problems, and will generally come back to bite you in the bum when you least expect it. Add accessor and mutator methods to your classes and call them from your scripts, e.g.

    my $varname = $self->get_varname(); $self->set_varname($new_name);

    Passing a hashref to a constructor (e.g. Class::Name::->new($hashref)) is fairly common practice. I don't see any cause for concern in doing this.

    The code you have in new() is somewhat deceptive and may trip you up down the track. Both "$self->{storm_auth_login}" and "$self->{append_text}" look like instance variables, but they're not! At this point in your code, $self is an unblessed hashref: it's not until a few lines later that it becomes an instance of $class (i.e. bless $self, $class;). You may want to consider rewriting new() (and adding an additional instance method) something like this:

    sub new { my ($class, $args) = @_; my $self = bless $args => $class; $self->init; return $self; } sub init { my ($self) = @_; if ($self->{storm_auth_login}) { ... } ... # other initialisation code here return; }

    Now new() is clean and could be inherited by a subclass which has its own init() method.

    There's lots of documentation on this subject. Search perldoc: perl for any entries matching "mod" or "OO".

    You may also be interested in Moose (and related modules). That's probably getting a little off-topic from what you're asking about here.

    -- Ken

      Thanks for the detailed response, Ken. I really appreciate it.

      The code I posed was just for ease of reading and I would expect to split the class out to it's own package file when in use

      I am working my way though the book "Intermediate Perl" at the moment but I would love to find some real world examples that are simple enough for me to read. I have looked at many packages (from CPAN and other devs) but they are still a little complex for the standard I am at and I find trying to work them out is very time consuming due to their large size

      I will battle on though and thanks again for the reply.

        However, is it the "norm / acceptable", as above, to check the argument and based on a value then modify it.

        It is acceptable to modify any attributes based on arguments

        They say the user of a class shouldn't know/care about the internal state of attributes -- attributes are an implementation detail for the writer of a class

        so as the writer of a class do what makes sense, but as the user of a class pretend you don't know how it works inside (hard I know)

        smart people say: design a class the way you want to use it, start by writing code that uses it first

        perlootut, Modern Perl, Moo

        OR, is it better/normal to create a "blank" instance and then pass that into another method to change/modify?

        That depends, does the change/modification happen only once, during object creation? Or can it happen at any time in the life of the object?

        If you allow the user of your class to modify user/pass after object instantiation, and some other attribute depends on user/pass , then its best to encapsulate this relationship in a seperate method

        Instead of allowing ->change_username and ->change_password, call it ->change_userpass, and document that it affects the value of append_text

        But most likely you'll simply want to stick with doing this in the constructor -- and not allow changing of user/pass after instantiation

        OR, the third option: I am getting way to bogged down on what is the "norm" and I should worry more about my apparent lack of understanding of how OOP works!

        This! I recognize you as a one-of-us who doesn't really understand how OOP works :)

        This question is more of a low-level detail and you're not going to ferret out any benefits/tradeoffs/caveats... to find the best approach in this project -- its just not big enough and this detail is very low level

        No matter what you do, you're going to get it wrong, so you might as well get on with it, write your code (deliver product), make your mistakes, and learn from them :)

        The part you're probably missing
        Class-responsibility-collaboration card
        GRASP (object-oriented design)
        OOP in the Real World - Creating an Equation Editor - CodeProject
        How I explained OOD to my wife - CodeProject
        A Simple Example of Object-Oriented Design: An Address Book and An Example of Object-Oriented Design: An ATM Simulation ; tarballs at http://www.cs.gordon.edu/courses/cs211/
        Story-driven modeling

        Just remember that every one of us fought that same battle, from time to time felt just as lost and just as stupid, and that from time to time we still do.   It comes with the territory.   Meditate on all of the responses (present one excepted?) that you have received so far.   “Welcome to Perl,” and keep us up-to-date of your progress down The Path.   Bounce questions off of us (boink!!) anytime.

Re: High level OOP query
by hippo (Bishop) on Oct 24, 2013 at 12:57 UTC

    I couldn't comment on the norm, but I think both are acceptable and which you use will be determined by what sort of object you are creating and how it will be used.

    Personally, I tend to use new to create an empty object and then optionally call init from within new to populate it based on arguments to new. The advantage in doing it this way is that you can create an empty object efficiently or create a populated object with a single call from the calling routine, or create an empty object now and then populate it later by calling init directly from the calling routine. I don't see many downsides to this approach, certainly in the general case.

    But yes, don't get bogged down by this. There are much bigger fish to fry in the wonderful world of OOP. And before you go too much further down the roll-your-own path have you looked at the Modern Perl book or directly at Moose/Mouse/Moo?


    Updated to explain the call tree more clearly.

Re: High level OOP query
by pajout (Curate) on Oct 24, 2013 at 13:50 UTC

    Hello packetstormer,
    for better general understanding, I recommend Advanced Perl Programming by Sriram Srinivasan, that book helped me very much.

    Specially about your question: There are many ways how to initiate instances of classes, for instance:

    • blank instance, initialized just by calling setter methods
    • with hardcoded default values
    • with values specified by argument of new method
    • combination of previous 2 concepts
    • new method calls init method
    • etc., typically with usage of inheritance
    I think the point is to design it in way, which satisfies your exact requirements.

Re: High level OOP query
by stonecolddevin (Parson) on Oct 25, 2013 at 21:20 UTC

    Typically, I initialize things immediately that aren't going to block my application (meaning, things that aren't CPU or memory intensive). Then, I lazily load everything else, either through setters, or even more lazily, when I want to persist something through a method call (say I have a save() method, I'll initialize whatever I need prior, then if I have an attribute that doesn't need to be set prior to calling save, say something like, retrieve_siblings(), which we will say hits a database, or something else that generates a thumbnail that hasn't been cached yet. That way you're calling it at the last possible moment and not creating a bottleneck in your application unnecessarily. Of course, this is a bit further down the road, there's no need for optimization until you've actually discovered slow parts in your code.

    I also strongly suggest you learn how Moose and Moo work. They bring a sane object system into the Perl land and have some very, very good developers working on them.

    Three thousand years of beautiful tradition, from Moses to Sandy Koufax, you're god damn right I'm living in the fucking past

      I also strongly suggest you learn how Moose and Moo work. They bring a sane object system into the Perl land and have some very, very good developers working on them.

      Moo/Moose later :)

      I think its better to first learn why/when/what/whichway ... and only later deal with mechanics/implementation/how-details like moo/moose/badger...bless

      To wit http://www.developer.com/author/Matt-Weisfeld-81520.htm / https://en.wikiversity.org/wiki/Object_Oriented_Software_Design#Introductory_Articles

      • The Object-Oriented Thought Process
      • Moving from Procedural to Object-Oriented Development
      • Object Relationships
      • Thinking in Objects
      • Furthering the Object-Oriented Mindset
      • Exploring Encapsulation
      • Hiding Data within Object-Oriented Programming
      • Protecting Data through Object Oriented Programming
      • Putting an Object in a Safe State
      • The Components of a Class
      • The Evolution of Object-Oriented Languages
      • Object Responsibility
      • Object Construction
      • Inside Constructors
      • Encapsulation vs. Inheritance
      • Packaging Objects to Preserve Encapsulation
      • Object Signatures
      • Object Serialization
      • Connecting to a Database with JDBC
      • Using More Advanced JDBC Features
      • Serializing an Object via a Client/Server Connection
      • Objects and Client/Server Connections
      • Primitives and Object Wrappers
      • Objects and Collections
      • Objects and Collections: Vectors
      • Objects and Collections: ArrayLists
      • Objects and Interfaces
      • Designing with Interfaces and Abstract Classes

      Building Skills in Object-Oriented Design — S.Lott Building Skills in Object-Oriented Design — Step-by-Step Construction of A Complete Application — Roulette / Craps / Blackjack / Testing

      Why yes, this is just an opportunity to link some new-to-me stuff I found :)

        I say Moose/Moo now, because that completely sidesteps the frustrations of having to figure out how to coerce native perl into doing OO. It's got a fairly standard OO syntax that you will be able to translate OO concepts into without too much hassle. Then, you can backtrack and learn how it's done with perl sans Moo(se) and it will make much more sense.

        Three thousand years of beautiful tradition, from Moses to Sandy Koufax, you're god damn right I'm living in the fucking past

Log In?
Username:
Password:

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

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

    No recent polls found