@blog.justoneplanet.info

日々勉強

PHP Class Inheritance and Member Visibility(継承と可視性)

■可視性

PHP5ではメンバの可視性を、以下のキーワードを使用して設定することができる。明示しない場合はpublicとなるが、個人的には可読性を上げる意味も含め全てのメンバにアクセス修飾子を記述するべきかと思う。

public 全てのスコープからアクセスができる
protected サブクラスからアクセスができる
private 自身のクラス内のみアクセスできる
final 全てのスコープからアクセスができる。但し、上書きができない

名前空間が近いかもしれないが、Javaと違ってパッケージという概念はない。

クラスにfinalを設定する場合

finalキーワードはクラスの継承を防ぐ事もできる。以下のように記述するとエラーとなる。

final class MyClass {
    //code
}
class ChildClass extends MyClass  {
    //code
}

■クラスの継承

オブジェクト指向では継承という概念の元、親クラスの機能をそのまま受け継いだり補完したりして、コードの記述量を減らしたり機能の拡張を行ったりすることができる。また、親クラスのメソッドを呼び出したいときは「parent::メソッド名」の記法を用いる。

class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function introduce(){
        print("I'm {$this->name}! Nice to meet you!");
    }
}
class Dog extends Animal {
    public function introduce(){
        parent::introduce();
        print(' bow!');
    }
    public function bark(){
        print("{$this->name}, bow!");
    }
}
$pochi = new Dog('Pochi');
$pochi->introduce();//I'm Pochi! Nice to meet you! bow!
$pochi->bark();//Pochi, bow!

但し、以下のようにクラスAnimalでprivate $nameとしてしまうと、サブクラスには$nameが雛形として存在しないため(アクセスできず)、エラーが発生する。

class Animal {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function introduce(){
        print("I'm {$this->name}! Nice to meet you!");
    }
}
class Dog extends Animal {
    public function introduce(){
        parent::introduce();
        print(' bow!');
    }
    public function bark(){
        print("{$this->name}, bow!");
    }
}
$pochi = new Dog('Pochi');
$pochi->introduce();//I'm Pochi! Nice to meet you! bow!
$pochi->bark();//Notice:  Undefined property:  Dog::$name in ....

実例

どの参考書を見てもイマイチ理解しづらい。そんな時は、色々なものを書いてみることを繰り返すしかないかな・・・

通常の継承
class Animal {
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print('Bow!');
    }
}
$pochi = new Dog();
$pochi->bark();//Bow!

まぁ、こんな感じだ。でも、犬の名前くらい付けてやりたい。

プロパティの使用

そこで犬の名前を保持する方法を考える。$pochiは変数名であり、文字列として名前を保持するにはプロパティを使用する。

class Animal {
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print('Bow!');
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//Bow!

上述のようにプロパティを明記しなくてもエラーではないが、非常に分かりにくい上にアクセス権がpublicに設定されるので以下のように記述する。

class Animal {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print('Bow!');
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//Bow!
プロパティとアクセス権

上述のコードでbarkメソッドに自分の名前を含めるたいときには、ちょっと頭をひねらなければならない。以下のようにDogクラスから$thisを参照すると、スーパークラスの$name
はサブクラスからは見えないので、「Notice: Undefined property: Dog::$name」とエラーが表示される。

class Animal {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
}
$pochi = new Dog('pochi');
$pochi->bark();

そこで以下のようにアクセス修飾詞protectedを用いて、$nameを継承されつつ外からは見えないように設定する。

class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

privateのままで、全てサブクラスでオーバーライドする方法もあるが記述量が多くなり、オブジェクト指向の恩恵が少なくなると思われる。

抽象クラスを使用した継承

但し、深く考えてみると「Animal」というクラスは、クラスの雛形的な存在でインスタンス化しない可能性が高いことに気づく。従って、以下のように抽象クラスを使用してみる。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

これでクラスAnimalはインスタンス化できなくなった。せっかく抽象クラスにしたので抽象メソッドも実装してみたい。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

当然ではあるが、サブクラスでspeakメソッドを実装しなくてはならなくなった。

アクセサメソッドの実装

何らかの都合で$nameに外側からアクセスしたい可能性があるかもしれない。publicにする方法も考えられるが、オブジェクト指向のメリットの一つである隠蔽性が低下してしまう。そこでアクセス専用のメソッドを用意してあげるのがイイと思う。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    public function getName(){
        return $this->name;
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!
print($pochi->getName());//pochi
オーバーライド

サブクラスでスーパークラスのメソッドを補完したい場合もあるかもしれない。以下のように、サブクラスでスーパークラスと同名のメソッドを再度宣言するオーバーライドといった手法で、そういった要望に応えることができる。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    public function getName(){
        return $this->name;
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function walk(){
        parent::walk();
        print("n{$this->name}");
    }
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->walk();//I'm walking! pochi

ちなみに、「self::」でコールする場合はメソッドはstaticである必要があるが、「parent::」でコールする場合はstaticでなくても問題ない。

オーバーライドをさせない実装

キーワード「final」を使用する。以下の例ではサブクラスでwalkメソッドを再定義することはできない。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    final public function walk(){
        print('I\'m walking!');
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!
$pochi->walk();//I'm pochi. Bow!

また、以下のようにクラス名の前において、クラス自身を継承させないようにすることもできる。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    abstract public function speak($content);
}
final class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

1件のコメント»

RSS feed for comments on this post.TrackBack URL

Leave a comment