设计模式之六大原则之里氏替换原则

1. 定义

        第一种定义:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

        第二种定义:所有引用基类的地方必须能透明地使用其子类的对象。通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。

2. 规则

        里氏替换原则为良好的继承定义了一个规范。

2.1 子类必须完全实现父类的方法

        我们在做系统设计时,经常会定义一个接口或抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这里已经使用了里氏替换原则。

 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace helloworld
{
    class Program
    {
        static void Main(string[] args)
        {
            // 产生三毛这个士兵
             Soldier sanMao = new Soldier();
            //在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
            sanMao.setGun(new Rifle());
            sanMao.killEnemy();

        }
    }
    /// <summary>
    /// 定义了枪支的抽象类
    /// </summary>
    public abstract class AbstractGun
    {
        //枪用来干什么的?杀敌!
        public abstract void shoot();
    }
    public class Handgun : AbstractGun
    {
        //手枪的特点是携带方便,射程短
        public override void shoot()
        {
            Console.WriteLine("手枪射击...");
        }
    }
    public class Rifle : AbstractGun
    {
        //步枪的特点是射程远,威力大
        public override void shoot()
        {
            Console.WriteLine("步枪射击...");
        }
    }
    public class MachineGun : AbstractGun
    {
        public override void shoot()
        {
            Console.WriteLine("机枪扫射...");
        }
    }
    public class Soldier
    {
        //定义士兵的枪支
        private AbstractGun gun;
        //给士兵一支枪
        public void setGun(AbstractGun _gun)
        {
            this.gun = _gun;
        }
        public void killEnemy()
        {
            Console.WriteLine("士兵开始杀敌人...");
            gun.shoot();
        }
    }
}

         我们再来想一想,如果我们有一个玩具手枪,该如何定义呢?我们先在类图2-1上增加一个类ToyGun,然后继承于AbstractGun类,修改后的类图如图2-2所示。

        首先我们想,玩具枪是不能用来射击的,杀不死人的,这个不应该写在shoot方法中。

public class ToyGun : AbstractGun
    {
         //玩具枪是不能射击的,但是编译器又要求实现这个方法,怎么办?虚构一个呗!
         public override void  shoot()
        {
            //玩具枪不能射击,这个方法就不实现了
        }
    }

         在Soldier类中增加instanceof的判断,如果是玩具枪,就不用来杀敌人。这个方法可以解决问题,但是你要知道,在程序中,每增加一个类,所有与这个父类有关系的类都必须修改,你觉得可行吗?

        ToyGun应该脱离继承,建立一个独立的父类,为了实现代码复用,可以与AbastractGun建立关联委托关系。

         如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

2.2 子类可以有自己的个性

2.3 覆盖或实现父类的方法时输入参数可以被放大

        方法中的输入参数称为前置条件,这是什么意思呢?大家做过Web Service开发就应该知道有一个“契约优先”的原则,也就是先定义出WSDL接口,制定好双方的开发协议,然后再各自实现。里氏替换原则也要求制定一个契约,就是父类或接口,这种设计方法也叫做Design by Contract(契约设计),与里氏替换原则有着异曲同工之妙。契约制定了,也就同时制定了前置条件和后置条件,前置条件就是你要让我执行,就必须满足我的条件;后置条件就是我执行完了需要反馈,标准是什么。

2.4 覆写或实现父类的方法时输出结果可以被缩小

        父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类        

3.实践

        采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值