开发者

OOP/Procedural Design Questions

We have a few Third Party Perl libraries for transferring files to/from, and encrypting/decrypting files using PGP, among other things. Currently, these libraries use redundant code, and execute the above-mentioned methods using system() and command-line binaries.

I'm looking to re-write these libraries, using more modules and OOP where necessary. I'm currently racking my brain about how I want to setup the main library, which Perl scripts would include to use Net::SFTP to put/get files, and Crypt::PGPSimple to encrypt/decrypt files.

Should a library module be written in OOP? Does that make sense? Or should methods be imported (and designed procedurally) as needed, and create Net::SFTP and Crypt::PGPSimple objects as necessary? I'm just not sure I want to create a Lib object, and initialize SFTP, PGP, Constants, etc. all in the new sub. I figure I see this type of class more like a Java class with static methods, but there should be only one SFTP object/connection (not sure if Net::SFTP already takes care of this?), and one Crypt::PGPSimple, etc.

Also, back to the redundancy, this library should also have a parent which defines functions which many of the Third-Party libraries use (FTP, PGP, etc).

I'm not looking for a definite answer, as there probably isn't one, but maybe how others approach a design like this, and what makes the most "sense".

Thanks

Update: Added sample code of my OOP Library Module, which also utilizes other objects (PGPSimple, SFTP). Let me know if you can think of a better design/implementation. Thanks again.

Lib.pm

use Crypt::PGPSimple;
use Net::SFTP;
use File::Copy;
use Log::Log4perl qw(get_logger :levels);
use File::Basename;

my %CONS = (
    RECIPIENT => "ClientName";
    URL       => 'ftp.host.com';
    USER      => 'user';
    PASS      => ''; # use subroutine to obfuscate the password
    PORT    开发者_如何学Go  => '22'
    HOME_DIR  => '/Home';
    IN_DIR    => '/Incoming';
    OUT_DIR   => '/Outgoing';
);
my %VARS;

# private member variables
my ($logger);

BEGIN {
    %VARS = (
        IS_PROD     => $L_is_prod ? 1 : 0;
        APPS        => $ENV{'APPS'};
        OUTDIR      => $ENV{'OUTDIR'};
        TIME_ZONE   => $ENV{"TZ"};
    );

    $logger = get_logger("Lib");
}

sub new {
    my ($class, $self) = @_;

    $self = {
        pgp     => _setup_pgp();
        sftp    => undef; # Don't create SFTP connection until we need it
    };

    return bless($self, $class);
}

sub _setup_pgp {
    my $pgp = Crypt::PGPSimple->new();

    $pgp->PgpVersion(6.5.8);
    $pgp->PgpExePath("/path/to/pgp-6.5.8");
    $pgp->PgpKeyPath("/home/username/.pgp"); # Set this based on environment
    $pgp->PublicKey("pubring.pkr");
    $pgp->PrivateKey("secring.skr");
    $pgp->Password(pp());
    $pgp->UserId();
    $pgp->PgpTempDir("/tmp/");
    $pgp->PgpTimeZone();
    $pgp->PgpVerbose(2);

    return $pgp;
}

sub _setup_sftp {
    # Create SFTP connection 
    my $sftp;
    my ($host, $user, $pass);

    $host = $CONS{URL};
    $user = $CONS{USER};
    $pass = $CONS{PASS};

    $sftp = _connect_sftp($host, (user => $user, password => $pass));
    return $sftp;
}

sub encrypt {

    my ($self, $plain_file) = @_;
    my $pgp = $self->{pgp};

    $logger->info("Setting \$pgp->PlainTextFile to $plain_file");
    $pgp->PlainTextFile($plain_file);
    $pgp->PgpFlags("e");

    my $result = $pgp->EncryptFile;
    if($result != 0) {
        $logger->info("Failed to successfully encrypt $plain_file. Error code: " . $pgp->ErrCode() . ", Result: " . $pgp->Result());
    }

    return $result;
}

sub put {

    my $self = shift;
    $self->{sftp} = _setup_sftp() if(!defined $self->{sftp});
    my $local = $self->{pgp}->EncryptedTextFile();
    my $remote = basename($local);
    ...
    $sftp->put($local, $remote)
    ...

}


I tend to use OO if I need some kind of state for each instance. But I think nothing is bad about a procedural approach, where no "state" is needed.

Concerning the single connection problem: we (my company) use a "service-class" (procedural), which returns a suitable connection - that is useful if using threads / forks or if there can be multiple connections possible (e.g. with different options).

Update: If you decide to go OO, I would strongly recommend using Moose if possible. It will save you a lot of time, work and bugs... Have a look, how blessed references work, but use Moose or Mouse for your code.


If it makes sense to organize your data and subroutines that way, take a look at Moose, which adds a lot of OOP semantic glue to Perl 5, including inheritance.


Design the interface whichever way makes the most sense to you, anyone else who will be using the module, and the way that you intend to use it. You can even make it dual-interface relatively easily (at least if you don't use Moose; I've never made a dual-interface module with Moose, but I suspect that Moose would fight against you pretty hard if you tried it) by implicitly creating a private instance which procedural calls are run against.

Personally, I tend to go OOP for anything that requires reusable data (state, settings, etc.) for the code to run against and semi-dual-interface (specifically, procedural code which tolerates being called as MyMod->proc_name in addition to MyMod::proc_name) when every call will include all necessary data as parameters.

In this specific case, I think I'd go with a straight OO interface, with one instance of the class per open connection, then perhaps extend it to dual-interface if most apps using it only require a single connection (or perhaps not; I prefer calling things with the OO syntax rather than dealing with exported subs).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜