Perl 面向对象

  • 面向对象

    我们已经研究了 Perl 和 Perl匿名数组和哈希中的引用。Perl中的面向对象概念很大程度上基于引用以及匿名数组和哈希。让我们开始学习面向对象Perl的基本概念。
  • 对象基础

    从 Perl 如何处理对象的角度解释了三个主要术语。这些术语是对象方法
    • Perl中的对象仅仅是对知道其所属类的数据类型的引用。该对象作为参考存储在标量变量中。因为标量仅包含对对象的引用,所以同一标量可以将不同的对象保存在不同的类中。
    • 类是 Perl 中是包含创建和操作对象所需的相应方法的软件包。
    • 方法是 Perl 中是一个子程序,在包中定义。方法的第一个参数是对象引用或程序包名称,具体取决于方法是影响当前对象还是类。
    Perl 提供了bless() 函数,该函数用于返回最终成为对象的引用。
  • 定义类

    在 Perl 中定义一个类非常简单。一个类以最简单的形式对应于Perl包。要在Perl中创建一个类,我们首先构建一个包。
    软件包是用户定义的变量和子例程的独立单元,可以重复使用。Perl 软件包在Perl程序中提供了一个单独的名称空间,该名称空间使子例程和变量独立于与其他软件包中的子例程和变量的冲突。
    要在Perl中声明一个名为Person的类,我们要做-
    
    package Person;
    
    包定义的范围扩展到文件的末尾,或者直到遇到另一个 package 关键字。
  • 创建和使用对象

    要创建类(对象)的实例,我们需要一个对象构造函数。此构造函数是在包内定义的方法。大多数程序员选择将此对象构造函数方法命名为 new,但在Perl中可以使用任何名称。
    您可以将任何类型的Perl变量用作Perl中的对象。大多数Perl程序员都选择对数组的引用或对哈希的引用。
    让我们使用Perl哈希引用为Person类创建构造函数。创建对象时,需要提供构造函数,该构造函数是返回对象引用的包中的子例程。通过引用对程序包类的引用来创建对象引用。例如-
    
    package Person;
    sub new {
       my $class = shift;
       my $self = {
          _firstName => shift,
          _lastName  => shift,
          _ssn       => shift,
       };
       # Print all the values just for clarification.
       print "First Name is $self->{_firstName}\n";
       print "Last Name is $self->{_lastName}\n";
       print "SSN is $self->{_ssn}\n";
       bless $self, $class;
       return $self;
    }
    
    现在让我们看看如何创建一个对象。
    
    $object = new Person( "Moo", "Alex", 23234345);
    
    如果不想将任何值分配给任何类变量,则可以在构造器中使用简单的哈希。例如-
    
    package Person;
    sub new {
       my $class = shift;
       my $self = {};
       bless $self, $class;
       return $self;
    }
    
  • 定义方法

    其他面向对象的语言具有数据安全性的概念,以防止程序员直接更改对象数据,并且它们提供访问器方法来修改对象数据。Perl没有私有变量,但是我们仍然可以使用辅助方法的概念来操作对象数据。
    让我们定义一个辅助方法来获取人的名字-
    
    sub getFirstName {
       return $self->{_firstName};
    }
    
    另一个辅助函数来设置人的名字-
    
    sub setFirstName {
       my ( $self, $firstName ) = @_;
       $self->{_firstName} = $firstName if defined($firstName);
       return $self->{_firstName};
    }
    
    现在,让我们看一个完整的示例:将Person包和辅助函数保留在Person.pm文件中。
    
    package Person;
    
    sub new {
       my $class = shift;
       my $self = {
          _firstName => shift,
          _lastName  => shift,
          _ssn       => shift,
       };
       # Print all the values just for clarification.
       print "First Name is $self->{_firstName}\n";
       print "Last Name is $self->{_lastName}\n";
       print "SSN is $self->{_ssn}\n";
       bless $self, $class;
       return $self;
    }
    sub setFirstName {
       my ( $self, $firstName ) = @_;
       $self->{_firstName} = $firstName if defined($firstName);
       return $self->{_firstName};
    }
    
    sub getFirstName {
       my( $self ) = @_;
       return $self->{_firstName};
    }
    1;
    
    现在,让我们如下使用employee.pl文件中的Person对象-
    
    use Person;
    
    $object = new Person( "Moo", "Alex", 23234345);
    # Get first name which is set using constructor.
    $firstName = $object->getFirstName();
    
    print "Before Setting First Name is : $firstName\n";
    
    # Now Set first name using helper function.
    $object->setFirstName( "Mua." );
    
    # Now get first name set by helper function.
    $firstName = $object->getFirstName();
    print "Before Setting First Name is : $firstName\n";
    
    当我们执行上面的程序时,它产生以下结果-
    
    First Name is Moo
    Last Name is Alex
    SSN is 23234345
    Before Setting First Name is : Moo
    Before Setting First Name is : Mua.
    
  • 继承

    面向对象的程序设计具有非常好的和有用的概念,称为继承。继承仅表示父类的属性和方法可用于子类。因此,您不必一次又一次地编写相同的代码,只需继承父类即可。例如,我们可以有一个Employee类,该类继承自Person。因为员工是一个人,所以这被称为“isa”关系。Perl有一个特殊的变量@ISA,以帮助解决此问题。@ISA控制(方法)继承。
    以下是使用继承时要考虑的重点-
    • Perl在指定对象的类中搜索给定的方法或属性,即变量。
    • Perl搜索对象类的@ISA数组中定义的类。
    • 如果在步骤1或2中找不到任何方法,那么如果在@ISA树中找到一个方法,则Perl将使用AUTOLOAD子例程。
    • 如果仍找不到匹配的方法,则Perl在标准Perl库的一部分附带的UNIVERSAL类(包)中搜索该方法。
    • 如果仍然找不到该方法,则Perl放弃并引发运行时异常。
    因此,要创建一个新的Employee类以继承Person类的方法和属性,我们只需编写如下代码:将这些代码保存在Employee.pm中。
    
    package Employee;
    use Person;
    use strict;
    our @ISA = qw(Person);    # inherits from Person
    
    现在,Employee Class具有从Person类继承的所有方法和属性,您可以按以下方式使用它们:使用main.pl文件对其进行测试-
    
    use Employee;
    
    $object = new Employee( "Moo", "Alex", 23234345);
    # Get first name which is set using constructor.
    $firstName = $object->getFirstName();
    
    print "Before Setting First Name is : $firstName\n";
    
    # Now Set first name using helper function.
    $object->setFirstName( "Mua." );
    
    # Now get first name set by helper function.
    $firstName = $object->getFirstName();
    print "After Setting First Name is : $firstName\n";
    
    当我们执行上面的程序时,它产生以下结果-
    
    First Name is Moo
    Last Name is Alex
    SSN is 23234345
    Before Setting First Name is : Moo
    Before Setting First Name is : Mua.
    
  • 方法覆盖

    子类Employee继承了父类Person的所有方法。但是,如果您想在子类中覆盖这些方法,则可以通过提供自己的实现来实现。您可以在子类中添加其他功能,也可以在其父类中添加或修改现有方法的功能。可以按照以下步骤进行:修改Employee.pm文件。
    
    package Employee;
    use Person;
    use strict;
    our @ISA = qw(Person);    # inherits from Person
    
    # Override constructor
    sub new {
       my ($class) = @_;
    
       # Call the constructor of the parent class, Person.
       my $self = $class->SUPER::new( $_[1], $_[2], $_[3] );
       # Add few more attributes
       $self->{_id}   = undef;
       $self->{_title} = undef;
       bless $self, $class;
       return $self;
    }
    
    # Override helper function
    sub getFirstName {
       my( $self ) = @_;
       # This is child class function.
       print "This is child class helper function\n";
       return $self->{_firstName};
    }
    
    # Add more methods
    sub setLastName{
       my ( $self, $lastName ) = @_;
       $self->{_lastName} = $lastName if defined($lastName);
       return $self->{_lastName};
    }
    
    sub getLastName {
       my( $self ) = @_;
       return $self->{_lastName};
    }
    
    1;
    
    现在,让我们再次尝试在main.pl文件中使用Employee对象并执行它。
    
    use Employee;
    
    $object = new Employee( "Moo", "Alex", 23234345);
    # Get first name which is set using constructor.
    $firstName = $object->getFirstName();
    
    print "Before Setting First Name is : $firstName\n";
    
    # Now Set first name using helper function.
    $object->setFirstName( "Mua." );
    
    # Now get first name set by helper function.
    $firstName = $object->getFirstName();
    print "After Setting First Name is : $firstName\n";
    
    当我们执行上面的程序时,它产生以下结果-
    
    First Name is Moo
    Last Name is Alex
    SSN is 23234345
    This is child class helper function
    Before Setting First Name is : Moo
    This is child class helper function
    After Setting First Name is : Mua.
    
  • 默认自动加载

    Perl提供了其他任何编程语言都找不到的功能:默认子例程。这意味着,如果定义一个名为AUTOLOAD()的函数,则对未定义子例程的任何调用都会自动调用AUTOLOAD()函数。缺少的子例程的名称可以在该子例程中以$AUTOLOAD的形式访问。
    默认的自动加载功能对于错误处理非常有用。这是实现AUTOLOAD的示例,您可以以自己的方式实现此功能。
    
    sub AUTOLOAD {
       my $self = shift;
       my $type = ref ($self) || croak "$self is not an object";
       my $field = $AUTOLOAD;
       $field =~ s/.*://;
       unless (exists $self->{$field}) {
          croak "$field does not exist in object/class $type";
       }
       if (@_) {
          return $self->($name) = shift;
       } else {
          return $self->($name);
       }
    }
    
  • 析构函数和垃圾回收

    如果您以前使用面向对象编程进行编程,那么您将意识到有必要创建一个析构函数,以在完成使用对象后释放分配给该对象的内存。一旦对象超出范围,Perl就会自动为您执行此操作。如果要实现析构函数,该析构函数应负责关闭文件或进行一些额外的处理,则需要定义一个称为DESTROY的特殊方法。在Perl释放分配给它的内存之前,将在对象上调用此方法。在所有其他方面,DESTROY方法与任何其他方法一样,您可以在此方法内实现所需的任何逻辑。
    析构函数方法只是名为DESTROY的成员函数(子例程),在以下情况下会自动调用-
    • 当对象引用的变量超出范围时。
    • 未定义对象引用的变量时。
    • 脚本终止时
    • 当Perl解释器终止时
    例如,您可以将以下方法DESTROY放入您的类中-
    
    package MyClass;
    ...
    sub DESTROY {
       print "MyClass::DESTROY called\n";
    }
    
  • 面向对象的Perl示例

    这是另一个不错的示例,它将帮助您理解Perl的面向对象概念。将此源代码放入任何perl文件中并执行它。
    
    # Following is the implementation of simple Class.
    package MyClass;
    
    sub new {
       print "MyClass::new called\n";
       my $type = shift;            # The package/type name
       my $self = {};               # Reference to empty hash
       return bless $self, $type;   
    }
    
    sub DESTROY {
       print "MyClass::DESTROY called\n";
    }
    
    sub MyMethod {
       print "MyClass::MyMethod called!\n";
    }
    
    
    # Following is the implemnetation of Inheritance.
    package MySubClass;
    
    @ISA = qw( MyClass );
    
    sub new {
       print "MySubClass::new called\n";
       my $type = shift;            # The package/type name
       my $self = MyClass->new;     # Reference to empty hash
       return bless $self, $type;  
    }
    
    sub DESTROY {
       print "MySubClass::DESTROY called\n";
    }
    
    sub MyMethod {
       my $self = shift;
       $self->SUPER::MyMethod();
       print "   MySubClass::MyMethod called!\n";
    }
    
    # Here is the main program using above classes.
    package main;
    
    print "Invoke MyClass method\n";
    
    $myObject = MyClass->new();
    $myObject->MyMethod();
    
    print "Invoke MySubClass method\n";
    
    $myObject2 = MySubClass->new();
    $myObject2->MyMethod();
    
    print "Create a scoped object\n";
    {
       my $myObject2 = MyClass->new();
    }
    # Destructor is called automatically here
    
    print "Create and undef an object\n";
    $myObject3 = MyClass->new();
    undef $myObject3;
    
    print "Fall off the end of the script...\n";
    # Remaining destructors are called automatically here
    
    尝试一下
    当我们执行上面的程序时,它产生以下结果-
    
    Invoke MyClass method
    MyClass::new called
    MyClass::MyMethod called!
    Invoke MySubClass method
    MySubClass::new called
    MyClass::new called
    MyClass::MyMethod called!
    MySubClass::MyMethod called!
    Create a scoped object
    MyClass::new called
    MyClass::DESTROY called
    Create and undef an object
    MyClass::new called
    MyClass::DESTROY called
    Fall off the end of the script...
    MyClass::DESTROY called
    MySubClass::DESTROY called