设计模式SOLID - 里氏代换原则

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式SOLID - 里氏代换原则相关的知识,希望对你有一定的参考价值。

Principles Rule!
It’s been a while since OOP/Design Pattern principles have been a topic on this blog, and now is as good time as any. The 1987 OOPSLA keynote address by Barbara Liskov contained what has become known as the Liskov Substitution Principle. In that address and books and papers, the Liskov principle is an OOP one that is part of the more general OOP set of principles. In languages like C++ and Java where you have strongly typed languages, you can declare a variable (property) by the parent data type. Since php is flexibly typed, the data type can change, and while that practice [changing data types for a variable] is not recommended, it is certainly possible. As a result, it’s not as easy to explain and illustrate the principle with PHP.

Essentially, the principle holds that,

If a Client is using a reference to a base class it should be able to substitute a derived class for it without affecting the program’s operation

Actually Dr. Liskov said,:

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

and I was trying to put it in less technical terms.

Why is the Liskov Substitution Principle Important?

You can easily see three reasons that the principle is important:

  1. If substitutions cannot be made then class hierarchies would be a mess. Whenever a subclass instance is passed as parameter to any method, surprises might occur.
  2. Without the possibility of Liskov type substitutions unit tests for the superclass would never succeed for the subclass.
  3. Changes are easier and more predictable because you know what to expect from the superclass.

No doubt you can find more, but for now, the Liskov substitution principle is another way to keep your code loose and reusable and subject to change.

If the Client has an object of a certain type, it should be able to substitute a derived object of the same type. For example, suppose you have an abstract class namedAutomobile and from that class you have subclasses of FordToyota, and Peugeot. Your Client has an object, $myAuto. The $myAuto object can be any of the subclasses, and if a substitution is made for any one of them everything keeps on working without a problem and the change is unknown to the Client class. So if you substitute a Ford for a Toyota, the Client can work with the objects without having to adjust for the change. What’s more, if you want to add a Fiat or Mercedes Benzclass as a subclass of Automobile, the $myAuto object handles it with nary a whimper.

The one caveat is that the subclasses must all honor the contractual conditions of the parent class. So, any methods in the parent class must be functioning in the subclass (aka, derived class.)

Now you may be thinking, So what? If you’re at all familiar with other principles of OOP and Design Patterns, this principle may sound vaguely familiar, but what is the importance of this concept/principle/idea? It is this: Because the Client is unaware of the concrete class that the object may implement, the structure is far more resilient. Not only can the same structure be reused, it can be changed, updated and generally fiddled with without easily breaking anything. (Think of adding more car manufacturers to the Automobile class.) As far as the Client is concerned, as long as the interface rules are followed with the object, everything is hunky-dory. To get started Play the example and Download the source code:
技术分享技术分享

A Simple Practice for PHP Programmers

As a principle, the Liskov Substitution Principle is easy to use. The problem is in demonstrating it in PHP because of the lack of assigned data types to properties. However, using PHP type hinting, you will see how it works using at a classic example where the Liskov substitution principle (LSP) is used.

Explanations of how LSP works often use a rectangle/square example. Typically most programmers see two alternatives to illustrate LSP programming a rectangle and a square; with LSP and without LSP.

Beginning with the geometric observation, all squares are also rectangles, we’re likely to create a structure like that shown in Figure 1:

技术分享

Figure 1: Square is a child of Rectangle

After all, we can envision a square as nothing more than a rectangle with equal sides. So an operation such as,

makeRectangle(w,h);

is an effective way to make either a square or rectangle. We add the Square class simply to examine the LS Principle; otherwise creating rectangles or squares would be accomplished using the makeRectangle(w,h) method making sure that w and hare equal when making squares. The inheritance path clearly indicates that a Square object IS A Rectangle. (That is, the two have an IS A relationship.) However, because Rectangle is a concrete class if I substitute Rectangle for Square, I’m going to have to change the type. I cannot substitute an instance of Rectangle for an instance of Square. This violates the Liskov Substitution Principle.

Consider Figure 2. Instead of having Square as a child of Rectangle, both Square and Rectangle are children of the abstract class Box.

技术分享

Figure 2: Abstract Class is Parent of both Squares and Rectangles

 

If I have an instance of either Rectangle or Square, I have an instance of Box. I can substitute either Square or Rectangle for the instance. In looking at an example, the Client is where we can expect to see the substitution principle at work. In the following example, the Client has a single object ($subLiskov) and it is effectively a Box type. However, that single object can gingerly create either a square or rectangle without the Client knowing the difference. It is designed to illustrate LSP.

?View Code PHP
 
<?php
error_reporting(E_ALL | E_STRICT);
ini_set("display_errors", 1);
function __autoload($class_name) 
{
    include $class_name . ‘.php‘;
}
class Client
{
    private $subLiskov;
 
    public function __construct()
    {
        $this->doRectangle();
        $this->doSquare();
    }
 
    private function doSquare()
    {
        $this->subLiskov=new Square();
        $this->boxWork($this->subLiskov); 
    }
 
    private function doRectangle()
    {
        $this->subLiskov=new Rectangle();
        $this->boxWork($this->subLiskov); 
    }
 
    //The type hint ‘Box‘ insures the same parent class
    private function boxWork(Box $box)
    {
        echo $box->doBox();
    }
}
$worker = new Client();
?>

 

 

The Client has two methods to create either a square or rectangle and both methods use the object $subLiskov.

The Box Abstract Class and Derived Classes

The simplicity of the Box class and its derived concrete classes, Square and Rectangle, illustrate that to do good OOP programming, you need not make your code complex. By thinking interns of communication between objects derived from parent classes, you know that the child objects will have everything the parent has as a foundation. That makes it easy.

First, the Box class consists of a single abstract method and a single concrete method along with several properties.

?View Code PHP
 
<?php
abstract class Box
{
    //Abstract method 
    abstract protected function setData();
    //Concrete method
    public function doBox()
    {  
        $this->setData();
        $this->box=<<<BOX
         <!doctype html><html><head>
        <meta charset=‘UTF-8‘></head><body>
        <svg width=‘30%‘ height=‘25%‘ xmlns=‘http://www.w3.org/2000/svg‘ version=‘1.1‘>
        <rect x=‘$this->xpos‘ y=‘$this->ypos‘ width=‘$this->wide‘ height=‘$this->high‘ fill=‘$this->fill‘ stroke=‘$this->stroke‘ stroke-width=‘$this->strWidth‘ />
        </svg>
        </body></html>
BOX;
    return $this->box;
    }
 
    //Properties
    protected $xpos;
    protected $ypos;
    protected $wide;
    protected $high;
    protected $fill;
    protected $stroke;
    protected $strWidth;
    protected $box;    
}
?>

 

 

The concrete method simply sets up the HTML5 foundation for SVG graphics. For more variance, re-cast the method as abstract and let the concrete implementations take care of the details. For this demonstration, the limited range of the method might as well be concrete.

The two concrete classes are vitally identical with the only difference being the values set in the inherited properties:

?View Code PHP
 
//Rectangle.php
<?php
class Rectangle extends Box
{  
    /* Draw Rectangle */
    public function __construct()
    {
        $this->doBox();
    }
 
    //Set data for Rectangle
    protected function setData()
    {
        $this->xpos=20;
        $this->ypos=20;
        $this->wide=200;
        $this->high=150;
        $this->fill="#CFD7D9";
        $this->stroke="#000";
        $this->strWidth=1;
    }
}
?>
 
//Square.php
<?php
class Square extends Box
{
    /* Draw Square */
    public function __construct()
    {
        $this->doBox();
    }
 
    //Set values for square
    protected function setData()
    {
        $this->xpos=20;
        $this->ypos=20;
        $this->wide=150;
        $this->high=150;
        $this->fill="#6FA3CC";
        $this->stroke="#000";
        $this->strWidth=1;
    }
}
?>

 

 

The literal values could be passed as parameters by rewriting the setData() method in the parent and child classes, but for here, the point is to see how Liskov substitution works and nothing more.

The Testing Method

Using boxWork(BOX $box) the Client substitutes the Square and Rectangle for theBox object. Everything works smoothly. The way the program is set up when one is used, the box image changes from a square to a rectangle (or vice versa), but if you wanted, you could create as many as you want and splash them all over the stage. Figure 3 shows what you will see in the current setup.

技术分享

Figure 3: Substituting Square and Rectangle Subclasses for Boxes Object

 

With this simple program, you should be able to see the ramifications for larger programs. In fact, the more your program grows, the more important LSP becomes. If you know that you can substitute any derived class for a base class, you are less likely to have unpleasant surprises. As a program grows and changes, you will have more modules, more objects, and more methods that need to interact in a program. The LSP helps keep those relations functional.

No Concrete Bases, Please

After working with the LS Principle, I wondered,

…is there any reason to create a concrete base class?

Let me mull this over…mull, mull, mull. The answer is:

No!

Not a single design pattern has a structure with a concrete parent class with derived classes. Even the Adapter pattern, with dual inheritance, has one abstract base class (or interface).

Why do I ever need a concrete parent class? I don’t want to create a parent class with functionality that cements implementations of its derived classes, but by implementing concrete classes from an abstract parent, I get wiggle room for the subclasses to extend the base class. Following the open/closed principle (“Open for extension. Closed for modification”), I can extend with added functionality from a superclass but I don’t want to modify it.

Working with the Liskov Substitution Principle

What can we take to work with the Liskov Substitution Principle? It’s nice to know and understand why the principle is in place, but who needs to have all of the implied tentacles of the principle rattling around in your head—especially considering the way Liskov stated it. We know the principle as you should be able to substitute any derived class from a base class where an instance of the base class exists. After further examination, the only way to do that is to use an abstract class or interface for all of your inheritance and implementations. Put more succinctly, the Lunch Bucket Rule is,

Only subclass from abstract classes. Do not inherit from concrete classes.

That’s easy enough to remember. Also, given the weak (or dynamic) typing in PHP, that probably makes more sense. Put that in your head and see if it helps move an OOP and Design Pattern agenda along at work and in practical projects.






以上是关于设计模式SOLID - 里氏代换原则的主要内容,如果未能解决你的问题,请参考以下文章

从零开始学习Java设计模式 | 软件设计原则篇:里氏代换原则

从零开始学习Java设计模式 | 软件设计原则篇:里氏代换原则

设计模式里氏代换原则

面向对象设计原则之里氏代换原则

西游记之设计模式原则——里氏代换原则

设计原则之里氏代换原则