PHP Day5:面向对象

作者: 王中阳 | 来源:发表于2016-06-21 21:34 被阅读161次

    笔者回顾对面向对象的理解

    1. 封装:类是将对象进行封装,把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
    2. 继承:单继承,多实现。 有继承,就会有父子关系。子类拥有父类所有的方法和属性。
    3. 抽象:一切皆对象,一切都可以进行抽象。抽象就是忽略内部的方法和实现,只关心和自己需求相关的部分。抽象包括过程抽象和数据抽象。重点关注和需求相关的,忽略和需求无关的,集中注意力在需求上。
    4. 多态:经典的实现是重载和重写。笔者有一段时间理解实现接口和继承父类也是多态性的体现(可能不太准确)
    • 面向对象的思想需要和实际项目结合,绝对不是纸上谈兵。这种思想不仅适用于我们编写代码开发项目,更适用于我们采用什么样的方法和思路去敏捷开发,调优升级。

    对象的三大特征

    1. 对象的行为:可以给对象添加哪些操作:比如开门,关门。就是对门这个对象的行为;
    2. 对象的形态:给对象施加方法时,对象的外形如何变化:门的颜色、尺寸、材料;
    3. 对象的表示:就是对象的唯一标识(身份证),最大的意义是区分在相同行为和形态下有什么不同。
    对象的特征

    面向对象的内容

    • 类:定义了一件事物的抽象特点。类的定义包含了数据的形式(常量、变量)以及对数据的操作(函数)。
    • 对象:是类的实例。
    • 成员变量:定义在类内部的变量。该变量的值对外是不可见的,可以通过成员函数访问。在类被实例化为对象之后,该变量即可称为变量的属性。
    • 成员函数:定义在类的内部,可用于访问对象的数据。
    • 父类:一个类被其他类继承,可将该类称为父类,基类或超类。
    • 子类:一个类继承其他类,可将该类称为子类或派生类。
    • 继承:继承是子类自动共享父类数据结构和方法的一种机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上进行,将这个已经存在的类所定义的内容作为自己的内容,并添加若干新内容。这就是继承非常好理解,关键字extends。
    • 多态: 多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。通俗的解释一下:不同的对象,收到同一消息可以产生不同的结果,这种现象就是多态。(反思一下自己之前对接口实现和类继承的理解也是说的通的)
    • 重载:简单讲就是在同一个类中,方法名相同,但是参数列表不同(参数个数,参数属性....)。像这种同名不同参的函数或者方法之间,互相称为重载关系。
    • 抽象性:是指将具有一致数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,其核心是:反应了与应用(需求)密切相关的信息,忽略那些无关紧要的内容。在项目中,任何类的划分都是主观的,但是必须与具体的应用需求相关。
    • 封装:封装是指将现实世界中存在的某个客体的属性和行为绑定在一起,并放置在一个逻辑单元内。
    • 构造函数:主要用来在创建对象时初始化对象, 即为成员变量赋值初始化。构造函数在创建对象的语句中总与new关键字一起使用。
    • 析构函数(destructor)与构造函数相反, 当对象结束其生命周期时,系统自动执行析构函数。析构函数往往做的是‘清理善后’的工作。
    • 延伸之析构函数详解

    构造函数与析构函数

    构造函数
    function __construct(){
        parent::__construct(); // 调用父类的构造函数必须显示的使用parent调用父类构造函数
        classname::__construct(); // 调用其他类的构造函数,classname是类名
        //其他操作
    }
    
    

    如果涉及多层继承,当调用parent::__construct()时,会沿着父类向上搜索,直到找到最合适的构造函数,例如:

    // 接上例
    
    class Parrot extends Birds{
        private $name;
        private $leg;
        private $wing;
        function __construct($name){
            parent::__construct($name); // 此时没有找到父类(Birds类)合适的构造函数,只能向上搜索,搜索到Animal类时,才找到合适的构造函数
            echo "鹦鹉类被创建!";
            $this->smackTalk();
            /*
            输出结果:
            "动物类被创建!"
            "鹦鹉说话!"
            */
        }
        function smackTalk(){
            echo "鹦鹉说话!";    
        }
    
    }
    

    如果想要依次调用几个父类的构造函数,可以使用类名直接调用构造函数,例如:

     function __construct($name,$leg){
           Animal::__construct($name); // 调用Animal构造函数
            Birds::__construct($name,$leg); // 调用Birds构造函数
    }
    
    析构函数
    • 析构函数是在销毁对象时,自动调用,不能显示的调用。
    • 析构函数不能带参数。
    • 在以下几种情况下可能会调用析构函数(但不一定):
      • PHP页面加载完毕之后;
      • unset()类;
      • 变量引用指向别的对象或值时;
    <?php
    class test{
        function __destruct(){
            echo "当对象销毁时会调用!!!";
        }
    
    }
    $a = $b = $c = new test();
    
    $a = null;
    unset($b);
    
    echo "<hr />";
    
    ?>
    

    此例子,如下图,有三个变量引用$a,$b,$c指向test对象,test对象就有3个引用计数,当$a = null时,$a对test对象的引用丢失,计数-1,变为2,当$b被unset()时,$b对test对象的引用也丢失了,计数再-1,变为1,最后页面加载完毕,$c指向test对象的引用自动被释放,此时计数再-1,变为0,test对象已没有变量引用,就会被销毁,此时就会调用析构函数。


    析构函数
    PHP构造函数 __construct
    void __construct ([ mixed $args [, $... ]] )
    
    function __construct( $par1, $par2 ) {
       $this->url = $par1;
       $this->title = $par2;
    }
    
    PHP析构函数 __destruct
    <?php
    class MyDestructableClass {
       function __construct() {
           print "构造函数\n";
           $this->name = "MyDestructableClass";
       }
    
       function __destruct() {
           print "销毁 " . $this->name . "\n";
       }
    }
    
    $obj = new MyDestructableClass();
    ?>
    
    执行上述代码,返回结果是:
    构造函数
    销毁 MyDestructableClass
    

    PHP重写和重载

    • PHP不支持像Java那种强类型语言一样的重载。
    PHP重写

    如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
    实例中重写了 getUrl 与 getTitle 方法:

    function getUrl() {
       echo $this->url . PHP_EOL;
       return $this->url;
    }
       
    function getTitle(){
       echo $this->title . PHP_EOL;
       return $this->title;
    }
    

    访问控制

    PHP对属性或者方法的访问控制,是通过在前面添加关键字实现的:

    • public(共有的):公有的类成员可以在任何地方被访问。
    • protected(受保护):受保护的类成员可以在自身或者子父类中被访问。
    • private(私有):私有的类成员则只能被其定义所在的类访问。
    属性的访问控制

    类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。

    <?php
    /**
     * Define MyClass
     */
    class MyClass
    {
        public $public = 'Public';
        protected $protected = 'Protected';
        private $private = 'Private';
    
        function printHello()
        {
            echo $this->public;
            echo $this->protected;
            echo $this->private;
        }
    }
    
    $obj = new MyClass();
    echo $obj->public; // 这行能被正常执行
    echo $obj->protected; // 这行会产生一个致命错误
    echo $obj->private; // 这行也会产生一个致命错误
    $obj->printHello(); // 输出 Public、Protected 和 Private
    
    
    /**
     * Define MyClass2
     */
    class MyClass2 extends MyClass
    {
        // 可以对 public 和 protected 进行重定义,但 private 而不能
        protected $protected = 'Protected2';
    
        function printHello()
        {
            echo $this->public;
            echo $this->protected;
            echo $this->private;
        }
    }
    
    $obj2 = new MyClass2();
    echo $obj2->public; // 这行能被正常执行
    echo $obj2->private; // 未定义 private
    echo $obj2->protected; // 这行会产生一个致命错误
    $obj2->printHello(); // 输出 Public、Protected2 和 Undefined
    
    ?>
    
    方法的访问控制

    类中的方法可以被定义为公有,私有或受保护。如果没有设置这些关键字,则该方法默认为公有。

    <?php
    /**
     * Define MyClass
     */
    class MyClass
    {
        // 声明一个公有的构造函数
        public function __construct() { }
    
        // 声明一个公有的方法
        public function MyPublic() { }
    
        // 声明一个受保护的方法
        protected function MyProtected() { }
    
        // 声明一个私有的方法
        private function MyPrivate() { }
    
        // 此方法为公有
        function Foo()
        {
            $this->MyPublic();
            $this->MyProtected();
            $this->MyPrivate();
        }
    }
    
    $myclass = new MyClass;
    $myclass->MyPublic(); // 这行能被正常执行
    $myclass->MyProtected(); // 这行会产生一个致命错误
    $myclass->MyPrivate(); // 这行会产生一个致命错误
    $myclass->Foo(); // 公有,受保护,私有都可以执行
    
    
    /**
     * Define MyClass2
     */
    class MyClass2 extends MyClass
    {
        // 此方法为公有
        function Foo2()
        {
            $this->MyPublic();
            $this->MyProtected();
            $this->MyPrivate(); // 这行会产生一个致命错误
        }
    }
    
    $myclass2 = new MyClass2;
    $myclass2->MyPublic(); // 这行能被正常执行
    $myclass2->Foo2(); // 公有的和受保护的都可执行,但私有的不行
    
    class Bar 
    {
        public function test() {
            $this->testPrivate();
            $this->testPublic();
        }
    
        public function testPublic() {
            echo "Bar::testPublic\n";
        }
        
        private function testPrivate() {
            echo "Bar::testPrivate\n";
        }
    }
    
    class Foo extends Bar 
    {
        public function testPublic() {
            echo "Foo::testPublic\n";
        }
        
        private function testPrivate() {
            echo "Foo::testPrivate\n";
        }
    }
    
    $myFoo = new foo();
    $myFoo->test(); // Bar::testPrivate 
                    // Foo::testPublic
    ?>
    

    接口

    • 使用接口,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。(即只声明,不实现)
    • 接口是通过interface关键字来定义的,就像定义一个标准的类一样, 但其中定义的所有的方法都是空的。
    • 接口的特性是:接口中定义的所有方法必须都是公共的。
    • 要实现一个接口,使用implements操作符。类中必须实现接口中定义的所有方法,否则会报错。类可以实现多个接口(中间用逗号隔开),但是只能继承一个父类。(这点和Java是一样的)
    <?php
    
    // 声明一个'iTemplate'接口
    interface iTemplate
    {
        public function setVariable($name, $var);
        public function getHtml($template);
    }
    
    
    // 实现接口
    class Template implements iTemplate
    {
        private $vars = array();
      
        public function setVariable($name, $var)
        {
            $this->vars[$name] = $var;
        }
      
        public function getHtml($template)
        {
            foreach($this->vars as $name => $value) {
                $template = str_replace('{' . $name . '}', $value, $template);
            }
     
            return $template;
        }
    }
    

    常量

    • 把在类中始终保持不变的值定义为常量。注意:在定义和使用常量的时候不需要加$字符。
    • 常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。
    • 自php5.3开始,可以用一个变量来动态的调用类。但该变量的值不能为关键字(self,parent,static)
    <?php
    class MyClass
    {
        const constant = '常量值';
    
        function showConstant() {
            echo  self::constant . PHP_EOL;
        }
    }
    
    echo MyClass::constant . PHP_EOL;
    
    $classname = "MyClass";
    echo $classname::constant . PHP_EOL; // 自 5.3.0 起
    
    $class = new MyClass();
    $class->showConstant();
    
    echo $class::constant . PHP_EOL; // 自 PHP 5.3.0 起
    ?>
    

    抽象类

    • 任何一个类,如果他里面至少有一个方法被声明为抽象的,那么这个类就必须是抽象类。
    • 抽象类不能被实例化。(实例化由其实现类来做)。
    • 抽象方法只声明了其调用方式(参数),不能定义其具体的功能实现。(是不是想到了接口)
    • 继承抽象类时,子类必须定义父类所有的抽象方法。这些方法的访问控制(权限)必须和父类一致,或者更为宽松。
      • 例如某个抽象方法被声明为受保护的,那么子类中实现的方法就应该声明为受保护的或者公有的,而不能定义为私有的。
    • 此外方法的调用方式必须匹配,即类型和所需参数数量必须一致。
      • 例如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。
    <?php
    abstract class AbstractClass
    {
     // 强制要求子类定义这些方法
        abstract protected function getValue();
        abstract protected function prefixValue($prefix);
    
        // 普通方法(非抽象方法)
        public function printOut() {
            print $this->getValue() . PHP_EOL;
        }
    }
    
    class ConcreteClass1 extends AbstractClass
    {
        protected function getValue() {
            return "ConcreteClass1";
        }
    
        public function prefixValue($prefix) {
            return "{$prefix}ConcreteClass1";
        }
    }
    
    class ConcreteClass2 extends AbstractClass
    {
        public function getValue() {
            return "ConcreteClass2";
        }
    
        public function prefixValue($prefix) {
            return "{$prefix}ConcreteClass2";
        }
    }
    
    $class1 = new ConcreteClass1;
    $class1->printOut();
    echo $class1->prefixValue('FOO_') . PHP_EOL;
    
    $class2 = new ConcreteClass2;
    $class2->printOut();
    echo $class2->prefixValue('FOO_') . PHP_EOL;
    ?>
    

    以上代码的执行结果:

    ConcreteClass1
    FOO_ConcreteClass1
    ConcreteClass2
    FOO_ConcreteClass2
    

    Static关键字

    • 声明类属性或方法为static,就可以不实例化而直接访问了。(使用要有技巧,否则会内存溢出)
    • 静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)
    • 由于静态方法不需要通过对象就可以调用,所以伪对象$this在静态方法中不可用。
    • 静态属性不可以由对象通过->操作符来访问。
    • 自 PHP 5.3.0 起,可以用一个变量来动态调用类。但该变量的值不能为关键字 self,parent 或 static。
    <?php
    class Foo {
      public static $my_static = 'foo';
      
      public function staticValue() {
         return self::$my_static;
      }
    }
    
    print Foo::$my_static . PHP_EOL;
    $foo = new Foo();
    
    print $foo->staticValue() . PHP_EOL;
    ?>  
    

    执行以上程序,输出结果为:

    foo
    foo
    

    Final关键字

    PHP 5 新增了一个 final 关键字。如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。

    <?php
    class BaseClass {
       public function test() {
           echo "BaseClass::test() called" . PHP_EOL;
       }
       
       final public function moreTesting() {
           echo "BaseClass::moreTesting() called"  . PHP_EOL;
       }
    }
    
    class ChildClass extends BaseClass {
       public function moreTesting() {
           echo "ChildClass::moreTesting() called"  . PHP_EOL;
       }
    }
    // 报错信息 Fatal error: Cannot override final method BaseClass::moreTesting()
    ?>
    

    调用父类构造方法

    PHP 不会在子类的构造方法中自动的调用父类的构造方法。要执行父类的构造方法,需要在子类的构造方法中调用 parent::__construct() 。

    <?php
    class BaseClass {
       function __construct() {
           print "BaseClass 类中构造方法" . PHP_EOL;
       }
    }
    class SubClass extends BaseClass {
       function __construct() {
           parent::__construct();  // 子类构造方法不能自动调用父类的构造方法
           print "SubClass 类中构造方法" . PHP_EOL;
       }
    }
    class OtherSubClass extends BaseClass {
        // 继承 BaseClass 的构造方法
    }
    
    // 调用 BaseClass 构造方法
    $obj = new BaseClass();
    
    // 调用 BaseClass、SubClass 构造方法
    $obj = new SubClass();
    
    // 调用 BaseClass 构造方法
    $obj = new OtherSubClass();
    ?>
    

    执行以上程序,输出结果为:

    BaseClass 类中构造方法
    BaseClass 类中构造方法
    SubClass 类中构造方法
    BaseClass 类中构造方法
    

    一个敲代码,爱分享的人,我在这里!

    来玩啊

    相关文章

      网友评论

      本文标题:PHP Day5:面向对象

      本文链接:https://www.haomeiwen.com/subject/xdvgdttx.html