ホームページ > 見出し > PHP におけるオブジェクト指向設計の 5 つの基本原則

PHP におけるオブジェクト指向設計の 5 つの基本原則

步履不停
リリース: 2019-08-21 18:01:03
オリジナル
5085 人が閲覧しました

PHP におけるオブジェクト指向設計の 5 つの基本原則

S.O.L.I.D5 つのオブジェクト指向設計のうちの最初のものです(OOD)Principles の頭字語であるこれらの原則は、Uncle Bob として知られる Robert C. Martin によって提案されました。

これらのガイドラインにより、スケーラブルで保守可能なソフトウェアの開発が容易になります。また、コードがより合理化され、リファクタリングが容易になります。アジャイル開発と適応型ソフトウェア開発の一部でもあります。

: これは「__S.O.L.I.Dへようこそ」への単純な紹介ではなく、この記事は明確にしたいと考えています S.O.L.I.D どういうことですか。 (関連チュートリアルの推奨事項: php ビデオ チュートリアル)

S.O.L.I.D の意味:

展開された頭字語は複雑に見えるかもしれませんが、実際には非常に簡単に理解できます。

  • S - 単機能原理
  • #O - 開閉原理
  • #L - Liskov 置換原則
  • #I
  • - インターフェース分離原則
  • #D
  • - 依存関係逆転原則
  • #次は見てみましょうそれぞれの原則について、なぜ S.O.L.I.D が私たちがより良い開発者になるのに役立つのかを理解してください。

単一責任の原則

省略形は

S.R.P

です。この原則の内容は次のとおりです:

クラスには 1 つの責任しかありません。それは、クラスが 1 つの責任だけを持つべきであることを意味します。

たとえば、いくつかの形状があり、これらの形状の合計面積を計算したいとします。はい、簡単ですよね?
class Circle {
    public $radius;

    public function construct($radius) {
        $this->radius = $radius;
    }
}

class Square {
    public $length;

    public function construct($length) {
        $this->length = $length;
    }
}
ログイン後にコピー

まず、グラフィックス クラスを作成し、このクラスのコンストラクターで必要なパラメーターを初期化します。次に、

AreaCalculator

クラスを作成し、指定されたグラフィックの合計面積を計算するロジック コードを記述します。

class AreaCalculator {

    protected $shapes;

    public function __construct($shapes = array()) {
        $this->shapes = $shapes;
    }

    public function sum() {
        // logic to sum the areas
    }

    public function output() {
        return implode('', array(
            "",
                "Sum of the areas of provided shapes: ",
                $this->sum(),
            ""
        ));
    }
}
ログイン後にコピー

AreaCalculator

このメソッドを使用するには、このクラスをインスタンス化し、グラフィック配列に渡すだけで、ページの下部に出力コンテンツが表示されます。

$shapes = array(
    new Circle(2),
    new Square(5),
    new Square(6)
);

$areas = new AreaCalculator($shapes);

echo $areas->output();
ログイン後にコピー
出力メソッドの問題は、

AreaCalculator

がデータ出力ロジックを処理することです。では、ユーザーがデータを json またはその他の形式で出力したい場合はどうすればよいでしょうか? すべてのロジックは

AreaCalculator

クラスによって処理されますが、これは単一責任原則 (SRP) に正確に違反します。AreaCalculator クラスは合計面積の計算のみを担当する必要があります。ユーザーがデータを JSON 形式で必要とするか HTML 形式で必要とするかを気にする必要はありません。 したがって、この問題を解決するには、

SumCalculatorOutputter

クラスを作成し、それを使用して、すべてのグラフィックスの合計領域を表示する方法を処理するために必要な表示ロジックを処理します。

SumCalculatorOutputter

クラスは次のように機能します:

$shapes = array(
    new Circle(2),
    new Square(5),
    new Square(6)
);

$areas = new AreaCalculator($shapes);
$output = new SumCalculatorOutputter($areas);

echo $output->JSON();
echo $output->HAML();
echo $output->HTML();
echo $output->JADE();
ログイン後にコピー
これで、ユーザーに出力するデータの形式はすべて

SumCalculatorOutputter

によって処理されます。クラス。 オープンとクローズの原則

オブジェクトとエンティティは、拡張に対してオープンである必要がありますが、変更に対してはクローズされている必要があります。

簡単に言えば、クラスはそれ自体を変更せずにその機能を簡単に拡張できる必要があります。
AreaCalculator

クラス、特に sum メソッドを見てみましょう。

public function sum() {
    foreach($this->shapes as $shape) {
        if(is_a($shape, 'Square')) {
            $area[] = pow($shape->length, 2);
        } else if(is_a($shape, 'Circle')) {
            $area[] = pi() * pow($shape->radius, 2);
        }
    }

    return array_sum($area);
}
ログイン後にコピー

sum

メソッドを使用してさらに多くの図形の面積を計算したい場合は、さらに if/else ブロック を追加する必要がありますが、これは違反です開閉原理。 この

sum

メソッドをより良くする方法は、各図形の面積を計算するコード ロジックを sum メソッドから移動し、それを各図形クラスに入れることです。

class Square {
    public $length;

    public function __construct($length) {
        $this->length = $length;
    }

    public function area() {
        return pow($this->length, 2);
    }
}
ログイン後にコピー
同じ操作を使用して

Circle

クラスを処理し、クラスに area メソッドを追加する必要があります。これで、任意の形状の面積の合計の計算は次のように簡単になります。

public function sum() {
    foreach($this->shapes as $shape) {
        $area[] = $shape->area();
    }

    return array_sum($area);
}
ログイン後にコピー
次に、別の形状クラスを作成し、コードを中断することなく合計を計算するときにそれを渡すことができます。しかし、ここで別の疑問が生じます。

AreaCalculator

に渡されたオブジェクトが実際に形状であること、または形状オブジェクトに area メソッドが存在することをどのようにして知ることができるのでしょうか。 インターフェイス コーディングは、

S.O.L.I.D

の実践の一部です。たとえば、次の例ではインターフェイス クラスを作成し、各シェイプ クラスがこのインターフェイス クラスを実装します:

interface ShapeInterface {
    public function area();
}

class Circle implements ShapeInterface {
    public $radius;

    public function __construct($radius) {
        $this->radius = $radius;
    }

    public function area() {
        return pi() * pow($this->radius, 2);
    }
}
ログイン後にコピー

AreaCalculator

の sum メソッドでは、提供された形状クラスのインスタンスが ShapeInterface の実装であるかどうかを確認できます。そうでない場合は、例外がスローされます:

public function sum() {
    foreach($this->shapes as $shape) {
        if(is_a($shape, 'ShapeInterface')) {
            $area[] = $shape->area();
            continue;
        }

        throw new AreaCalculatorInvalidShapeException;
    }

    return array_sum($area);
}
ログイン後にコピー
置換の原則

型 T1 のすべてのオブジェクト o1 に型 T2 のオブジェクト o2 が存在する場合、T1 で定義されたすべてのプログラム P はすべてのオブジェクト o1 で o2 に置き換えられます。プログラム P の動作が変わらない場合、型 T2 は型 T1 のサブタイプになります。

这句定义的意思是说:每个子类或者衍生类可以毫无问题地替代基类/父类。

依然使用 AreaCalculator 类, 假设我们有一个 VolumeCalculator 类,这个类继承了  AreaCalculator 类:

class VolumeCalculator extends AreaCalulator {
    public function construct($shapes = array()) {
        parent::construct($shapes);
    }

    public function sum() {
        // logic to calculate the volumes and then return and array of output
        return array($summedData);
    }
}
ログイン後にコピー

SumCalculatorOutputter 类:

class SumCalculatorOutputter {
    protected $calculator;

    public function __constructor(AreaCalculator $calculator) {
        $this->calculator = $calculator;
    }

    public function JSON() {
        $data = array(
            'sum' => $this->calculator->sum();
        );

        return json_encode($data);
    }

    public function HTML() {
        return implode('', array(
            '',
                'Sum of the areas of provided shapes: ',
                $this->calculator->sum(),
            ''
        ));
    }
}
ログイン後にコピー

如果我们运行像这样一个例子:

$areas = new AreaCalculator($shapes);
$volumes = new AreaCalculator($solidShapes);

$output = new SumCalculatorOutputter($areas);
$output2 = new SumCalculatorOutputter($volumes);
ログイン後にコピー

程序不会出问题, 但当我们使用$output2 对象调用 HTML 方法时 ,我们接收到一个 E_NOTICE 错误,提示我们 数组被当做字符串使用的错误。

为了修复这个问题,只需:

public function sum() {
    // logic to calculate the volumes and then return and array of output
    return $summedData;
}
ログイン後にコピー

而不是让VolumeCalculator 类的 sum 方法返回数组。

$summedData 是一个浮点数、双精度浮点数或者整型。

接口隔离原则

使用方(client)不应该依赖强制实现不使用的接口,或不应该依赖不使用的方法。

继续使用上面的 shapes 例子,已知拥有一个实心块,如果我们需要计算形状的体积,我们可以在 ShapeInterface 中添加一个方法:

interface ShapeInterface {
    public function area();
    public function volume();
}
ログイン後にコピー

任何形状创建的时候必须实现 volume 方法,但是【平面】是没有体积的,实现这个接口会强制的让【平面】类去实现一个自己用不到的方法。

ISP 原则不允许这么去做,所以我们应该创建另外一个拥有 volume 方法的SolidShapeInterface 接口去代替这种方式,这样类似立方体的实心体就可以实现这个接口了:

interface ShapeInterface {
    public function area();
}

interface SolidShapeInterface {
    public function volume();
}

class Cuboid implements ShapeInterface, SolidShapeInterface {
    public function area() {
        //计算长方体的表面积
    }

    public function volume() {
        // 计算长方体的体积
    }
}
ログイン後にコピー

这是一个更好的方式,但是要注意提示类型时不要仅仅提示一个 ShapeInterfaceSolidShapeInterface
你能创建其它的接口,比如 ManageShapeInterface ,并在平面和立方体的类上实现它,这样你能很容易的看到有一个用于管理形状的api。例:

interface ManageShapeInterface {
    public function calculate();
}

class Square implements ShapeInterface, ManageShapeInterface {
    public function area() { /Do stuff here/ }

    public function calculate() {
        return $this->area();
    }
}

class Cuboid implements ShapeInterface, SolidShapeInterface, ManageShapeInterface {
    public function area() { /Do stuff here/ }
    public function volume() { /Do stuff here/ }

    public function calculate() {
        return $this->area() + $this->volume();
    }
}
ログイン後にコピー

现在在 AreaCalculator 类中,我们可以很容易地用 calculate替换对area 方法的调用,并检查对象是否是 ManageShapeInterface 的实例,而不是 ShapeInterface

依赖倒置原则

最后,但绝不是最不重要的:

实体必须依赖抽象而不是具体的实现.即高等级模块不应该依赖低等级模块,他们都应该依赖抽象.

这也许听起来让人头大,但是它很容易理解.这个原则能够很好的解耦,举个例子似乎是解释这个原则最好的方法:

class PasswordReminder {
    private $dbConnection;

    public function __construct(MySQLConnection $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}
ログイン後にコピー

首先 MySQLConnection 是低等级模块,然而 PasswordReminder 是高等级模块,但是根据 S.O.L.I.D. 中 D 的解释:依赖于抽象而不依赖与实现, 上面的代码段违背了这一原则,因为 PasswordReminder 类被强制依赖于 MySQLConnection 类.

稍后,如果你希望修改数据库驱动,你也不得不修改 PasswordReminder 类,因此就违背了 Open-close principle

PasswordReminder 类不应该关注你的应用使用了什么数据库,为了进一步解决这个问题,我们「面向接口写代码」,由于高等级和低等级模块都应该依赖于抽象,我们可以创建一个接口:

interface DBConnectionInterface {
    public function connect();
}
ログイン後にコピー

这个接口有一个连接数据库的方法,MySQLConnection 类实现该接口,在 PasswordReminder 的构造方法中不要直接将类型约束设置为 MySQLConnection 类,而是设置为接口类,这样无论你的应用使用什么类型的数据库,PasswordReminder 类都能毫无问题地连接数据库,且不违背 开闭原则

class MySQLConnection implements DBConnectionInterface {
    public function connect() {
        return "Database connection";
    }
}

class PasswordReminder {
    private $dbConnection;

    public function __construct(DBConnectionInterface $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}
ログイン後にコピー

从上面一小段代码,你现在能看出高等级和低等级模块都依赖于抽象了。

总结

说实话,S.O.L.I.D 一开始似乎很难掌握,但只要不断地使用和遵守其原则,它将成为你的一部分,使你的代码易被扩展、修改,测试,即使重构也不容易出现问题。

相关PHP面向对象视频教程推荐:《PHP面向对象视频教程》

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート