Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Modules design pattern: abstraction vs incarnation (providing not so static data)

by Discipulus (Canon)
on Nov 20, 2020 at 12:23 UTC ( [id://11123892]=perlmeditation: print w/replies, xml ) Need Help??

Hello monks,

this post is a follow up of Module design for loadable external modules containing data but, after I squeezed my mind to spot the core of the problem and after finding some valid approach, I think it is worth a new Meditation.

A dark corner where the problem raise

In the past when I found myself, facing an error or an unexpected behaviour, thinking: "it is perl or it is me?" it was always my fault. This time maybe not. Or maybe this is a problem in the way I imagine new things and perl does not make it easy as usual. What I mean is: modules are intended as an abstraction of behaviours and scripts are generally the incarnation of these behaviours in the real life. A module can be loaded and can export subs or methods. A module can be tested because generally it does nothing but exporting abstract behaviours. Only in few rare cases a module exposes data and if so it is just some bare package variable intended to modify its internal behaviour like $Data::Dumper::Indent and even this simple use can be accomplished in other, nowadays preferred, ways like providing parameters to the constructor and providing accessors for these kind of things.

What happens if I need a new design pattern? I still need an abstraction, obviously, but I also need a serie of incarnations to be loaded indipendently upon request. These are not plugins that extend the main module with new functionalities: they are different incarnations of a mother abstraction.

In the Perl::Teacher example I need the abstraction of Perl::Teacher::Lesson but then I need the incarnation of Perl::Teacher::Lesson::first_lesson and Perl::Teacher::Lesson::second_lesson and so on.

Another project of mine ( my mad and fun Game::Term :) is stucked exactly for the very same reason. Infact I started to code ignoring the above problem and I have designed it to have incarnations (game scenarios in this case) as standalone scripts and this approach lead me to shell out when changing the current scenario, messing the whole thing (shelling out it is always a bad thing and behaves very differently on different platforms like linux and windows).

A note about data: in the current post and in the previous one with data I intend not static data but a possible longish serie of perl data as others objects, anonymous subroutines, mixed with some (few) more static fragments as texts and questions.

Abstraction/Incarnation and OO roles

I was suggested to use a role for this. Honestly I'm not a big fan of OO perl frameworks, or better saying it, I will be a big fan when I will have the need to use all the feautures they provide.
I understand a role as a trasversal behaviour applicable to different kinds of objects, traversing the simple schema of father-children inheritance. The classic example of the breakble role can be consumed by very different classes of objects like bones, cars, cookies.. But I have many incarnations to one and only one abstraction. So no transversal roles to compose. Well.. I can transform my Perl::Teacher::Lesson into a role, let say Perl::Teacher::Teachable but I cannot see any advantage over a simple inheritance.

A new design pattern?

What I imagined is an orchestrator, a super-object (in this case of the Perl::Teacher class, but the same is valid for the Game::Term project) instantiated inside a perlteacher.pl program shipped within the main distribution.

This super-object will be able to do many things related to all teaching activities (reading configuration file, interact with the user..) and its Perl::Teacher distribution will include the abstraction of what a course, a lesson, a talk, a question and a test are. But the main activity will be to load a course and its lessons in sequence.

Real courses are just containers of real lessons and are shipped separetly from the main distribution. Here is the new design pattern I see.

If you look at solution I provide below, you will see I prefere to use the constructor of the incarnation module to ship the meat to the super object. This pattern is vaguely similar to routes in modern web frameworks: fatty subroutines of behaviours and data. Well... in web programming we were told to separate the logic from the presentation and this sounds sane. I think my case is a bit different because I have mainly perl data (objects of other classes like Perl::Teacher::Test or Perl::Teacher::Question filling a lesson) in my incarnations. There is not static data to serve (as templates of html in the case of web programming) and for this reason I dislike the idea to have external, yaml or json, files containing the data to be served. Infact to use static external files I have to strictly describe their format and I loose the flexibility of a perl module (for example a lesson can provide a special kind of test defining a custom sub or loading another module).

Here I present some sketch of my approaches, for sake of semplicity not in seprated .pm files but I think you will get an idea.

Possible approaches

First option: use the constructor of the incarnation module

This is my preferred one.
The abstraction module ( My::Lesson in this example ) defines methods usable by its children. Children (incarnation modules) use the new constructor to fill in the object with all the data it needs. Filling the object is done using methods provided by the abstraction module My::Lesson

use strict; use warnings; # ABSTRACTION package My::Lesson; sub new{ my $class = shift; return bless { steps => [] }, $class; } sub add_step{ my $self = shift; push @{ $self->{ steps }}, @_; } # INCARNATION package My::Lesson::Example; our @ISA = ( 'My::Lesson' ); sub new{ my $class = shift; my $self = $class->SUPER::new; $self->add_step('one','two'); return $self; } # USAGE # Please note that this is only an example of usage. The real one will + be done by an object of the Perl::Teacher class, like in # # $teacher->current_lesson( My::Lesson::Example->new ) # # or something similar package main; my $lesson = My::Lesson::Example->new;

Second option: using a bare EXPORT in the incarnation module to provide data

The abstraction module does not need to provide methods to its children. The abstraction module becomes an incarnation loading a data structure from My::Lesson::Example

# INCARNATION package My::Lesson::Example; require Exporter; our @ISA = qw( Exporter ); our @EXPORT = qw( @steps ); my @steps = ( 'three', 'four' ); # ABSTRACTION package My::Lesson; use Module::Load 'autoload'; # or CPAN Module::Runtime sub new{ my $class = shift; my %opts = @_; # here must be some logic to load the appropriate incarnation +module. # paying attention to the @steps array (?) # # autoload from Module::Load should be the right tool (not tes +ted) # autoload $opts{lesson}; warn "autoloading $opts{lesson}"; return bless { steps => [ @steps ] }, $class; } # USAGE package main; my $lesson = My::Lesson->new( lesson => 'My::Lesson::Example' );

Note: first and second approach can be mixed letting the incarnation module authors to use the interface they prefere. Something like this should be enough:

# ABSTRACTION package My::Lesson; use Module::Load 'autoload'; sub new{ my $class = shift; my %opts = @_; if ( $class->isa( 'My::Lesson' ) ){ return bless { steps => [] }, $class; } elsif ( $class->isa( 'Exporter' ) ){ # autoload $opts{lesson}; warn "autoloading $opts{lesson}"; return bless { steps => [ @steps ] }, $class; } else{ die "incarnation method not recognized for class [$class]! +"; } }

third option: static data

File::ShareDir::Install allows you to install read-only data files from a distribution and File::ShareDir will be able to retrieve them. For reasons explained above I dont intend to use this solution for the Perl::Teacher project, but can be a viable solution, for example, to provide static maps to my scenarios in the Game-Term project. Thanks to kcott to show these modules. Anyway I dont see any advice against using __DATA__ in a module (if it is not a enormous amount of data): it will be accessible from within the module itself or from outside specifying the package name as described in Special Literals (taking care to close the filehandle when finished).

Conclusions

It seems very weird to me that this design problem does not occured before. As always there is the possibility that it is me :)
I tend to imagine very complex designs with a super-object able to rule and load a plethora of incarnations. In a ideal world my perlteacher.pl will consists of a mere use Perl::Teacher; my $teacher = Perl::Teacher->new(); $teacher->run;

The ability to switch from an incarnation to another, at runtime, in Perl is only available through modules. So I need an easy way to provide this mechanism and a clear interface to propose to incarnation modules authors, if any.

I think my preferred solution, providing the meat in the incarnation module constructor, it is not so nice as interface for eventual authors but is the more perlish one I found at the moment.

Any comment, suggestion, inspiration will be welcome.

Thanks for reading.

L*

There are no rules, there are no thumbs..
Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

Replies are listed 'Best First'.
Re: Modules design pattern: abstraction vs incarnation (providing not so static data)
by haj (Vicar) on Nov 20, 2020 at 14:19 UTC

    I find it rather difficult to advise on this article and would suggest a different approach: Step back and describe the problem you're actually trying to solve. You seem to be stuck in "solution space", with some design decisions already made - but these might actually get in the way.

    As an example, you write modules are intended as an abstraction of behaviours and scripts are generally the incarnation of these behaviours in the real life. I'd challenge that view and say that objects are the incarnations, and that you are (ab)using subclasses instead of different objects of the same class, each with their own attributes. Also, you seem to use scripts to store particular objects. All this might be justifiable, but I can't see how.

    Also, the decision to be a fan of OO frameworks only when I will have the need to use all the features they provide seems questionable. You might fare better if you decide to embrace them as soon as they help to solve your problem. I am pretty sure that almost no one needs to use all the features of Perl itself, yet many of us are Perl fans, and quite happy with it.

    So, perhaps the problem has occurred before. I don't recognize it. Chances are that some features of OO Perl frameworks have been created to solve your problem. Can you describe how you intend this to be used? Are users supposed to write their own modules, and how should they feed them to the system? Can you describe the problem without using the words "incarnation", "abstraction" and "design pattern"?

      hello haj and thanks for looking at my long post,

      did you also followed links I provided? Forget for the moment my Game::Term project and focus on the Perl::Teacher one.

      In Perl Automateaching -- part 1: brainstorming I presented the idea and I asked for suggestions on the implementation (how to judge perl documents) while in automateaching -- part 2: proof of concept I sketched out some viable approach to accomplish my task.

      In my mind Perl::Teacher and all its classes are the teaching framework and must be written by me. This is what I named abstraction in this post.

      No one line of code has stil been wrote for the Perl::Teacher module, so I have really took no techincal decisions at the moment, only the decision to process perl documents using PPI (ie: no code given into STDIN).

      By other hand all lessons contained in courses can be written as separated modules by anyone. This is what I named incarnation in this post. So in the future peraphs haj will publish on CPAN Perl::Teacher::Course::EN::PerlIntro and this will contain Perl::Teacher::Course::EN::PerlIntro::01_safetynet and Perl::Teacher::Course::EN::PerlIntro::02_strictures and so on.

      I hope the above satisfies your Are users supposed to write their own modules? question.

      I presented my doubts about how to design this Perl::Teacher class and its interface in Module design for loadable external modules containing data and shown a brief example in Re^2: Module design for loadable external modules containing data

      That said you are right to challange my point of view, and you are right when saying that objects are the incarnations all correct but does not change the point, or at list my point of view.

      > Also, you seem to use scripts to store particular objects.

      Here I dont follow you: where this sentence comes from? If from my post I must have wrote something in the very wrong way.

      About OO frameworks:

      > You might fare better if you decide to embrace them as soon as they help to solve your problem. I am pretty sure that almost no one needs to use all the features of Perl itself, yet many of us are Perl fans, and quite happy with it.

      Well.. you turned the example in a nice way :) and yes, erase the all word from my sentence and will be the same of your. I will embrace them with happines when they help me in some way. Read above posts: I dont see any gain using roles.

      I hope the above clarify your doubts, hoping now you can clarify mines :)

      L*

      There are no rules, there are no thumbs..
      Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

        I had a glance at the other articles when they appeared and re-read them before answering. First of all it is a bit of a misnomer: Your goal is not to actually teach: you want to present exercises and evaluate responses. Teaching needs a storyline, a concept of independent modules doesn't help with that.

        Your second article contains 460 lines of code but not a single line of POD - sorry, I didn't spend time to walk through that. It doesn't give the impression of No one line of code has stil been wrote.

        I almost suspected that you aren't aware of the technical decisions you already made. I don't mind PPI - that's what I'd call an "internal affair". But there are others:

        • You define courses and lessons as Perl modules. This is weird. Both courses and lessons are mostly data, in particular text, and not code. I don't believe that Perl modules are the best format to write lessons and exercises.
        • You have each course to be a separate class, inheriting from your base class. Why? Why not different objects of a class you're providing?
          For this particular decision, I might be able to hallucinate the problem which you actually want to solve: You want authors of courses and lessons to be able to distribute their work. I don't know whether that is what brought you to the idea to use modules and CPAN, but CPAN would allow to "install" different courses by their name, therefore offering some method for distribution. As far as I'm concerned, I'm out: I don't have a CPAN account.
        • You claim that you don't need to separate logic from presentation because you have mainly Perl data. Well, that's a direct consequence of the previous decision, isn't it? You surely know that web applications usually mix static data (templates) with database data, XML documents, and other dynamic data. The value comes with these dynamic data, not unlike to your situation.
        • You dislike the idea to have external, yaml or json, files containing the data to be served. That's a decision against technical solutions, but with a rather lame reasoning. It isn't you who writes the courses, right? Do you have enough authors who share your preferences?

        So, how did I guess that you use scripts to store objects? You wrote: I have designed it to have incarnations (game scenarios in this case) as standalone scripts and this approach lead me to shell out when changing the current scenario. If you can't get a scenario's attributes without running a script, then I guessed the script stores this scenario's data. This may be wrong, or at least irrelevant.

        And finally, about OO frameworks: I didn't mention roles, and I didn't even have them in mind. All problems can be solved just fine without roles, but roles can bring elegance by avoiding boilerplate code. But Perl's OO frameworks have a lot more to offer. The issue is that there are too many of them :)

        A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Modules design pattern: abstraction vs incarnation (providing not so static data)
by tobyink (Canon) on Nov 21, 2020 at 17:58 UTC
Re: Modules design pattern: abstraction vs incarnation (providing not so static data)
by karlgoethebier (Abbot) on Nov 22, 2020 at 10:55 UTC

Log In?
Username:
Password:

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

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

    No recent polls found