彩票网站开发多少钱,手机app开发工具有哪些,绍兴做网站比较专业的公司,wordpress 换数据库设计模式专栏#xff1a;http://t.csdnimg.cn/4Mt4u 思考#xff1a;什么样的代码才算违反里氏替换原则#xff1f; 目录
1.里氏替换原则的定义
2.里氏替换原则与多态的区别
3.违反里氏替换原则的反模式
4.总结 1.里氏替换原则的定义 里氏替换原则#xff08;Liskov S… 设计模式专栏http://t.csdnimg.cn/4Mt4u 思考什么样的代码才算违反里氏替换原则 目录
1.里氏替换原则的定义
2.里氏替换原则与多态的区别
3.违反里氏替换原则的反模式
4.总结 1.里氏替换原则的定义 里氏替换原则Liskov Substitution principle是由芭芭拉·利斯科夫Barbara Liskov在1987年在一次会议上名为“数据的抽象与层次”的演说中首先提出。他当时是这样描述这条原则的如果S是T的子类型那么T的对象可以被S的对象所替换并不影响代码的运行。1966年Robert Martin在他的SOLID原则中重新描述了里氏替换原则使用父类对象的函数可以在不了解子类的情况下替换为使用子类对象。 结合Bartbara Liskov和Robert Martin 的描述我们将里氏替换原则描述为子类对象(object of subtype/derived class)能够替换到程序(program)中父类对象(object of base/parent class)出现的任何地方并且保证程序原有的逻辑行为(behavior)不变和正确性不被破坏。 里氏替换原则的定义比较抽象我们通过一个代码示例进行解释。其中父类 Transporter 使用org.apache.http库中的 HttpChient 类传输网络数据子类 SecurityTransporter 继承父类 Transporter增加了一些额外的功能支持在传输数据的同时传输 appld和 appToken 安全认证信息。
public class Transporter {private Httpclient httpclient;public Transporter(Httpclient httpclient){this.httpclient httpclient;}public Response sendRequest(Request request){//...省略使用httpclient发送请求的代码逻辑...}
}public class SecurityTransporter extends Transporter {private String appId;private String appToken;public SecurityTransporter (Httpclient httpclient, String appId, String appToken){super (httpClient);this.appId appId;this.appToken appToken;}Overridepublic Response sendRequest(Request request){if (StringUtils.isNotBlank(appId) stringUtils.isNotBlank(appToken)) {request.addPayload(app-id,appId);request.addPayload (app-token, appToken);}return super.sendRequest(request);}
}public class Demo {public void demorunction(Transporter transporter){Reugest request new Request();//...省略设置request中数据值的代码.Response response transporter.sendRequest (request);//...省略其他逻辑...}
}//里氏替换原则
Demo demo new Demo();
demo.demofunction (new securityTransporter(/*省略參数*/);) 在上述代码中子类SecurityTransporter的设计符合里氏替换原则其对象可以替换到父类对象出现的任何位置并且代码原来的逻辑行为不变且正确性也没有被破坏。
2.里氏替换原则与多态的区别 不过读者可能会有疑问上述代码设计不就是简单利用了面向对象的多态特性吗?多态和里氏替换原则是不是一回事?从上面的代码示例和里氏替换原则的定义来看里氏替类与多态看起来类似但实际上它们完全是两回事。 我们还是通过上面的代码示例进行解释。不过我们需要对SecuityTransporer类中sendRequest0函数稍加改造。改造前如果appld或 appToken 没有设置则不做安全校验改造后如果 appId或 appToken 没有设置则直接抛出 NoAuthorizationRunfimeException未授权异常。改造前后的代码对比如下。
//改造前:
public class SecurityTransporter extends Transporter {//...省略其他代码...Overridepublic Response sendRequest(Request request){if (stringUtils.isNotBlank(appId) StringUtils.isNotBlank(appToken)) {request.addPayload(app-id, appId);request .addPayload(app-token,appToken);}return super.sendRequest(request);}
}//改造后:
public class SecurityTransporter extends Transporter {//...省略其他代码..Overridepublic Response sendRequest(Request request){if(Stringutils.isBlank(appId) stringutils.isBlank(approken)){throw new NoAuthorizationRuntimeException(...);}request.addPayload (app-id, appId) ;request .addPayload (app-token, appToken);return super.sendRequest(request);}
} 在改造后的代码中如果传入demoFunction()函数的是父类Transporter 的对象那么demoFuncion()函数并不会抛出异常但如果传入demoFuncion()函数的是子类 SecurityTransporter的对象那么 demoFuncion()有可能抛出异常。尽管代码中抛出的是运行时异常(Runtime Exception)可以不在代码中显式地捕获处理但子类替换父类并传入 demoFunction() 函数之后,整个程序的逻辑行为有了改变。 虽然改造之后的代码仍然可以通过Java的多态语法动态地使用子类 SecwriyTansport替换父类Tansporer也并不会导致程序编译或运行报错但是从设计思路上来讲SecurityTransporter的设计是不符合里氏替换原则的。多态是一种代码实现思路、而里氏替换原则是一种设计原则用来指导维承关系中子类的设计在换父类时、确保不改变程的逻辑行为以及不破坏程序的正确性。
3.违反里氏替换原则的反模式 实际上里氏替换原则还有一个能落地且更有指导意义的描述那就是按照协议来设计。在设计子类时需要遵守父类的行为约定(或称为协议)。父类定义了函数的行为约定子类可以改变函数内部实现逻辑但本能改变函数原有的行为约定。这里的行为约定包括函数声明要实现的功能对输入、输出和异常的约定以及注释中罗列的任何特殊情况说明等。实际上、这里所讲的父类和子类的关系可以替换成接口和实现类的关系。 为了更好地理解上述内容我们提供若干违反里氏替换原则的例子。1.子类违反父类声明要实现的功能 例如父类定义了一个订单排序函数sortOrdersByAmount()该函数按照金额从小到大来给订单排序而子类重写sorOrdersByAmount()之后按照创建日期来给订单排序。那么这个子类的设计就违反了里氏替换原则。2.子类违反父类对输入、输出和异常的约定 在父类中某个函数约定运行出错时返回null获取数据为空时返回空集合(empty collection)。而子类重载此函数之后重新定义了返回值运行出错时返回异常(exception), 获取不到数据时返回 null。那么这个子类的设计就违反了里氏替换原则。 在父类中某个函数约定输入数据可以是任意整数但子类重载此函数之后只允许输入数据是正整数如果是负数就抛出异常也就是说子类对输入数据的校验比父类更加产格。那么这个子类的设计就违反了里氏替换原则。 在父类中某个函数约定只抛出 ArgumentNullException 异常那么子类重载此函数之后也只允许抛出 ArgumentNullException异常否则子类就违反了里氏替换原则。3.子类违反父类注释中罗列的任何特殊说明 在父类中定义了一个提现函数 withdraw()其注释是这样写的:“用户的提现金额不得超过账户余额……”而子类重写 withdraw() 函数之后针对 VIP 账号实现了透支提现的功能也就是提现金额可以大于账户余额。那么这个子类的设计就不符合里氏替换原则。如果想要这个子类的设计符合里氏替换原则那么较为简单的办法是修改父类的注释。 以上便是3种典型的违反里氏替换原则的反模式。 除此之外判断子类的设计实现是否违反里氏替换原则还有一个小窍门那就是用父类的单元测试验证子类的代码。如果某些单元测试运行失败就说明子类的设计实现没有完全遵守父类的约定子类有可能违反了里氏替换原则。
4.总结 里氏替换原则是面向对象设计的基本原则之一。它强调在软件设计中子类对象应当能够替换其父类对象并且替换后程序的行为应当保持不变。这一原则确保了软件系统的稳定性和可扩展性。 里氏替换原则的核心思想可以概括为
子类应当能够替换其父类并且在替换后程序的行为应当保持不变。这意味着子类必须完全遵守父类的行为约定即子类不能改变父类原有的功能。子类可以扩展父类的功能但不能改变父类原有的功能。也就是说子类在继承父类的基础上可以添加新的方法或属性但不能覆盖或修改父类的非抽象方法。 里氏替换原则的实现有助于保持软件系统的稳定性和灵活性。通过使用基类类型来对对象进行定义可以在运行时根据实际需要替换为不同的子类对象从而实现多态性。这种设计方式使得软件系统更加易于维护和扩展同时也提高了代码的可重用性。 然而在实际应用中要完全遵守里氏替换原则并不容易。有时为了实现特定的功能或优化性能可能会需要对父类的方法进行覆盖或修改。在这种情况下需要仔细权衡利弊确保修改后的子类仍然能够保持与父类相似的行为并且不会对现有的代码产生不良影响。 总之里氏替换原则是面向对象设计中的重要原则之一它有助于确保软件系统的稳定性和可扩展性。在设计和开发过程中应当尽量遵守这一原则以实现高质量的软件系统。