zoukankan      html  css  js  c++  java
  • 设计模式学习--面向对象的5条设计原则之Liskov替换原则--LSP

    一、LSP简介(LSP--Liskov Substitution Principle):
    定义:如果对于类型S的每一个对象o1,都有一个类型T的对象o2,使对于任意用类型T定义的程序P,将o2替换为o1,P的行为保持不变,则称S为T的一个子类型。
    子类型必须能够替换它的基类型。LSP又称里氏替换原则。
    对于这个原则,通俗一些的理解就是,父类的方法都要在子类中实现或者重写。
     
    二、举例说明:
    对于依赖倒置原则,说的是父类不能依赖子类,它们都要依赖抽象类。这种依赖是我们实现代码扩展和运行期内绑定(多态)的基础。因为一旦类的使用者依赖某个具体的类,那么对该依赖的扩展就无从谈起;而依赖某个抽象类,则只要实现了该抽象类的子类,都可以被类的使用者使用,从而实现了系统的扩展。
    但是,光有依赖倒置原则,并不一定就使我们的代码真正具有良好的扩展性和运行期内绑定。请看下面的代码:
    public class Animal
    {
        private string name;
        public Animal(string name)
        {
            this.name = name;
        }
        public void Description()
        {
            Console.WriteLine("This is a(an) " + name);
        }
    }
     
    //下面是它的子类猫类:
    public class Cat : Animal
    {
        public Cat(string name)
        {
            
        }
        public void Mew()
        {
            Console.WriteLine("The cat is saying like 'mew'");
        }
    }
     
    //下面是它的子类狗类:
    public class Dog : Animal
    {
        public Dog(string name)
        {
     
        }
        public void Bark()
        {
            Console.WriteLine("The dog is saying like 'bark'");
        }
    }
     
    //最后,我们来看客户端的调用:
    public void DecriptionTheAnimal(Animal animal)
    {
        if (typeof(animal) is Cat)
        {
            Cat cat = (Cat)animal;
            Cat.Decription();
            Cat.Mew();
        }
        else if (typeof(animal) is Dog)
        {
            Dog dog = (Dog)animal;
            Dog.Decription();
            Dog.Bark();
        }
    }
    通过上面的代码,我们可以看到虽然客户端的依赖是对抽象的依赖,但依然这个设计的扩展性不好,运行期绑定没有实现。
    是什么原因呢?其实就是因为不满足里氏替换原则,子类如Cat有Mew()方法父类根本没有,Dog类有Bark()方法父类也没有,两个子类都不能替换父类。这样导致了系统的扩展性不好和没有实现运行期内绑定。
    现在看来,一个系统或子系统要拥有良好的扩展性和实现运行期内绑定,有两个必要条件:第一是依赖倒置原则;第二是里氏替换原则。这两个原则缺一不可。
     
    我们知道,在我们的大多数的模式中,我们都有一个共同的接口,然后子类和扩展类都去实现该接口。
    下面是一段原始代码:
    if(action.Equals(“add”))
    {
      //do add action
    }
    else if(action.Equals(“view”))
    {
      //do view action
    }
    else if(action.Equals(“delete”))
    {
      //do delete action
    }
    else if(action.Equals(“modify”))
    {
      //do modify action
    }
    我们首先想到的是把这些动作分离出来,就可能写出如下的代码:
    public class AddAction
    {
        public void add()
        {
            //do add action
        }
    }
    public class ViewAction
    {
        public void view()
        {
            //do view action
        }
    }
    public class deleteAction
    {
        public void delete()
        {
            //do delete action
        }
    }
    public class ModifyAction
    {
        public void modify()
        {
            //do modify action
        }
    }
    我们可以看到,这样代码将各个行为独立出来,满足了单一职责原则,但这远远不够,因为它不满足依赖颠倒原则和里氏替换原则。
    下面我们来看看命令模式对该问题的解决方法:
    public interface Action
    {
        public void doAction();
    }
    //然后是各个实现:
    public class AddAction : Action
    {
        public void doAction()
        {
            //do add action
        }
    }
    public class ViewAction : Action
    {
        public void doAction()
        {
            //do view action
        }
    }
    public class deleteAction : Action
    {
        public void doAction()
        {
            //do delete action
        }
    }
    public class ModifyAction : Action
    {
        public void doAction()
        {
            //do modify action
        }
    }
    //这样,客户端的调用大概如下:
    public void execute(Action action)
    {
        action.doAction();
    }
    看,上面的客户端代码再也没有出现过typeof这样的语句,扩展性良好,也有了运行期内绑定的优点。

    三、LSP优点:
    1、保证系统或子系统有良好的扩展性。只有子类能够完全替换父类,才能保证系统或子系统在运行期内识别子类就可以了,因而使得系统或子系统有了良好的扩展性。
    2、实现运行期内绑定,即保证了面向对象多态性的顺利进行。这节省了大量的代码重复或冗余。避免了类似instanceof这样的语句,或者getClass()这样的语句,这些语句是面向对象所忌讳的。
    3、有利于实现契约式编程。契约式编程有利于系统的分析和设计,指我们在分析和设计的时候,定义好系统的接口,然后再编码的时候实现这些接口即可。在父类里定义好子类需要实现的功能,而子类只要实现这些功能即可。
     
    四、使用LSP注意点:
    1、此原则和OCP的作用有点类似,其实这些面向对象的基本原则就2条:1:面向接口编程,而不是面向实现;2:用组合而不主张用继承
    2、LSP是保证OCP的重要原则
    3、这些基本的原则在实现方法上也有个共同层次,就是使用中间接口层,以此来达到类对象的低偶合,也就是抽象偶合!
    4、派生类的退化函数:派生类的某些函数退化(变得没有用处),Base的使用者不知道不能调用f,会导致替换违规。在派生类中存在退化函数并不总是表示违反了LSP,但是当存在这种情况时,应该引起注意。 
    5、从派生类抛出异常:如果在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把他们添加到派生类的方法中就可以能会导致不可替换性。
     
     
     
     
  • 相关阅读:
    阿里云服务器mysql连接不了2003 -can't connect to mysql server on 'ip' (10038)
    androidstudio代码混淆
    apk反编译
    android bottomnavicationbar底部图标vector设计
    Java++:不要随意使用 Cookie 会引火烧身的
    Java++:用户认证-基于 jwt 和 session 的区别和优缺点
    Spring++:AOP 拦截 Service | Controller 日志
    Spring++:整合 Retry 实现重试机制
    (xxl_job | quartz):XXL_JOB 对比 Quartz 一决高下!
    Spring++:定时任务 Cron表达式详解
  • 原文地址:https://www.cnblogs.com/smiler/p/3265916.html
Copyright © 2011-2022 走看看