ホームページ  >  記事  >  バックエンド開発  >  PHP オブジェクト指向設計 (S.O.L.I.D) の 5 つの基本原則について話しましょう

PHP オブジェクト指向設計 (S.O.L.I.D) の 5 つの基本原則について話しましょう

青灯夜游
青灯夜游転載
2022-09-07 19:44:473627ブラウズ

この記事では、PHP オブジェクト指向設計 (S.O.L.I.D) の 5 つの基本原則について話しましょう オブジェクト指向設計 (S.O.L.I.D) の 5 つの基本原則について説明します。お役に立てば幸いです。

PHP オブジェクト指向設計 (S.O.L.I.D) の 5 つの基本原則について話しましょう

##S.O.L.I.D は、Robert C. によって開発されたオブジェクト指向設計 (OOD) の 5 つの原則の頭字語です。アンクルとして知られる Martin によって提案されました。ボブ。 これらのガイドラインにより、スケーラブルで保守可能なソフトウェアの開発が容易になります。また、コードがより合理化され、リファクタリングが容易になります。アジャイル開発と適応型ソフトウェア開発の一部でもあります。

備考

:

これは「__S.O.L.I.D* へようこそ」への単純な紹介ではありません。この記事は、##S.O.L.I.D とは何かを明確にしたいと考えています。 ***。

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 '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() {
    $area = [];
    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() {
    $area = [];
    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 'Sum of the areas of provided shapes: ' . $this->calculator->sum();
    }
}

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

$areas = new AreaCalulator($shapes);
$volumes = new VolumeCalculator($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 オブジェクト指向設計 (S.O.L.I.D) の 5 つの基本原則について話しましょう ビデオ チュートリアル

以上がPHP オブジェクト指向設計 (S.O.L.I.D) の 5 つの基本原則について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。