Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

How do I go from procedural to object oriented programming?

by Lady_Aleena (Priest)
on Apr 20, 2015 at 20:43 UTC ( [id://1124071]=perlquestion: print w/replies, xml ) Need Help??

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

(Jenda corrected me on what my subroutines are. They are procedures not functions. The title of this node and where ever the word, or any derivatives of, function has been changed to procedure.)

Going from procedural programming to object oriented programming has been on my mind a lot lately. I have been told by a couple of people my code is very close to being OO, however when I gave OO a try the first time, I was told I was doing it all wrong. I would like to see how close I am, but I am having a hard time learning objects because the tutorials I have found start writing code right away. I have yet to find a tutorial which starts with the objective of the objects being written. For example I want...

Criminal Minds is a 2005 television series which is still running.

Iron Man is a 2008 film based on comics by Marvel Comics.

The tutorials also do not show the data being munged up front like...

my %movies_data = ( 'Firefly' => { 'title' => 'Firefly', 'start year' => '2002', 'end year' => '2003', 'media' => 'tv', 'based on' => undef, 'company' => undef, }, 'Criminal Minds' => { 'title' => 'Criminal Minds', 'start year' => '2005', 'end year' => 'tbd', 'media' => 'tv', 'based on' => undef, 'company' => undef, }, 'The 10th Kingdom' => { 'title' => 'The 10th Kingdom', 'start year' => '2000', 'end year' => '', 'media' => 'miniseries', 'based on' => undef, 'company' => undef, }, 'Iron Man' => { 'title' => 'Iron Man', 'start year' => '2008', 'end year' => '', 'media' => 'film', 'based on' => 'comics', 'company' => 'Marvel Comics', }, 'Tin Man' => { 'start year' => '2007', 'title' => 'Tin Man', 'end year' => '', 'media' => 'miniseries', 'based on' => 'novel', 'company' => 'L. Frank Baum' }, 'The Avengers (1998)' => { 'title' => 'The Avengers (1998)', 'start year' => '1998', 'end year' => '', 'media' => 'film', 'based on' => 'television series', 'company' => 'Thames Television', }, );

They all start right in on the objects, leaving me completely in the dark about what the end goal for the objects is. From above you know my objective and have the data to reference while reading the procedures I wrote to get to my objective. The whole module is here if you would like to see the bigger picture.

package Movies::LookUp; use strict; use warnings FATAL => qw( all ); use Lingua::EN::Inflect qw(A PL_N NUM NUMWORDS inflect); ############################# # insert %movies_data here! # ############################# my $current_year = (localtime())[5] + 1900; # returns the entire movies hash. sub movie_hash { return %movies_data; } # returns a hash ref for a single movie. sub movie { my ($movie,$caller) = @_; if (!$movies_data{$movie}) { warn $caller ? "$caller: $movie not in database" : "$movie not in +database"; } return $movies_data{$movie}; } # returns the start year of a movie sub start_year { my ($imovie) = @_; my $movie = movie($imovie,'start_year'); return $movie->{'start year'}; } # returns a numeric end year of a movie for comparisons sub end_year { my ($imovie) = @_; my $movie = movie($imovie,'end_year'); my $end_year = $movie->{'end year'} ? ($movie->{'end year'} eq 'tbd' + ? $current_year : $movie->{'end year'}) : $movie->{'start year'}; return $end_year; } # returns a string with the run time of a television series. sub run_time { my ($imovie) = @_; my $movie = movie($imovie,'run_time'); my $run_text = undef; if ($movie->{'media'} eq 'tv') { if ($movie->{'end year'} eq 'tbd') { $run_text = "which is still running"; } elsif (end_year($movie->{'title'}) - start_year($movie->{'title'}) + > 0) { my $run_time = end_year($movie->{'title'}) - start_year($movie-> +{'title'}); $run_text = inflect("which ran for NUMWORDS($run_time) PL_N(year +,$run_time)"); } } return $run_text; } # returns the media type of a movie sub media { my ($imovie) = @_; my $movie = movie($imovie,'media'); my $media = $movie->{'media'} eq 'tv' ? 'television series' : $movie +->{'media'}; return $media; } # returns what the movie is based on and by who or what sub basis { my ($imovie) = @_; my $movie = movie($imovie,'basis'); my $basis = $movie->{'based on'} ? qq(based on the $movie->{'based o +n'} by $movie->{'company'}) : undef; return $basis; } # returns a string with nearly all the properties of a movie sub movie_is { my ($movie) = @_; my $start = start_year($movie); my $media = media($movie); my $basis = basis($movie); my $runtime = run_time($movie); my $movie_is = A(join_defined(' ',[$start,$media,$basis,$runtime])). +'.'; return $movie_is; } 1;

Now putting it all together to get my objective.

use strict; use warnings FATAL => qw( all ); use Movies::LookUp qw(movies movie_is); my %movies = movie_hash(); for my $movie (sort keys %movies) { my $movie_is = movie_is($movie); print "<p>$movie $movie_is</p>\n"; }

I would like to know how close I am to having objects, what they would look like if I am close, and if there is anything which does not fit into OO. Is there anywhere I need to change my thinking (which will be hard since I have been doing things as above for a long while now)?

If any of the OO tutorials were written with the objective, data, code, and wrap-up in that order; I might get them.

Another reason I am doing this is to get my mind off of my pain and impending surgery. I am doing everything I can think of to get my mind off of them and stave off panic. Would you please help me?

I hope I am not asking too much. Please sit back and enjoy a cookie.

No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
Lady Aleena

Replies are listed 'Best First'.
Re: How do I go from procedural to object oriented programming?
by jeffa (Bishop) on Apr 20, 2015 at 21:42 UTC

    How close? Very close. Why don't you install Moose and then try this code out. Feel free to ask lots of questions.

    #!/usr/bin/env perl package Base; use Moose; has title => ( is => 'rw', isa => 'Any' ); has start_year => ( is => 'rw', isa => 'Any' ); sub to_string { my $self = shift; return join "\n", ' Title: ' . $self->title, 'Start Year: ' . $self->start_year, ; } package Movie; use Moose; extends 'Base'; has based_on => ( is => 'rw', isa => 'Any' ); has company => ( is => 'rw', isa => 'Any' ); sub to_string { my $self = shift; return join "\n", $self->SUPER::to_string, ' Based On: ' . $self->based_on, ' Company: ' . $self->company, '', ; } package TV; use Moose; extends 'Base'; has end_year => ( is => 'rw', isa => 'Any' ); sub run_time { my $self = shift; return $self->end_year - $self->start_year; } sub to_string { my $self = shift; return join "\n", $self->SUPER::to_string, ' End Year: ' . $self->end_year, ' Run Time: ' . $self->run_time, '', ; } package main; use strict; use warnings; my %movies_data = ( Firefly => { title => 'Firefly', start_year => '2002', end_year => '2003', media => 'tv', }, 'The Avengers' => { title => 'The Avengers', start_year => '1998', end_year => '', media => 'film', based_on => 'television series', company => 'Thames Television', }, ); my @objects; for (keys %movies_data) { if ($movies_data{$_}{media} eq 'film') { push @objects, Movie->new( $movies_data{$_} ); } elsif ($movies_data{$_}{media} eq 'tv') { push @objects, TV->new( $movies_data{$_} ); } } print $_->to_string, $/ for @objects;

    UPDATE (5/6/2015)
    I was not honestly expecting the OP to be sincere, but i really love these kinds of "problems" and am glad to have posted regardless. Something else i would love to share is MooseX::AbstractFactory. By only adding one new class (and not changing a line in the others) the decision of which object to create is moved to the data itself. Very nice:

    package Factory; use MooseX::AbstractFactory; implementation_class_via sub { shift }; package main; use strict; use warnings; my %movies_data = ( Firefly => { title => 'Firefly', start_year => '2002', end_year => '2003', media => 'TV', # <--- real class name }, 'The Avengers' => { title => 'The Avengers', start_year => '1998', end_year => '', media => 'Movie', # <--- real class name based_on => 'television series', company => 'Thames Television', }, ); my @objects; for (keys %movies_data) { push @objects, Factory->create( $movies_data{$_}{media}, $movies_d +ata{$_} ); } print $_->to_string, $/ for @objects;

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      Moose under CGI? Just say no
        Moose under C G I? Just say no

        What a completely pointless comment. Are you suggesting that one cannot use Moose because they are still using C G I? The reason I put spaces in "C G I" is because if one searches for C G I without spaces, the only match is yours. This means you are the only one who mentioned it at the time. Why? So which is it: use C G I or use Moose?

Re: How do I go from procedural to object oriented programming?
by NetWallah (Canon) on Apr 20, 2015 at 21:35 UTC
    The main concept in OO is that you construct a "Class" - representing generic properties, then you "Instantiate" one or more instances of that class.

    In your case, the "Class" that stands out is "Movie" (Note - this is singular).
    A movie has properties 'title', 'start year' , 'end year' , 'media' .. etc .

    You would instantiate an instance of the 'Movie" class by :

    my $m = Movie::->new (title=>'Great gatsby', year ....);
    For your case, it would also be helpful to have a different class for a COLLECTION of movies (and to enable search).

    Let me know if this is heading in a direction that means something to you. I'm sure other monks will also chime in and offer more detailed code.

            "You're only given one little spark of madness. You mustn't lose it."         - Robin Williams

Re: How do I go from functional to object oriented programming?
by Jenda (Abbot) on Apr 20, 2015 at 23:32 UTC

    $functional ne $procedural

    I seriously doubt you are comming from Haskell, Clean, oCaml or Lisp.

    Update after Lady_Aleena's update: within this program all your subroutines are functions. The naming of the programming paradigms is a little confusing and the distinction is fairly blurred by gradual introduction of originally functional concepts to (mainly) imperative languages and imperative features to (some mainly) functional languages.

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

      Jenda, many many moons ago, I was talking with someone, I can not remember who, in the chatterbox; and he, I remember the person being male, called my subroutines "functions". Since then I have thought of my subroutines as "functions". It appears I have been wrong in my thinking. I do tend to have a lot of faith in the people I talk with in the chatterbox so I do not fact check them when they tell me something (unless it sounds so absurd I have to verify). This is an easy shift in my thinking unlike going from procedural programming to object oriented programming.

      You are invited to change the name of your reply node to reflect the correction (functional to procedural) as are all of the other respondents. I have made the change in the title of this node.

      Thank you for stopping by and correcting this minor(?) error.

      Special note: This is my 300th node.

      No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
      Lady Aleena
        You can keep calling your subroutines functions even if they are procedures. Everybody calls them functions inside the context of imperative programming.

        Functional programming was a obscure thing until a few* years ago. If I recall correctly, the popular meaning for functional programming back them was "programming using functions as first class objects". For instance, Lisp was considered a functional programming language because it was able to build, pass and call functions and closures at runtime, in opposition to other lower level languages as C which didn't support closures and couldn't build functions dynamically.

        Then, the *real* functional programming wave toke over, there was an explosion of new functional languages and functional programming became synonymous to using functions without side effects as in the mathematical sense.

        Nowadays, the name "function" is still used to designate procedures. Any programmer using an imperative language as C/C++, Python, Perl or even old-school functional languages as Lisp will use the term "function" to designate both functions and procedures. Very few would take the time to consider the difference and state it explicitly. AFAIK, the only imperative language making a difference between functions and procedures is Eiffel and then it uses a completely different nomenclature.

        In summary, you can call your subs functions even if you don't do functional programming. And say that you do procedural programming, or more commonly, imperative programming (that subsumes both the procedural and object oriented paradigms).

        *) I would say until around the turn of the millennium... but it is quite subjective.

Re: How do I go from procedural to object oriented programming?
by choroba (Cardinal) on Apr 20, 2015 at 22:06 UTC
    Your code is indeed very close to Object Oriented. I translated part of it to Moo:

    I don't like the result much, though. When writing OOP code, you should concentrate on methods (what the objects do), not on the attributes (what properties the objects have). I haven't studied the full code to find out more than shown here, but e.g. the run_time method seems ugly.

    Update: Added isa checks.

    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Hello choroba and thank you for stopping by. I have a few specific questions about the code you provided. I will be writing up something more general as a reply to my OP to show the true breadth of the module.

      1. How are you getting %movies_data from the module to the script below?
        #!/usr/bin/perl use strict; use warnings; # Movie is the only package name I found in the module, # so I'm guessing it is also the module's name. use Movie; for my $title (sort keys %movies_data) { my $movie = 'Movie'->new( title => $title, map { (my $t = $_) =~ s/ /_/; $t => $movies_data{$title}{$_} } keys %{ $movies_data{$title} }); print $movie->movie_is, "\n"; }
        I see %movies_data but I don't see how you got it from the module. %movies_data is not something I want to have to copy and paste to the three scripts which use it now.
      2. Why did you constrain media and based_on? media could grow to include web series, music videos, and even video games (interactive movies). based_on includes so much more than the few things I included in the sample data. There are movies based on video games, board games, role-playing games, plays, etc. I'm waiting for the SyFy Original movie Qwerty vs. Dvorak which would be based on computer equipment without a company.
      3. How would your code handle movies with the start year "tbd"?
      4. It is easy to strip off the Moo formatting to make it normal OO? My web host does not have Moo installed.
      5. Does this code work in perl 5.8.8?

      A side note: run_time is not as bad as some of the other subroutines in the module which are truly hairy.

      No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
      Lady Aleena
        1. My example was not a module, it was a script with an embedded package. package has a block scope, so to create a real module, only save the portion of the script that defines the package. The rest (including the %movies_data hash) belongs to the main package, i.e. to the script.
        2. I prefer to constrain values. You can add any values you need, but checking they belong to a given set can still help you: it will catch typos in newly added data and provide a list of all possible values when you're not sure what category to use.
        3. The same way as your code, i.e. there's no particular code related to the case (maybe because there's no such movie in the data?).
        4. It's possible. It can take some time, but it isn't hard.
        5. I haven't tested it, but I think so. I'm not aware of any 5.10+ features used in the code.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: How do I go from procedural to object oriented programming?
by marinersk (Priest) on Apr 20, 2015 at 22:29 UTC

    I'll let the others continue their guidance on the how of OO, but I wanted to point out the one piece of information which transformed my understanding of OO. I believe it came from Thinking in Java (for me, second edition).

         The tutorials also do not show the data being munged up front...

    That's because the very point of Object Oriented programming is to not think about the data object in terms of the data and how to store and move it, but to think of it as the real world thing it is supposed to represent.

    Edit: s/data/object/

      So, you don't start by creating a monster hash called %movies_data. That would be thinking about the data. OO is about thinking about the thing.

      So what you need is an object called movie.

      That object will represent the movie. Think of the object as a movie.

      Don't fret about the data yet.

      Okay, so you need an object called movie.

      So how do we do that? This is OO -- it's all about the thing. So let's back off a bit and think about what a movie is.

      A bunch of these would be called movies. And that's a clue to the next step.

      A cat is a type of object. A house is a type of object. A movie is a type of object. They are all objects; but they are all different. We could say that they are somewhat defined by their attributes. So we will need to declare what type of object movie will be.

      In OO, we call that a Class.

      So we'll create a Class called Movies, and in it, define what attributes movies have.

      Okay. So we need a Class called Movies. The other folks on this thread will be far better suited to give technical details. I have no need for objects in my world, and use them only when I have to in order to use somebody else's Perl Module.

      But, in general, you will define an object called Movies, and in that definition, you will indicate that movies have attributes such as:

      • title
      • start year
      • end year
      • media
      • based on
      • company

      Once you've defined your Movies Class, you're almost ready for the second earth-shifting part of converting to OO.

      And the final shift necessary to think in Object-Oriented terms: Methods.

      When you defined your Moviesclass, you would also have put in subroutines to perform tasks. Simple ones like setTitle , setMedia, that sort of thing. And more. Again, I leave it to the OO-savvy folks to get you details. This is about the shift in consciousness. About how to think in an object-oriented manner.

      But you'll invoke those methods with a syntax something like this:

           $movies->setMedia("tv");

      You've no doubt seen this sort of thing in your use of modules. Hopefully, now you see why.

      There's a ton of stuff to learn regarding inheritence, but I'd let that go at first and let the lessons come as they are needed.

      Only one step left in our journey -- which we have already taken, but now will describe: Instances.

      Okay. You've defined a Class called Movies.

      The next step will probably be somewhat familiar to you, especially if you've used Perl Modules that are OO.

      You're ready to start mucking about with your first movie object -- Firefly.

           my $movie = Movies->new(title=>"Firefly");

      I may have the syntax wrong -- as I've already indicated, I don't do OO except when required, and then I beat the Synopsis examples with a cudgel until I get something out of it that seems to do what I want.

      But you -- Brave OO soldier that you are -- now you have created a movieobject from the Moviesclass.

      You can do stuff with it now.

      You have a class called Movies.
      You have an object called movie. (It's currently stored in your Perl scalar called $movie).

      But this simple example belies the power of OO.

      You can now create multiple instances of that object.

      Firefly is a movie (as defined in your original hash, anyway). But so is Iron Man. And so on.

      Each of these is an instance of the movieobject, from the Moviesclass.

      You can create any number of movieobjects as you need in your code, for whatever purpose.

      my $oldFavorite = Movies->new(title=>"FireFly"); my $newFavorite = Movies->new(title=>"Iron Man"); $oldFavorite->setMedia("tv"); $newFavorite->setMedia("film"); $oldFavorite->demote(); $newFavorite->promote();

      This wasn't meant teach you to code OO; it was meant to demonstrate how OO changes the way you look at programming.

      If it helps, great.
      If not -- well, I tried.

      Good luck!

        One of the benefits of OO is being able to organize your thoughts and design your solution. OO tends to persuade the coder to not just bash out a bunch of code, but instead plan and refactor. What you did was post 7 nodes of stream-of-thought off the top of your head consciousness without bothering to write a rough draft and refactor your repeated thoughts. Very non-OO.

      Agree about Java. Learning Java helps too. Very much. You don't even need to read all the book, cause a lot of it is about Java classes. Just read language concepts.

      The main point is: perl allows you to do anything anyhow and it works. Java forces you to use OOP. Reading, some helloworld codings with change your POV to the coding at all.

      And after that you see that perl is really awesome (except exceptions). You may make fast and dirty, or you may do correct way and let's say 'enterprise'. Depending on goals.

Re: How do I go from procedural to object oriented programming?
by SimonPratt (Friar) on Apr 21, 2015 at 09:23 UTC

    The biggest difference I can think of between procedural programming and OO programming is where state logic is maintained.

    In procedural programming, state logic tends to be maintained by the programmer within the main body of the program. This means that in the main body of the program you have to maintain all the gory guts of everything you are working with. In order to try and deal with all of this, procedural programmers do tend towards an OO style, in an attempt to maintain sanity.

    In OO programming state logic is maintained by the objects themselves (I am a movie, I know what state I am in and I know how to modify that state based on the actions I let you perform on me). This leaves the main body of the program free to describe the logic of what is happening.

    use 5.16.2; use warnings FATAL => qw( all ); package movie; sub new { my $class = shift; my %params = @_; my $self = bless {}, $class; $self->title($params{title}); $self->startyear($params{startyear}); $self->endyear($params{endyear}); $self->media($params{media}); $self->basedon($params{basedon}); $self->company($params{company}); return $self; } sub title { my $self = shift; my $title = shift; $self->{title} = $title if $title; return $self->{title}; } sub startyear { my $self = shift; my $startyear = shift; $self->{startyear} = $startyear if $startyear; return $self->{startyear}; } sub endyear { my $self = shift; my $endyear = shift; $self->{endyear} = $endyear if $endyear; return $self->{endyear}; } sub media { my $self = shift; my $media = shift; $self->{media} = $media if $media; return $self->{media}; } sub basedon { my $self = shift; my $basedon = shift; $self->{basedon} = $basedon if $basedon; return $self->{basedon}; } sub company { my $self = shift; my $company = shift; $self->{company} = $company if $company; return $self->{company}; } sub print { my $self = shift; say "I am a movie and my title is $self->{title}"; } package library; sub new { my $class = shift; my %params = @_; my $self = bless {}, $class; $self->name($params{name}); $self->add_movie($_) for @{$params{movies}}; return $self; } sub name { my $self = shift; my $name = shift; $self->{name} = $name if $name; return $self->{name}; } sub add_movie { my $self = shift; my $movie = shift; $self->{movies}{$movie->title()}{movie} = $movie; $self->{movies}{$movie->title()}{location} = 'in'; } sub print { my $self = shift; say "I am a library called '".$self->name()."' and contain the fol +lowing movies:"; say "\t".$_->{movie}->title() foreach values %{$self->{movies}}; } package main; my %movies_data = ( 'Firefly' => { 'title' => 'Firefly', 'startyear' => '2002', 'endyear' => '2003', 'media' => 'tv', }, 'Criminal Minds' => { 'title' => 'Criminal Minds', 'startyear' => '2005', 'endyear' => 'tbd', 'media' => 'tv', }, 'The 10th Kingdom' => { 'title' => 'The 10th Kingdom', 'startyear' => '2000', 'endyear' => '', 'media' => 'miniseries', }, 'Iron Man' => { 'title' => 'Iron Man', 'startyear' => '2008', 'endyear' => '', 'media' => 'film', 'basedon' => 'comics', 'company' => 'Marvel Comics', }, 'Tin Man' => { 'start year' => '2007', 'title' => 'Tin Man', 'media' => 'miniseries', 'basedon' => 'novel', 'company' => 'L. Frank Baum' }, 'The Avengers (1998)' => { 'title' => 'The Avengers (1998)', 'startyear' => '1998', 'media' => 'film', 'basedon' => 'television series', 'company' => 'Thames Television', }, ); my $library = library->new(name => "Lucky's"); foreach my $movie (values %movies_data) { $library->add_movie(movie->new(%$movie)); } $library->print();

    See how in main, there is now no logic about how to update state? (I'm using your hash to pass the required values to the movie object - The object itself has the logic for using (or not) the values provided). Main is now all about the logic of what you want to do (make a movie, make a library, add a movie to a library, print out the contents of the library, etc)

      Hello SimonPratt and thank you for stopping by. I have a few specific questions about the code you provided. I will be writing up something more general as a reply to my OP to show the true breadth of the module.

      1. Is Perl 5.16.2 required? I write for 5.8.8 since that is what my web host provides.
      2. How are you getting %movies_data from the module to the script below?
        #!/usr/bin/perl use strict; use warnings; # movie is the first package name I found in the module, # so I'm guessing it is also the module's name. use movie; my $library = library->new(name => "Lucky's"); foreach my $movie (values %movies_data) { $library->add_movie(movie->new(%$movie)); } $library->print();
        I see %movies_data but I don't see how you got it from the module. %movies_data is not something I want to have to copy and paste to the three scripts which use it now.
      3. Why doesn't end_year get a value if it does not have one? It needs an end year for run_time which you seem to have dropped.

      You do not need to worry about adding movies with this module. I enter the data into the file by hand since the last time I did anything with the data file with a script, I accidentally dropped a value causing me to have to redo my research for the value for a lot of movies.

      The module I am possibly rewriting has 7 hashes, 5 of which get sent out to scripts from the module. Some values of the hashes use the subroutines and other hashes to create some of the values. It is a big complex organism.

      No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
      Lady Aleena

        Kia ora, Lady_Aleena, sure

        1. No it isn't, just happens to be the version I use (faster than writing use strict; use features;. Also gives hints about the version of Perl I am using, in case people do run into issues with any scripts I have created)
        2. In the sample I provided, this is main:
          my %movies_data = ( 'Firefly' => { 'title' => 'Firefly', 'startyear' => '2002', 'endyear' => '2003', 'media' => 'tv', }, 'Criminal Minds' => { 'title' => 'Criminal Minds', 'startyear' => '2005', 'endyear' => 'tbd', 'media' => 'tv', }, 'The 10th Kingdom' => { 'title' => 'The 10th Kingdom', 'startyear' => '2000', 'endyear' => '', 'media' => 'miniseries', }, 'Iron Man' => { 'title' => 'Iron Man', 'startyear' => '2008', 'endyear' => '', 'media' => 'film', 'basedon' => 'comics', 'company' => 'Marvel Comics', }, 'Tin Man' => { 'startyear' => '2007', 'title' => 'Tin Man', 'media' => 'miniseries', 'basedon' => 'novel', 'company' => 'L. Frank Baum' }, 'The Avengers (1998)' => { 'title' => 'The Avengers (1998)', 'startyear' => '1998', 'media' => 'film', 'basedon' => 'television series', 'company' => 'Thames Television', }, ); my $library = library->new(name => "Lucky's"); foreach my $movie (values %movies_data) { $library->add_movie(movie->new(%$movie)); } $library->print();
          From this, you can see that $movie is a reference to each of the hashes contaning the movie info (startyear, endyear, etc). When I call movie->new(%$movie) it is a call to the new function in the package movie, passing in the de-referenced hash containing the movie information. new is expecting a hash of stuff and deals with it appropriately when it creates the movie object. As for copying it, you don't have to do this - You can store this information in a myriad of ways (I would suggest a read-only XML file that you can easily backup, which your scripts can load easily)
        3. It won't get a value if I don't give it one. I didn't implement run_time as I was just trying to show a quick sample of how to create an OO object. Implementing this should be pretty straight-forward (and I would expect no end_year would be handled appropriately by run_time

        Heh, certainly looks pretty meaty. In a more OO fashion, I would expect lookup to be a single function within a library class, that returns an object from the library (movie, tv show, whatever). Then getting any of the underlying state information of the object that the caller wants is placed back onto the caller, who simply asks the object for that piece of information.

        UPDATE: You can copy the original sample I provided as-is, stick it all into a single script, change the use 5.16.2; line to use 5.8.8;, save it and run it - It should work just fine (though might complain about say (not sure when this feature was implemented), which you can just change to print.

        Uhh, did a quick update to my previous script, to print the movies on individual lines, as per your OP. Notice how most of the work was done inside the classes, adding and extending function and procedure calls as necessary. main really is only having to deal with the what side of the equation, as opposed to the how, which is encapsulated inside the classes.

        use 5.16.2; use warnings FATAL => qw( all ); package movie; sub new { my $class = shift; my %params = @_; my $self = bless {}, $class; $self->title($params{title}); $self->start_year($params{start_year}); $self->end_year($params{end_year}); $self->media($params{media}); $self->based_on($params{based_on}); $self->company($params{company}); return $self; } sub title { my $self = shift; my $title = shift; $self->{title} = $title if $title; return $self->{title}; } sub start_year { my $self = shift; my $start_year = shift; $self->{start_year} = $start_year if $start_year; return $self->{start_year} // 'TBD'; } sub end_year { my $self = shift; my $end_year = shift; $self->{end_year} = $end_year if $end_year; return $self->{end_year} // 'TBD'; } sub media { my $self = shift; my $media = shift; $self->{media} = $media if $media; return $self->{media} // 'TBD'; } sub based_on { my $self = shift; my $based_on = shift; $self->{based_on} = $based_on if $based_on; return $self->{based_on} // 'TBD'; } sub company { my $self = shift; my $company = shift; $self->{company} = $company if $company; return $self->{company} // 'TBD'; } sub run_time { my $self = shift; my $result = $self->end_year(); $result = $self->start_year() if $self->media() eq 'film'; return $result; } sub print { my $self = shift; my $output = $self->title()." is a ".$self->start_year()." "; if ($self->media() eq 'tv') { $output .= "television series which "; if ($self->end_year()) { $output .= "completed in ".$self->end_year(); } else { $output .= "is still running."; } } else { $output .= $self->media()." based on ".$self->based_on()." by +".$self->company(); } say $output; } package library; sub new { my $class = shift; my %params = @_; my $self = bless {}, $class; $self->name($params{name}); $self->add_movie($_) for @{$params{movies}}; return $self; } sub name { my $self = shift; my $name = shift; $self->{name} = $name if $name; return $self->{name}; } sub add_movie { my $self = shift; my $movie = shift; $self->{movies}{$movie->title()}{movie} = $movie; $self->{movies}{$movie->title()}{location} = 'in'; } sub get_allmovies { my $self = shift; my @movies = map {$_->{movie}} values $self->{movies}; return @movies; } sub print { my $self = shift; say "I am a library called '".$self->name()."' and contain the fol +lowing movies:"; say "\t".$_->{movie}->title() foreach values %{$self->{movies}}; } package main; use XML::Simple; my @data = <DATA>; my $movies_data = eval { XMLin("@data") }; my $library = library->new(name => "Lucky's"); foreach my $movie (@{$movies_data->{movie}}) { $library->add_movie(movie->new(%$movie)); } $library->print(); say ""; foreach my $movie ($library->get_allmovies()) { $movie->print(); } __DATA__ <movies> <movie> <title>Firefly</title> <start_year>2002</start_year> <end_year>2003</end_year> <media>tv</media> </movie> <movie> <title>Criminal Minds</title> <start_year>2005</start_year> <end_year>tbd</end_year> <media>tv</media> </movie> <movie> <title>The 10th Kingdom</title> <start_year>2000</start_year> <end_year></end_year> <media>miniseries</media> </movie> <movie> <title>Iron Man</title> <start_year>2008</start_year> <end_year></end_year> <media>film</media> <based_on>comics</based_on> <company>Marvel Comics</company> </movie> <movie> <start_year>2007</start_year> <title>Tin Man</title> <media>miniseries</media> <based_on>novel</based_on> <company>L. Frank Baum</company> </movie> <movie> <title>The Avengers (1998)</title> <start_year>1998</start_year> <media>film</media> <based_on>television series</based_on> <company>Thames Television</company> </movie> </movies>
        the last time I did anything with the data file with a script, I accidentally dropped a value causing me to have to redo my research for the value for a lot of movies.
        Now that you have an offsite backup , that should be a simple matter of git revert or at most git cherry-pick … (at least as long as you keep it up to date, of course)
Re: How do I go from procedural to object oriented programming?
by einhverfr (Friar) on Apr 21, 2015 at 08:21 UTC

    I am going to comment on this from a different direction, namely *why* object-oriented programming. Understanding this makes a lot of things easier to understand.

    There are basically two kinds of bugs in software: those that involve doing the wrong thing with the right data, and those that involve doing the right thing with the wrong data. Of the two, the second is far harder to track down and fix because the data bacame wrong somewhere else and that has to be tracked down. For this reason state management is a central problem in programming.

    Looking at your code you seem close to structural programming. The idea of structural programming is you keep data together that belongs together and this makes it easier to determine when data is bad. With your hashes, this is what you are doing. It also simplifies assumptions your code has to make.

    The next step is to go object oriented and tie the data to behavior. Ideally state should only be changed by going through the behavior. This gives you fewer points to track down when looking for how data became bad. In Perl you use packages and bless to do this, but you can also use Moose which is a really nice framework.

    One reason to recommend Moose btw is that it makes the next step (after going OO) a lot easier, namely making your objects immutable. With immutable objects you have only one place to check data validity (the constructor) and so it becomes nearly possible to eliminate this whole class of bugs.

    This latter point gets you closer to functional programming because if your output is dependent only on your input (i.e. instead of changing an object you return a new one), you can manage state well enough to have programs which safely write programs.

Re: How do I go from procedural to object oriented programming? ( ooad ood modeling crc)
by Anonymous Monk on Apr 21, 2015 at 00:23 UTC

    You start with a story and some paper index cards ( Class Responsibility Collaboration(crc) cards ) because there is (no isa in crc)

    marinersk has the right idea, you should always start by asking questions, too many tutorials jump straight into writing code ... how can you write code before you know what its supposed to do?

    Similar to what marinersk is trying to convey but the important difference is you don't design around attributes -- otherwise you end up with a bag of attributes -- you're designing about responsibility, then the actions(methods) needed for such responsibility

    So this is what you currently have

    So you can see that some some reorganization is in order , and you need to step back and tell a story (and write it down), then create some class/responsibility/relationship cards and see how they fit together

    One class has too much responsibility and needs to be split up into two maybe three classes

    The other class is confused a little bit about its purpose

    And a third class is missing ... the classic pattern is MVC, you have Model (or the makings of a model), you have the beginning of a View, but there is no Controller

    For another comparison jeffa also starts with inheritance based on attributes, but its too early in design phase to think about attributes, esp inheritance based on attributes , inheritance isa crock best avoided

    Its like almost every tutorial on MVC... just takes for granted the questions what is the responsibility / what should it do, and jumps straight into bag of attributes, but you're supposed to hide the data behind an interface

    So here is the best next step you can take, tell a story, make some index cards, see how they fit together to help tell the story, and then share your thoughts with us, this is the beginning :)

Re: How do I go from procedural to object oriented programming?
by jdporter (Chancellor) on Apr 23, 2015 at 15:06 UTC

    Your get_hash (in Base::Data) really should be returning a reference to its hash variable, rather than a copy of the hash's contents. Not only is the copy unnecessary and potentially nontrivially costly, but having it return a hash ref will also make life easier for your callers, especially as you "upgrade" to OO. (and ditto, mutatis mutandis, for get_array.)

      Assuming you make such a change, here's my version of your main program:

      use Movie; my $movies_hr = get_hash( ... ); for my $v ( values %$movies_hr ) { bless $v, 'Movie'; } for my $v ( values %$movies_hr ) { printf "<p>%s %s</p>\n", $v->title, $v->movie_is; }

      It's a little unusual, but I'm blessing the "records" within your main hash directly in-place.

        And here's my OO version of your (abbreviated) module:

        package Movie; use strict; use warnings FATAL => qw( all ); use Lingua::EN::Inflect qw(A PL_N NUM NUMWORDS inflect); my $current_year = (localtime())[5] + 1900; sub title { $_[0]->{'title'} } # returns the start year of a movie sub start_year { $_[0]->{'start year'} } # returns a numeric end year of a movie for comparisons sub end_year { my $self = shift; $self->{'end year'} or return $self->{'start year'}; $self->{'end year'} eq 'tbd' and return $current_year; $self->{'end year'} } # returns a string with the run time of a television series. sub run_time { my $self = shift; $self->{'media'} eq 'tv' or return(); $self->{'end year'} eq 'tbd' and return "which is still running"; my $sy = $self->start_year; my $ey = $self->end_year; $ey == $sy and return "which ran for less than a year"; my $run_time = $ey - $sy; inflect("which ran for NUMWORDS($run_time) PL_N(year,$run_time)"); } # returns the media type of a movie sub media { my $self = shift; $self->{'media'} eq 'tv' ? 'television series' : $self->{'media'} } # returns what the movie is based on and by who or what sub basis { my $self = shift; $self->{'based on'} or return(); qq(based on the $self->{'based on'} by $self->{'company'}) } # returns a string with nearly all the properties of a movie sub movie_is { my $self = shift; A(join(' ', grep { defined $_ } $self->start_year, $self->media, $ +self->basis, $self->run_time )).'.'; } 1;

        Notice that I deleted the stuff about "insert %movies_data here!". Because this isn't where it should go. Instead, it should be placed in a "higher" level of the code -- whether that's the main program, or a containing module, or what. This module is only for representing individual movie records. Hence the name "Movie", not "Movies".

        Also, I renamed that last method (was "movie_is"), and I added a title method.

        Also, I replaced your fatal warn() with a croak, so that I could get a full stack dump. You can revert that easily enough.

Re: How do I go from procedural to object oriented programming?
by karlgoethebier (Abbot) on Apr 21, 2015 at 19:28 UTC

    Hi Lady_Aleena,

    Some bros (no nuns except you on PM as far as i know) mentioned Java, for various reasons.

    So i thought that it wouldn't be a bad idea to point you to Processing.

    Perhaps a more "visual approach" is what you need for further inspiration?

    If so, please take a look at A Car Class and a Car Object and all the other examples provided ebenda.

    The stuff provided there is less or more trivial but i think that this isn't a bad approach "to get the idea".

    And to be honest: I like trivial examples.

    My best regards, Karl (Rare Bird)

    «The Crux of the Biscuit is the Apostrophe»

      Nothing useful to say, just that I, too, am a nun. For the record.

      Kaiti
      Swiss Army Nerd
        "Nothing useful to say..."

        No, no. My pleasure. I remember - or i guess, as you like that we had a short conversation some time ago in the CB? I'm unsure about this.

        My best regards, Karl

        «The Crux of the Biscuit is the Apostrophe»

Re: How do I go from procedural to object oriented programming?
by Lady_Aleena (Priest) on Apr 27, 2015 at 02:29 UTC

    Quick note for those following this: I merged Movies::LookUp and Movies::Display into Movie, so new link to Movie.

    After reading everyone above, it looks like I will not get to keep all of my eggs in one basket. The reason I named this module LookUp was to show myself I could look up anything I have stored about the movies I have some (small) interest in. I should have just posted the entire ugly module instead of just the easy parts. I honestly thought I would get to keep my hashes (lines 21-268) in the module with the objects. I really do not know if the objects will work as I wish without the data being processed before the objects are called.

    If you are wondering what has taken me so long to respond, there were a few detours along the way. First I got distracted by the idea of creating a super-duper data getter. The one subroutine to rule them all. So, I wrote get_data and plunked it into Base::Data. (get_data is very similar to random in RolePlaying::Random.) I then applied it to my top nine subroutines which I cut down to five. Then there was the whole CGI discussion which lead me to switch from CGI to CGI::Minimal by editing 15 scripts. Then there was the advice to have my get_hash and get_array subroutines from Base::Data return refs instead of copies which I did by editing 32 files. (That task took me about an hour to track all those hashes and arrays down and make them refs.)

    When I saw the CRC (class, responsibility, and collaboration) cards, I realized I had subroutines which returned mixed in with subroutines which printed. So, I moved all the returning subroutines out of Display and put them in LookUp. (I left one returning subroutine in Display, but I have reasons.) I had a couple subroutines in my scripts too, so I moved them to LookUp as well. A few subroutines got renamed. So now I am more organized than I was before.

    After reading the direct replies, I realized I had to divorce my subroutines from the data. So, I went through and refactored almost all of my subroutines to be independent from the data above them. (There are two which are giving me trouble, but I will go into it in just a bit.) I also saw I showed you such a small picture of what the module is about. So to break some assumptions based on just what I initially posted, I am going to show you the big picture.

    Now, take a deep breath, close your eyes, and relax before you continue. Please.

    The breakdown of Movie::LookUp

    There are five different types of movie things. Movies could be in series and franchises and can have seasons and episodes within them. Here is a quick list of definitions.

    • franchise: a collection of series and movies on the same topic but not necessarily sharing a cosmology. (Example: Superman franchise)
    • series: a collection of movies sharing a cosmology. (Example: 1978 Superman series)
    • movie: a film, miniseries, or television series. Web series and some video games could also be included. (Examples: Superman (1978) and Smallville)
    • season: a collection of television or web episodes. (Example: the first season of Smallville)
    • episode: a part of a miniseries or television or web series. (Example: the pilot of Smallville)

    Here is a table showing what each has. A question mark before a "no" means it could be added in the future.

     franchiseseriesmovieseasonepisodeNotes
    filmminiseriestv
    title yesyesyesyesyesyesyes 
    counts yesyesno yesyesyesno 
    counts
    types
    • series
    • film
    • miniseries
    • television series
    • season
    • episode
    • film
    • miniseries
    • television series
    • season
    • episode
     
    • episode
    • season
    • episode
    • episode
       
    start_year yesyesyesyesyes?nono 
    end_year yesyesno no yes?nono 
    years_running yesyesno no yes?nono season gets this if at least start year added
    run_time yesyesno no yes?nono season gets this if at least start year added
    links yesyesyesyesyesno no season and episode could be given a Google link
    media ?no?noyesyesyes?no?no season and episode inherit from movie
    franchise and series can build from children
    genre ?no?noyesyesyesno no franchise and series can build from children
    about ?no?noyesyesyesno no franchise and series can build from children
    basis ?no?noyesyesyesno no franchise and series can build from children
    crossovers no no no yesyesno yes 
    series_text no no yesyesyesno no 
    mini_parts no no no yesno no no 
    programs yesyesno no no no no 

    There are several helper subroutines in this module. get_crossover returns a string for a single crossover for crossovers. search_link returns a link to a series or individual movies with are tv series for get_crossover and series_text. get_crossover and search_link are also two subroutines I can not divorce from the data. unquote_parts processes episode titles. nav_link is an independent subroutine which returns a link for navigating the Movies_by_series script so is used in Movie::Display.

    Wrapping up Movie::LookUp are these four subroutines which all up for rename so there will be no confusion with Movie::Display.

    • display_movie returns a sentence or two about a movie.
    • display_simple_movie returns a movie title and the start year, it is also a subroutine I can not find a way to divorce from the data.
    • display_episode returns the processed episode name with any crossovers.
    • display_option returns processed selection options.

    Movie::Display has the following subroutines.

    • print_series
    • print_movie
    • print_season
    • like (It is a returning subroutine, but I just left it here since it is so text heavy.)

    In the future I will be adding print_franchise.

    Take another deep breath, close your eyes again, and relax before you continue if you wish.

    I can not separate the data into its own module just yet because of get_crossover, series_text, and display_simple_movie. They all require the data to be there. I use start_year and end_year while creating the data for series and franchises. So, if I move the data to its own module, I would have a module loop. Movie::LookUp would use Movie::Data in the three former subroutines, and Movie::Data would use Movie::LookUp to create the data. It will not work! I have tried it before with no success. So either I keep all of this mess together or find a way of looking up the data needed in the former three subroutines in a different way. Movie::Data can use Movie::LookUp but not vice versa, imo.

    I am going to include the three problem subroutines only instead of pasting 317 lines. You can click the link up top to get to the whole (heavily edited) module. Maybe fresh eyes will see something I missed. I have been refactoring this for days as inspiration hit me.

    get_crossover
    # returns a string for a single crossover. The input is a hash ref. sub get_crossover { my ($crossover) = @_; my $episode = $crossover->{'episode'}; my $season = $crossover->{'season'}; my $program = $crossover->{'movie'}; my $cseries = $crossover->{'series'}; my $crossover_text = undef; if ($episode || $season || $program || $cseries) { my $season_text = $season ? "season $season" : undef; my $episode_text = $episode ? textify(qq( "$episode")) : ''; my $search = $cseries ? $cseries : $program ? $program : undef; my $id = $cseries && $series->{$cseries} ? [grep(defined,($program +,$season_text))] : $program && $movies->{$program} && $season_text ? +[$season_text] : undef; $crossover_text = search_link($search,$id).qq($episode_text); } return $crossover_text; }
    search_link
    # creates a link to the Movies_by_series page. It is used in series_te +xt, get_crossover, and elsewhere. sub search_link { my ($movie, $id) = @_; my $search = undef; my $texti = textify($movie); if (($series->{$movie} && $id) || ($movies->{$movie} && $id)) { $search = searchify($movie,$id); $texti = $movies->{$id->[0]} ? textify($id->[0]) : textify($movie +); } elsif($series->{$movie} || $movies->{$movie}) { $search = searchify($movie); } my $text = "<i>$texti</i>"; my $root_link = get_root('link'); my $search_text = $search ? anchor($text, { 'href' => "$root_link/Mo +vies/Movies_by_series.pl?series=$search" }) : $text; return $search_text; }
    display_simple_movie fixed (see update)
    # returns the title in italics and the start year in parentheses. sub display_simple_movie { my ($imovie) = @_; my $movie = movie($imovie, 'display_simple_movie'); my $title = textify($imovie); my $start = $movie->{'start year'} ? $movie->{'start year'} : undef; my $item; if ($movie->{'series'}) { my $group; if (@{$movie->{'series'}} > 1) { ($group) = sort { scalar @{$series->{$b}{'programs'}} <=> scalar + @{$series->{$a}{'programs'}} } @{$movie->{'series'}}; } else { $group = $movie->{'series'}[0]; } $item = search_link($group, [$movie->{'title'}]); } elsif ($movie->{'seasons'}) { $item = search_link($movie->{'title'}); } else { $item = "<i>$title</i>"; } $item .= $start ? " ($start)" : undef; return $item; }
    Fixed code
    # returns the title in italics and the start year in parentheses. sub display_simple_movie { my ($movie) = @_; my $title = textify($movie->{'title'}); my $start = $movie->{'start year'} ? $movie->{'start year'} : undef; my $item; if ($movie->{'series'}) { my $group; my @list = keys %{$movie->{'series'}}; if (keys %{$movie->{'series'}} > 1) { ($group) = sort { $movie->{'series'}{$b} <=> $movie->{'series'}{ +$a} } keys %{$movie->{'series'}}; } else { $group = $list[0]; } $item = search_link($group, [$movie->{'title'}]); } elsif ($movie->{'seasons'}) { $item = search_link($movie->{'title'}); } else { $item = "<i>$title</i>"; } $item .= $start ? " ($start)" : undef; return $item; }

    Hopefully after I get wholly organized, I can start seeing what classes, methods, and attributes I have here. Since classes appear to be on top of methods and attributes, I will need to know what should or should not be classes in this. Then I can dive into methods, then attributes. (It appears that is the order of things in OO.)

    Thank you all for trying to help me get my mind around objects.

    Update: display_simple_movie is fixed, though I am not entirely happy about how I had to do it. While series are being added to their movies, each movie in the series will have the total amount of movies in the series. $movie->{'series'} has gone from an array to a hash.

    No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
    Lady Aleena

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (4)
As of 2024-03-19 11:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found