设计模式——二:里氏替换原则

GD32F307 USB主机模式 外接U盘升级 调试问题记录 在移植USB驱动做主机读U盘时遇到的问题。 问题1、移植USB驱动完成后,插入U盘,一直在 usbh_core_task 的默认case 和测试case中反复执行无法执行到后面的连接及以后得步骤。 问题2、在 usbh_core_task 函数中,一直卡在枚举的case,无法执行下面操作。具体卡在urb_wait的while循环无法跳出。 阅读详情

什么是里氏替换原则?Why里氏替换原则?

先看看里氏替换原则(Liskov Substitution Principle)的定义:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

所有引用基类的地方必须能透明地使用其子类的对象。

通俗的说,子类可以扩展父类功能,但不能改变父类原有功能。
 

在这里插入图片描述

这是一位女士Barbara Liskov 在1988 年提出——这是最原始的定义:

“ What is wanted here is something like the following substitution property: 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. ”

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

在面向对象的语言里,继承无疑是一个优秀的特性,它有提高代码复用性、扩展性等优点,但是白璧微瑕,它同样也会带来一些不足:
● 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
● 降低了代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约 束;
● 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。

为了扬长避短,就有必要引入里氏替换原则。

里氏抽丝剥茧

里氏替换原则包含了四层含义:
 

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

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

举个例子来说明这个原则,以CS游戏为例,来描述一下里面用到的枪,类图如图2-1所示:

2-1:CS游戏中的枪支类图

在这里插入图片描述


枪的主要职责是射击,如何射击在各个具体的子类中定义,手枪是单发射程比较近,步枪威力大射程远,机枪用于扫射。在士兵类中定义了一个方法killEnemy,使用枪来杀敌人, 具体使用什么枪来杀敌人,调用的时候才知道。

枪支抽象类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">AbstractGun</span> {
  <span style="color:#a0a1a7"><em>//枪用来干什么的?杀敌! </em></span>
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">shoot</span>();
}
</code></strong>

手枪、步枪、机枪的实现类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Handgun</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">AbstractGun</span> { 
   <span style="color:#a0a1a7"><em>//手枪的特点是携带方便,射程短 </em></span>
   <span style="color:#4078f2">@Override</span> 
   <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">shoot</span>() { 
     System.out.println(<span style="color:#50a14f">"手枪射击..."</span>); 
     }
 }
 

<span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Rifle</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">AbstractGun</span>{ 
  <span style="color:#a0a1a7"><em>//步枪的特点是射程远,威力大 </em></span>
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">shoot</span>(){ 
    System.out.println(<span style="color:#50a14f">"步枪射击..."</span>); 
    } 
}


<span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">MachineGun</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">AbstractGun</span>{ 
    <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">shoot</span>(){
      System.out.println(<span style="color:#50a14f">"机枪扫射..."</span>);
     }
}
</code></strong>

士兵的实现类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Soldier</span> { 
   <span style="color:#a0a1a7"><em>//定义士兵的枪支 </em></span>
   <span style="color:#a626a4">private</span> AbstractGun gun; <span style="color:#a0a1a7"><em>//给士兵一支枪 </em></span>
   <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">setGun</span>(AbstractGun _gun){ 
       <span style="color:#c18401">this</span>.gun = _gun;
   }
  
   <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">killEnemy</span>(){ 
     System.out.println(<span style="color:#50a14f">"士兵开始杀敌人..."</span>); 
     gun.shoot();
   }

 }
</code></strong>

定义士兵使用枪来杀敌,但是这把枪是抽象的,具体是手枪还是步枪需 要在上战场前(也就是场景中)前通过setGun方法确定。场景类Client如下:

场景类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { 
   <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) {
      <span style="color:#a0a1a7"><em>//产生三毛这个士兵 </em></span>
      <span style="color:#986801">Soldier</span> <span style="color:#986801">sanMao</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">Soldier</span>(); <span style="color:#a0a1a7"><em>//给三毛一支枪</em></span>
      sanMao.setGun(<span style="color:#a626a4">new</span> <span style="color:#4078f2">Rifle</span>());
      sanMao.killEnemy(); 
    } 
}
</code></strong>

在这个程序中,给三毛这个士兵一把步枪,然后就开始杀敌了。如果三毛要使用机枪,当然也可以,直接把sanMao.setGun(new Rifle())修改为sanMao.setGun(new MachineGun()) 即可,在编写程序时Solider士兵类根本就不用知道是哪个型号的枪(子类)被传入。

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

2-2:枪支类图


在这里插入图片描述

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

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">ToyGun</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">AbstractGun</span> { 
  <span style="color:#a0a1a7"><em>//玩具枪是不能射击的,但是编译器又要求实现这个方法,怎么办?虚构一个呗!  </em></span>
   <span style="color:#4078f2">@Override</span> 
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">shoot</span>() { 
  <span style="color:#a0a1a7"><em>//玩具枪不能射击,这个方法就不实现了</em></span>
   } 
}
</code></strong>

由于引入了新的子类,场景类中也使用了该类,Client类稍作修改:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { 
    <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) { 
       <span style="color:#a0a1a7"><em>//产生三毛这个士兵 Soldier sanMao = new Soldier();     </em></span>
       sanMao.setGun(<span style="color:#a626a4">new</span> <span style="color:#4078f2">ToyGun</span>()); sanMao.killEnemy(); 
     } 
}
</code></strong>

结果:

复制代码
<strong><code class="language-java">士兵开始杀敌人...
</code></strong>


玩具枪开始杀人了!在这种情况下,我们发现业务调用类已 经出现了问题,正常的业务逻辑已经不能运行,那怎么办?有两种办法。


● 在Soldier类中增加instanceof的判断,如果是玩具枪,就不用来杀敌人。这个方法可以 解决问题,但是,在程序中,每增加一个类,所有与这个父类有关系的类都必须修改,显然,这个方案被否定了。

● ToyGun脱离继承,建立一个独立的父类,为了实现代码复用,可以与AbastractGun建 立关联委托关系,如图2-3所示:
 

2-3:玩具枪与真实枪分离的类图

在这里插入图片描述
例如,可以在AbstractToy中声明将声音、形状都委托给AbstractGun处理,然后两个基类下的子类自由延展,互不影响。

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

* 子类可以有自己的个性

子类可以有自己的行为和外观了,也就是方法和属性—— 里氏替换原则可以正着用,但是不能反过来用。在子类出现的地方,父类未必就可以胜任。还是以刚才的关于枪支的例子为例,步枪有几个比较“响亮”的型号,比如AK47、AUG 狙击步枪等,把这两个型号的枪引入后的Rifle子类图如图2-4所示:

2-4:增加AK47和AUG后的Rifle子类图


在这里插入图片描述

AUG继承了Rifle类,狙击手(Snipper)则直接使用AUG狙击步枪。

AUG狙击枪类代码:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">AUG</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Rifle</span> { 
 <span style="color:#a0a1a7"><em>//狙击枪都携带一个精准的望远镜</em></span>
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">zoomOut</span>(){ 
   System.out.println(<span style="color:#50a14f">"通过望远镜察看敌人..."</span>);
 }
 <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">shoot</span>(){ 
   System.out.println(<span style="color:#50a14f">"AUG射击..."</span>); 
 }
 
}
</code></strong>

狙击手类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Snipper</span> { 
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">killEnemy</span>(AUG aug){ 
    <span style="color:#a0a1a7"><em>//首先看看敌人的情况,别杀死敌人,自己也被人干掉 </em></span>
    aug.zoomOut(); 
    <span style="color:#a0a1a7"><em>//开始射击 </em></span>
    aug.shoot();
 } 
}
</code></strong>


狙击手使用狙击枪来杀死敌人,业务场景Client类如下:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { 
   <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) { 
     <span style="color:#a0a1a7"><em>//产生三毛这个狙击手 </em></span>
     <span style="color:#986801">Snipper</span> <span style="color:#986801">sanMao</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">Snipper</span>();
     sanMao.setRifle(<span style="color:#a626a4">new</span> <span style="color:#4078f2">AUG</span>()); 
     sanMao.killEnemy(); 
   }
}
</code></strong>

运行结果:

复制代码
<strong><code class="language-java">通过望远镜察看敌人...
AUG射击...
</code></strong>

在这里,系统直接调用了子类,狙击手是很依赖枪支的,别说换一个型号的枪了,就是换一个同型号的枪也会影响射击,所以这里就直接把子类传递了进来。这个时候,我们能不 能直接使用父类传递进来呢?修改一下Client类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { 
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) { 
    <span style="color:#a0a1a7"><em>//产生三毛这个狙击手 </em></span>
    <span style="color:#986801">Snipper</span> <span style="color:#986801">sanMao</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">Snipper</span>(); 
    sanMao.setRifle((AUG)(<span style="color:#a626a4">new</span> <span style="color:#4078f2">Rifle</span>()));
    sanMao.killEnemy(); 
   }
}
</code></strong>

显然是不行的,会在运行期抛出java.lang.ClassCastException异常,这也是大家经常说的 向下转型(downcast)是不安全的,从里氏替换原则来看,就是有子类出现的地方父类未必就可以出现。

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

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

先定义一个Father类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Father</span> { 
  <span style="color:#a0a1a7"><em>//把HashMap转换为Collection集合类型</em></span>
  <span style="color:#a626a4">public</span> Collection <span style="color:#4078f2">doSomething</span>(HashMap map){
     System.out.println(<span style="color:#50a14f">"父类被执行..."</span>); 
     <span style="color:#a626a4">return</span> map.values();
  } 
}
</code></strong>

再定义一个子类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Son</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Father</span> { 
  <span style="color:#a0a1a7"><em>//放大输入参数类型 </em></span>
  <span style="color:#a626a4">public</span> Collection <span style="color:#4078f2">doSomething</span>(Map map){ 
     System.out.println(<span style="color:#50a14f">"子类被执行..."</span>);
      <span style="color:#a626a4">return</span> map.values();
 } 
}
</code></strong>

子类的doSomething方法与父类的方法名相同,但又不是覆写(Override)父类的方法。方法名虽然相同,但方法的输入参数不同,就不是覆写,是重载(Overload)!——继承,子类拥有父类的所有属性和方法,方法名相同,输入参数类型又不 相同,当然是重载。

场景类的调用如下:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { 
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">invoker</span>(){
   <span style="color:#a0a1a7"><em>//父类存在的地方,子类就应该能够存在 </em></span>
   <span style="color:#986801">Father</span> <span style="color:#986801">f</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">Father</span>(); 
   <span style="color:#986801">HashMap</span> <span style="color:#986801">map</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">HashMap</span>(); 
   f.doSomething(map); 
  }
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) { 
    invoker();
 }
}
</code></strong>

运行结果:

复制代码
<strong><code class="language-java">父类被执行...
</code></strong>


根据里氏替换原则,父类出现的地方子类就可以出现,修改场景类:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { 
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">invoker</span>(){ 
    <span style="color:#a0a1a7"><em>//父类存在的地方,子类就应该能够存在 </em></span>
    <span style="color:#986801">Son</span> <span style="color:#986801">f</span> <span style="color:#ab5656">=</span><span style="color:#a626a4">new</span> <span style="color:#4078f2">Son</span>(); 
    <span style="color:#986801">HashMap</span> <span style="color:#986801">map</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">HashMap</span>(); 
    f.doSomething(map);
  }

  <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) { 
    invoker();
  }
  
}
</code></strong>

运行结果还是一样。父类方法的输入参数是HashMap类型,子类的输入参数是Map类型,也就是说子类的输入参数类型的范围扩大了,子类代替父类传递到调用者中,子类的方法永远都不会被执行。这是正确的,如果你想让子类的方法运行,就必须覆写父类的方法。大家可以这样想,在一个Invoker类中关联了一个父类,调用了一个父 类的方法,子类可以覆写这个方法,也可以重载这个方法,前提是要扩大这个前置条件,就是输入参数的类型宽于父类的类型覆盖范围。这样说可能比较难理解,我们再反过来想一 下,如果Father类的输入参数类型宽于子类的输入参数类型,会出现什么问题呢?会出现父类存在的地方,子类就未必可以存在,因为一旦把子类作为参数传入,调用者就很可能进入 子类的方法范畴。

把上面的例子修改一下,扩大父类的前置条件:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Father</span> { 
    <span style="color:#a626a4">public</span> Collection <span style="color:#4078f2">doSomething</span>(Map map){
      System.out.println(<span style="color:#50a14f">"父类被执行..."</span>); 
      <span style="color:#a626a4">return</span> map.values(); 
     }
 }
</code></strong>

把父类的前置条件修改为Map类型,再修改一下子类方法的输入参数,相对父类缩小输入参数的类型范围,也就是缩小前置条件:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Son</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Father</span> {
  <span style="color:#a0a1a7"><em>//缩小输入参数范围 </em></span>
  <span style="color:#a626a4">public</span> Collection <span style="color:#4078f2">doSomething</span>(HashMap map){
    System.out.println(<span style="color:#50a14f">"子类被执行..."</span>);
    <span style="color:#a626a4">return</span> map.values();
 } 
}
</code></strong>

在父类的前置条件大于子类的前置条件的情况下,业务场景:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { 
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">invoker</span>(){ 
    <span style="color:#a0a1a7"><em>//有父类的地方就有子类</em></span>
     Father f= <span style="color:#a626a4">new</span> <span style="color:#4078f2">Father</span>(); 
     <span style="color:#986801">HashMap</span> <span style="color:#986801">map</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">HashMap</span>(); 
     f.doSomething(map); 
  }
  
  <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) { 
   invoker(); 
  } 
}
</code></strong>

运行结果:

复制代码
<strong><code class="language-java">父类被执行...
</code></strong>

再把里氏替换原则引入进来——有父类的地方子类就可以使用,把Client类修改一下:

复制代码
<strong><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> { <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">invoker</span>(){ 
  <span style="color:#a0a1a7"><em>//有父类的地方就有子类 </em></span>
  <span style="color:#986801">Son</span> <span style="color:#986801">f</span> <span style="color:#ab5656">=</span><span style="color:#a626a4">new</span> <span style="color:#4078f2">Son</span>(); 
  <span style="color:#986801">HashMap</span> <span style="color:#986801">map</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">HashMap</span>(); 
  f.doSomething(map);
 }
 <span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) {
   invoker(); 
 } 

}
</code></strong>

运行结果:

复制代码
<strong><code class="language-java">子类被执行...
</code></strong>

子类在没有覆写父类的方法的前提下,子类方法被执行了,这会引起业务 逻辑混乱,因为在实际应用中父类一般都是抽象类,子类是实现类,你传递一个这样的实现 类就会“歪曲”了父类的意图,引起一堆意想不到的业务逻辑混乱,所以子类中方法的前置条 件必须与超类中被覆写的方法的前置条件相同或者更宽松。
 

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

父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一 个类型,要么S是T的子类,为什么呢?分两种情况,如果是覆写,父类和子类的同名方法的 输入参数是相同的,两个方法的范围值S小于等于T,这是覆写的要求,这才是重中之重,子类覆写父类的方法,天经地义。如果是重载,则要求方法的输入参数类型或数量不相同,在 里氏替换原则要求下,就是子类的输入参数宽于或等于父类的输入参数,也就是说你写的这 个方法是不会被调用的,参考上面讲的前置条件。

采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。

某物data魔改与变种算法还原(含加解密Py纯算) 某物data加解密、设备指纹魔改、变异、自定义加密分析与纯算还原 阅读详情

相关推荐

EMGPro 肌肉电手势识别手环传感器

EMG手势识别技术需要进行多方面的工作,包括传感器的设计和布局、EMG信号的采集和预处理、特征提取和分类等。通过对采集到的信号进行特征提取和分类,可以将手势信号与预定义的手势模型进行匹配,从而实现手势识别和控制。通过使用EMG手势识别技术,用户可以更加自由地操纵设备,使得我们的交互方式更加智能和自然。EMG手势识别是利用EMG信号测量肌肉运动的实时性和高精度性,采用机器学习等技术进行数据处理和分析,并将其应用于手势识别领域的一种技术。

m0_68593468的博客 1390

设计模式—— 二:里氏替换原则

文章目录什么是里氏替换原则?Why里氏替换原则?里氏抽丝剥茧 什么是里氏替换原则?Why里氏替换原则? 先看看里氏替换原则(Liskov Substitution Principle)的定义: Functions that use pointers or references to base classes must be able to use objects of derived clas...

三分恶的博客 1868

latex使用overleaf插入多行图片

对于一般的latex模板,可以使用下面方式: (1)现在模板最前面的加入包 \usepackage{graphicx} \usepackage{subfigure} (2) 使用下面代码插入图片 \begin{figure}[htbp] \centering \subfigure[pic1.]{ \includegraphics[width=5.5cm]{pic1.png} } \quad \subfigure[pic2.]{ \includegraphics[width=5.5cm]{pic

Look Forward To 1万+

里氏替换原则

该原则的核心思想就是在程序当中,如果将一个父类对象替换成它的子类对象后,该程序不会发生异常。这也是该原则希望达到的一种理想状态。通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。里氏代换原则是开闭原则的重要方式之一,由于使用父类对象的地方都可以使用子类对象,因此在程序中尽量使用父类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。优点:1、代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原

weixin_39327556的博客 1万+

Unity URP炫彩干涉效果:从薄膜干涉原理到Shader Graph实现

薄膜干涉是一种常见的光学现象,指光在透明薄膜上下表面反射后产生干涉,形成彩虹色条纹。其原理基于光程差导致的相长与相消干涉,不同波长的光被选择性增强或削弱。在计算机图形学中,这一物理现象被抽象为高效的数学模型,通过Shader编程实现动态色彩变化,为游戏和实时渲染带来独特的视觉表现力。技术实现上,常采用正弦函数结合噪声纹理来模拟色彩循环,利用法线与视角点积作为基础变量,加入噪声扰动模拟厚度不均,实现随视角变化的虹彩效果。在Unity URP渲染管线中,可通过Shader Graph可视化工具或手写HLSL代码

weixin_34026997的博客 296

学习STM32的无人机控制

下面是一个基于STM32的无人机控制的简单示例代码,包括四旋翼的PID控制、遥控器信号解析和电机控制。本示例以STM32F103系列为例,使用Keil C编译器进行开发。

qq_67153941的博客 707

设计原则之里氏替换原则详解

一、里氏替换原则定义 定义: 如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得所有以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生任何变化,那么类型T2是类型T1的子类型。 通俗理解就是:子类可以扩展父类的功能,但不能改变父类原有的功能。有以下几个引申含义: 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。 子类中可以增加自己特有的方法。 当子类的方法重载父类的方法时,方法的前置条件(方法的输入,入参)要比父类的入参更宽松。 当子类的方法实现父类的方法时(

qq_38925911的博客 6111

头歌OpenGauss数据库 - 单表查询 答案

本关任务:用SELECT语句检索数据表中指定字段的数据;用SELECT语句检索数据表中所有字段的数据。为了完成本关任务,你需要掌握: 1.如何获取数据表中指定字段的数据;2.如何获取数据表中的所有数据。

d214493517的博客 5722

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

里氏替换原则LSP)是面向对象设计的基本原则之一,它确保在软件系统中,当使用一个子类对象替换一个基类对象时,程序的行为保持不变。‌ 这一原则是面向对象程序设计(Object-Oriented Programming, OOP)中的一个重要概念,它强调了子类必须能够替换其基类,而不会导致程序功能或行为的变化。里氏替换原则的主要目的是确保软件的可维护性可扩展性和健壮性,同时降低需求变更时引入的风险。里氏替换原则的具体含义可以概括为以下几点‌子类必须能够替换其基类‌。

在技术的广袤天地里,本博客如精准罗盘。剖析前沿科技,深掘代码奥秘,以精炼笔触,带您穿越复杂技术迷宫,速达知识彼岸。 3080

23种设计模式-中介者(Mediator)设计模式

软考中级-软件设计师 中介者设计模式

片言可决讼,半纸能缚龙 1865

大模型安全-生成检测-事实核查-攻击-防御-思考-记忆-融合-语音相关研究 2025年版

本文整理了关于大模型安全领域的综合性资源,包括7篇综述论文、9个安全数据集以及5篇强化学习相关的安全研究论文。主要涉及大模型的对抗攻击、隐私保护、安全评估等方向,涵盖TruthfulQA、ToxiGen等知名数据集和TrustLLM、HarmBench等关键研究。资源包括知乎博主分享、arXiv预印本和顶会论文,并附有详细的中文讲解链接,为研究者提供了系统性的安全研究参考。

CSPhD-winston的博客 3601

设计模式七大原则-里氏替换原则

里氏替换原则(Liskov Substitution Principle) oo中的继承性的思考和说明: 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。 继承在给程序设计带来便利的同时,也带来了弊端,比如使用继承会给程序带来侵入性,程序的可移植性降低,增...

sinat_14840219的博客 515

[AI] 嵌入模型选择:bge / text-embedding-3 / Jina Embeddings 对比与选型指南

摘要: 本文对比了BGE-M3、OpenAI和Jina三种主流嵌入模型在语言覆盖、性能、延迟、维度和成本等维度的差异。BGE-M3适合中文场景,本地部署成本低;OpenAI API多语能力强但依赖网络;Jina在多语表现突出但需关注商用许可。测试显示BGE-M3本地延迟3-6ms,OpenAI API延迟200-600ms。选型建议:隐私场景选BGE本地部署,多语低维护选OpenAI,高精度选Jina。部署时需注意批处理优化、向量库压缩和商用许可等问题,建议建立评测指标并监控关键指标。

每日出拳老爷子的博客 428

设计模式六大原则(二):里氏替换原则(Liskov Substitution Principle)

里氏替换原则(LSP)由来: 最早是在 妖久八八 年, 由麻神理工学院得一个女士所提出来的。 定义: 1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子型。 2:所有引用基类的地方必须能透明地使用其子类的对象。 问题由来: ...

weixin_30544657的博客 190

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

[设计模式之禅读书笔记]002_设计模式六大原则(二):里氏替换原则(Liskov Substitution Principle)...

序言 本节作者用了很大的篇幅来阐述自己对里氏替换原则的理解,而且代码片段也相当翔实。笔者阅读该节之后,认为该节的核心内容可以归结如下: 1. 里氏替换原则的定义 2. 里氏替换原则的规范 里氏替换原则的定义 里氏替换原则的定义有两种: 第一种(作者认为是最正宗的,但是却是理解起来稍微有点绕的) 如果对于每一个类型为S的对象o1,都有类型为T的对象o2,使得以T...

weixin_34007879的博客 97

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

里氏替换原则_设计模式(二):里氏替换原则

什么是里氏替换原则?定义:所有引用父类的地方必须能透明地使用其子类的对象。为什么要有里氏替换原则?里氏替换原则主要阐述了关于面向对象继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了父类与子类之间的关系。里氏替换原则具有以下优点:为良好的继承定义了一个规范;提高代码的健壮性,降低程序出错的可能性;里氏替换原则的四个原则1、子...

weixin_39585761的博客 140

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【专题系列】设计模式——二:里氏替换原则

点击上方“Java大数据修炼之道”,右上角选择“设为星标”你想要的优质技术好文第一时间送达!公众号后台回复“资源”,获取独家秘制精品资料来源 |https://www.cnblogs....

hello world! 432

Java 设计模式6大原则之(二):里氏替换原则

里氏替换原则(LSP) 定义 如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T2定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。 简单理解 任何基类可以出现的地方,子类一定可以出现。 详细描述 在代码中将一个基类对象替换成它的子类对象,程序不会产生任何错误和异常,反过来则不成立,如果一个类实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。 例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此

baipenggui_bo 650

设计模式六大原则(二):里氏替换原则

里氏替换原则的定义是:所有引用基类的地方必须能透明化地使用其子类的对象。 里氏替换原则针对的问题 有一个功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。 里氏替换原则的解决方案 当使用继承的时候,遵循里氏替换原则。类B继承类A的时...

weixin_30514745的博客 255

设计模式之七大原则——里氏替换原则(LSP)(三)

里氏替换原则(Liskov Substitution Principel)是解决继承带来的问题。 继承的优点: 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性; 提高代码的重用性; 子类可以形似父类,但又异于父类; 提高代码的可扩展性; 提高产品或项目的开放性。 继承的缺点: 继承是侵入性的,只要继承就必须拥有父类的所有属性和方法; 降低代码的灵活性,子类必须拥有父类的属性和方法,让子类增加了约束; 增强了耦合性,当父类的常量、变量和方法被修改时,必须考虑子类的修改。 .

Gorden的博客 8463

设计模式六大原则——里氏替换原则(LSP)

概述        里氏替换原则(LSP,Liskov Substitution Principle)是关于继承机制的原则,是实现开放封闭原则的具体规范,违反了里氏替换原则必然违反了开放封闭原则。        引经据典

令仔很忙 8553

深入理解设计原则之里氏替换原则(LSP)【软件架构设计】

里氏替换原则(Liskov Substitution Principle, LSP)于1986年有Barbara Liskov提出,他当时是这样描述这条原则的:如果S是T的子类型,那么T的对象可以被S的对象所替换,并不影响代码的运行。1996年,Robert Martin在他的SOLID原则中重新描述了里氏替换原则:使用父类对象的函数可以在不了解子类的情况下替换为使用子类对象。 结合上面的描述,我们将里氏替换原则描述为:子类对象能够替换到程序中的父类对象出现的任何地方,并且保证程序原有的逻辑行为不变和正确性

weixin_30197685的博客 3441

【设计原则】里氏替换原则(LSP):构建稳健继承体系的黄金法则

里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计SOLID原则中的"L",由Barbara Liskov在1987年提出。所有引用基类(父类)的地方必须能透明地使用其子类的对象子类必须完全实现父类的抽象方法子类可以扩展父类功能但不能改变原有行为子类方法的前置条件不应强于父类子类方法的后置条件不应弱于父类优先使用组合而非继承保持继承层次扁平化使用设计模式:策略模式模板方法模式装饰器模式定期进行代码审查编写契约测试。

OpenSeek的博客 1990

设计模式六大原则之里氏替换原则(Liskov Substitution Principle, LSP)

里氏替换原则的定义是:“继承必须确保超类(基类)所拥有的性质在子类中仍然成立。”这一原则强调了子类与基类之间的继承关系应当遵循一定的规则,以确保在替换基类对象时,程序的行为保持不变。里氏替换原则是实现开闭原则的重要方式之一,它确保了软件系统的可扩展性和可维护性,同时降低了需求变更时引入的风险。提高代码的健壮性:通过确保子类能够替换基类而不影响程序行为,减少了因继承关系不当而导致的错误。降低程序出错的可能性:子类在扩展功能时,必须保持与基类一致的接口和行为,从而降低了程序出错的风险。增强软件的可维护性。

shiming8879的博客 1846

LSP里氏替换原则

里氏替换原则(LSP)是面向对象设计中的重要原则,强调子类必须能够无缝替换父类,且不改变程序的行为。

and的博客 630

闲谈设计模式之里氏替换原则

闲谈设计模式之里氏替换原则里氏替换原则(Liskov Substitution Principle)代码示例分析Android设计模式代码示例 里氏替换原则(Liskov Substitution Principle) 里氏替换原则:里氏替换原则俗称LSP,其含义每一个类型S的对象假设为O1,都有一个T类型的对象为O2,当业务场景需要的是O1被当作O2使用,但不会导致程序出现问题,换言之就是基类使...

易庞宙的博客 530
上一篇: 聊聊Maven的依赖传递、依赖管理、依赖作用域?
下一篇: 设计模式——三:依赖倒置原则
加班不秃顶
博客等级 码龄7年 1822粉丝 599原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值