Maison  >  Article  >  Cinq principes de base de la conception orientée objet en PHP

Cinq principes de base de la conception orientée objet en PHP

步履不停
步履不停original
2019-06-12 17:54:185041parcourir

Cinq principes de base de la conception orientée objet en PHP

S.O.L.I.D est le premier 5 design orienté objet(OOD) Acronyme des principes , ces principes ont été proposés par Robert C. Martin, plus connu sous le nom de Oncle Bob.

Ces directives facilitent le développement de logiciels évolutifs et maintenables. Cela rend également le code plus rationalisé et plus facile à refactoriser. Fait également partie du développement agile et du développement de logiciels adaptatifs.

Remarques : Ceci n'est pas une simple introduction à "Bienvenue à_S.O.L.I.D", cet article veut clarifier S.O.L.I.D Qu'est-ce que c'est. (Recommandations de didacticiel associées : tutoriel vidéo php)

S.O.L.I.D signifie :

L'acronyme développé peut sembler compliqué, mais en fait, il est facile à comprendre.

  • S - Principe monofonction
  • O - Principe ouvert-fermé
  • L - Principe de substitution de Liskov
  • I - Principe d'isolement d'interface
  • D - Principe d'inversion de dépendance

Connect Let's examinez chaque principe pour comprendre pourquoi S.O.L.I.D peut nous aider à devenir de meilleurs développeurs.

Principe de responsabilité unique

L'abréviation est S.R.PLe contenu de ce principe est :

Une classe en a et ne peut en avoir qu'une. facteur qui le fait changer, ce qui signifie qu'une classe ne doit avoir qu'une seule responsabilité.

Par exemple, supposons que nous ayons des formes et que nous souhaitions calculer l'aire totale de ces formes. Oui, c'est facile, non ?

class Circle {
    public $radius;

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

class Square {
    public $length;

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

Tout d'abord, nous créons la classe graphique, et le constructeur de cette classe initialise les paramètres nécessaires. Ensuite, créez la classe AreaCalculator, puis écrivez le code logique pour calculer la surface totale du graphique spécifié.

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 Pour utiliser la méthode, nous instancions simplement cette classe et lui passons un tableau graphique pour afficher le contenu de sortie en bas de la page. Le problème avec la méthode de sortie

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

$areas = new AreaCalculator($shapes);

echo $areas->output();

est que AreaCalculator gère la logique de sortie des données. Alors, que se passe-t-il si l'utilisateur souhaite afficher les données au format JSON ou dans d'autres formats ?

Toute la logique est gérée par la classe AreaCalculator, ce qui viole exactement le principe de responsabilité unique (SRP) ; la classe AreaCalculator ne devrait être responsable que du calcul de la superficie totale. du graphique, ce n'est pas le cas. Vous devez vous soucier de savoir si l'utilisateur souhaite des données au format json ou HTML.

Donc, pour résoudre ce problème, vous pouvez créer une classe SumCalculatorOutputter et l'utiliser pour gérer la logique d'affichage requise pour gérer la façon dont la surface totale de tous les graphiques doit être affichée. La classe

SumCalculatorOutputter fonctionne comme ceci :

$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();

Maintenant, quel que soit le format de données que vous souhaitez envoyer à l'utilisateur, il est géré par SumCalculatorOutputter classe.

Principe ouvert-fermé

Les objets et les entités doivent être ouverts pour extension, mais fermés pour modification.

Pour faire simple, une classe doit pouvoir facilement étendre ses fonctionnalités sans se modifier. Jetons un coup d'œil à la classe AreaCalculator, en particulier à la méthode 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);
}

Si nous voulons utiliser la méthode sum pour calculer l'aire de plus de formes, nous devons ajouter plus de blocs if/else, mais cela viole Principe d'ouverture et de fermeture.

La façon d'améliorer cette méthode somme est de déplacer la logique de code qui calcule l'aire de chaque forme hors de la méthode somme et de la placer dans chaque classe de forme :

class Square {
    public $length;

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

    public function area() {
        return pow($this->length, 2);
    }
}

La même opération doit être utilisée pour gérer la classe Circle, en ajoutant une méthode area à la classe. Désormais, calculer la somme des aires de n'importe quelle forme devrait être aussi simple que :

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

    return array_sum($area);
}

Ensuite, nous pouvons créer une autre classe de forme et la transmettre lors du calcul de la somme sans casser notre code. Mais maintenant une autre question se pose, comment pouvons-nous savoir que l'objet passé dans AreaCalculator est en fait une forme, ou qu'il existe une méthode area dans l'objet forme ?

Le codage d'interface fait partie de la pratique de S.O.L.I.D Par exemple, dans l'exemple suivant, nous créons une classe d'interface, et chaque classe de forme implémentera cette classe d'interface :

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);
    }
}

. Dans notre méthode In the sum de AreaCalculator, nous pouvons vérifier si l'instance fournie de la classe de forme est une implémentation de ShapeInterface, sinon nous lancerons une exception :

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

        throw new AreaCalculatorInvalidShapeException;
    }

    return array_sum($area);
}

Principe de substitution

Si pour tout objet o1 de type T1, il existe un objet o2 de type T2, de sorte que tous les programmes P définis avec T1 sont remplacés par o2 dans tous les objets o1 , le comportement du programme P ne change pas, alors le type T2 est un sous-type du type 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面向对象视频教程》

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn