西安外贸网站开发wordpress 支持 标签

当前位置: 首页 > news >正文

西安外贸网站开发,wordpress 支持 标签,巴中做网站的公司,wordpress 标点排版设计模式专栏#xff1a; http://t.csdnimg.cn/4Mt4u 目录 1.引言 2.如何理解“对扩展开放、对修改关闭” 3.修改代码就意味着违反开闭原则吗 4.如何做到“对扩展开放、对修改关闭” 5.如何在项目中灵活应用开闭原则 6.总结 1.引言 开闭原则(Open Closed Principle… 设计模式专栏 http://t.csdnimg.cn/4Mt4u 目录 1.引言 2.如何理解“对扩展开放、对修改关闭” 3.修改代码就意味着违反开闭原则吗 4.如何做到“对扩展开放、对修改关闭” 5.如何在项目中灵活应用开闭原则 6.总结 1.引言 开闭原则(Open Closed PrincipleOCP)又称为“对扩展开发、对修改关闭”原则。开闭原则既是 SOLID 原则中最难理解、最难掌握的又是最有用的。之所以说开闭原则难理解是因为“怎样的代码改动才被定义为扩展’?怎样的代码改动才被定义为修改’?怎么才算满足或违反开闭原则’?修改代码就一定意味着违反开闭原则’吗?”等问题都比较难理解。之所以说开闭原则难掌握是因为“如何做到对扩展开发、对修改关闭’?如何在项目中灵活应用开闭原则’避免在追求高扩展性的同时影响代码的可读性?”等问题都比较难掌握。 之所以说开闭原则最有用是因为扩展性是代码质量的重要衡量标准。在22种经典设计模式中大部分设计模式都是为了解决代码的扩展性问题而产生的它们主要遵守的设计原则就是开闭原则。 2.如何理解“对扩展开放、对修改关闭” 开闭原则的英文描述是:software enities(modules,classes,fiuncions,etc.)should be open for extension but closed for modification。对应的中文为软件实体(模块、类和方法等)应该“对扩展开放、对修改关闭”详细表述为: 添加一个新功能时应该是在已有代码基础上扩展代码(新类和方法等)而非修改已有代码(修改模块、类和方法等)。 为了让读者更好地理解开闭原则我们举例说明。 下面是一段AR(应用程序编程接口)监控告警的代码。其中AlertRule 类存储告警规则Notification类负责告警通知支持电子邮件、短信和微信等多种通知渠道;NotificationEmergencyLevel类表示告警通知的紧急程度包括SEVERE(严重)、URGENCY(紧急)、NORMAL(普通)和 TRIVIAL(无关紧要)不同的紧急程度对应不同的通知渠道。 public class Alert{private AlertRule rule;private Notifcation notification;public Alert(AlertRule rule, Notification notification){this.rule rule;this.notifcation notifcation;}public void check(String api,long requestCount, long errorCount, long duration){long tpsrequestCount/duration;if (tps rule.getMatchedRule(api).getMaxTps()) { notification.notify(NotificationEmergencyLevel.URGENCY,…);}if(errorCountrule.getMatchedRule(api).getMaxErrorCount()){notification.notify(NotifcationEmergencyLevel.SEVERE,…);}} } 上面这段代码的业务逻辑主要集中在check()函数中。当接口的TPS(Transactions PerSecond每秒事务数)超过预先设置的最大值时或者当接口请求出错数大于最大允许值时就会触发告警通知接口的相关负责人或团队。 如果我们需要添加更多的告警规则:“当每秒接口超时请求个数超过预先设置的最大值时也要触发告警并发送通知”那么如何改动代码呢?代码的主要改动有两处:第一处是修改check()函数的入参添加一个新的统计数据 timeoutCount表示超时接口请求数; 第二处是有check()函数中添加新的告警逻辑。具体的代码改动如下所示。 public class Alert{//…省略AlertRule/Notifcation属性和构造函数…//改动一:添加参数timeoutCountpublic void check(String api, long regueatcount, long erorcount, long timeoutCount, long duration){long tpsreqestCount /duration;if (tps rule.getMatchedRule(api) .getMaxTps()){notification.notify(NotificationEmergencyLevel.URGENCY, …);}if(errorCount rule.getMatchedRule(api).getMaxErrorCount())){notification.notify(NotificationEmergencyLevel.SEVERE, …);}//改动二:添加接口超时处理逻辑long timeoutTpstimeoutCount /duration;if(timeoutTps rule.getMatchedRule(api).getMaxTimeoutTps()) {notification.notify(NotificationEmergencyLevel.URGENCY, …);}} } 上述代码的改动带来下列两方面的问题。一方面对接口进行了修改调用这个接口的代码就要做相应的修改。另一方面修改了check()函数相应的单元测试需要修改。 上述代码改动是基于“修改”方式增加新的告警。如果我们遵守开闭原则也就是“对扩展开放、对修改关闭”那么如何通过“扩展”方式增加新的告警呢? 我们先重构添加新的告警之前的 Alert类的代码让它的扩展性更好。重构的内容主要包含两部分:第一部分是将check()函数的多个入参封装成ApiStatInfo类;第二部分是引入handler(告警处理器)将if判断逻辑分散到各个handler 中。具体的代码实现如下。 public class Alert{private listAlertHandler alertHandlers new ArrayList();public void addAlertHandler(AlertHandler alertHandler) { this.alertHandlers.add(alertHandler);}public void check(ApiStatInfo apistatInfo){for(AlertHandler handler:alertHandlers){handler.check(apistatInfo);}}public class ApistatInfo{//省略constructor、getter和setter方法private string api;private long requestCount;private long errorCount;private long duration;}public abstract class AlertHandler(protected AlertRule rule;protected Notification notification;public AlertHandler(AlertRule rule, Notifcation notification){this.rule rule;this.notificationnotifcation;}public abstract void check(ApiStatInfo apistatInfo) ;}public class TpsAlertHandler extends AlertHandler {public TpsAlertHandler(AlertRule rule, Notification notification){super(rule, notifcation);}}Overridepublic void check(ApiStatInfo apistatInfo){long tps apiStatInfo.getReguestcount () / apiStatInfo.getpuration();if (tps rule.getMatchedRule(apiStatInfo.getApi()).getMaxTps()){ notification.notify(NotifcationEmergencyLevel.URGENCY, …);}}}public class ErrorAlertHandler extends AlertHandler{public ErrorAlertHandler (AlertRule rule, Notifcation notification){super(rule, notifcation);}Overridepublic void check (ApiStatInfo apistatInfo) {if (apistatInfo.getErrorCount() rule.getMatchedRule(apistatInfo.getApi()).getMaxErrorCount()){notifcation.notify(NotifcationEmergencyLevel.SEVERE,…);}} } 接下来我们看一下重构之后的Alent类的具体使用方式如下列代码所示。其中ApplicationContext是一个单例类,负责Alert类的创建、组装(alertRule和notification 的依赖注入)和初始化(添加handler)。 public class ApplicationContext{private AlertRule alertRule;private Notifcation notification;private Alert alert;public void initializeBeans(){alertRulenew AlertRule(/.省略参数,/);//省略一些初始化代码notifcationnew Notification(/.省略参数./);//省略一些初始化代码alertnew Alert();alert.addAlertHandler(new TpsAlertHandler(alertRule,notifcation));alert.addAlertHandler(new ErrorAlertHandler(alertRule, notifcatíon));}public Alert getAlert(){ return alert; )//“饿汉式”单例private static final Applicationcontext instance new ApplicationContext();private ApplicationContext(){initializeBeans();}public static ApplicationContext getInstance(){return instance;} }public class Demo{public static void main(string[] args){ApiStatInfo apistatInfo new ApiStatInfo();//…省略设置apistatInfo数据值的代码Applicationcontext.getinstance().getAlert().check(apistatInfo)} } 对于重构之后的代码如果添加新的告警:“如果每秒接口超时请求个数超过最大值就告警”那么如何改动代码呢?主要的改动有下面4处。 改动一: 在 ApiStatInfo 类中添加新属性 timeoutCount。 改动二:添加新的 TimeoutAlertHander 类。 改动三:在ApplicationContecxt 类的 initializeBeans()方法中向 alert对象中注册  Timeout-AlertHandler。 改动四:使用 Alert 类时需要给check()函数的入参 apiStatInfo对象设置timeoutCount属性值。 改动之后的代码如下所示 public class Alert{//代码未改动}public class ApistatInfo{//省略constructorgetter和setter方法private String api;private long requegtCount;private long errorCount;private long duratlon; private 1ong timeoutCount;//改动一:添加新属性timeoutcount }public abstract class AlertHandler{//代码未改动 public class TpsAlertHandler extends AlertHandler{ //代码未改动} public class ErrorAlertHandler extends AlertHandler { //代码未改动} //改动二:添加新的TimeoutAlertHander类 public class TimeoutAlertHandler extends AlertHandler {//省略代码public class ApplicationContext{private AlertRule alertRule;private Notification notifcation;private Alert alert;public void initinlizeBeanst{alertRule new AlertRule(/.省路参数。/);//省略一些初始化代码 notification new Notification(/.省略参数./);//省路一些初始化代码alert now Alert();alert.addAlertHandler(new psAlertHandler(alertRule, notification ));alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));//改动三:向alert对象中注册TimeoutAlertHandleralert.addAlertHandler(new imeoutAlertHandler(alertRule, notification));}//…省略其他未改动代码 }public class Demo{public static void main(Stringl] args){ApiStatInfo apiStatInfo new ApistatInfo();apistatInfo.setTimeoutCount(289);//改动四:设置timeoutCount值//…省路apistatInfo的set字段代码ApplicationContext.getInstance().getAlert().check(apiStatInfo); } 重构之后的代码更加灵活更容易扩展。如果想要添加新的告警那么只需要基于扩展的方式创建新的handler类不需要改动check()函数。不仅如此我们只需要为新的handler头添加新的单元测试旧的单元测试都不会失败也不用修改。 3.修改代码就意味着违反开闭原则吗 读者可能对上面重构之后的代码产生疑间:在添加新的告警时尽管改动二(添加新的TimeoutAlertHander类)是基于扩展而非修改的方式完成的但改动一、改动三和改动四是基于修改而非扩展的方式完成的改动一、改动三和改动四不违反开闭原则吗? 我们先分析一下改动一: 在 ApiStatInfo类中添加新属性 timeoutCount。 在改动一中我们不仅在ApiStatInfo的类中添加了新的属性还添加了对应的getter和setter方法。那么上述问题就转化为在类中添加新的属性和方法属于“修改”还是“扩展” 我们回忆一下开闭原则的定义: 软件实体(模块、类和方法等)应该“对扩展开放、对修改关闭”。从定义中可以看出开闭原则作用的对象可以是不同粒度的代码如模块、类和方法(及其属性)。对于同一代码改动在粗代码粒度下可以被认定为“修改”在细代码粒度下可以被认定为“扩展”。例如“改动一”中添加属性和方法相当于修改类在类这个层面这个代码改动可以被认定为“修改”;但这个代码改动并没有修改已有的属性和方法在方法(及其属性)这一层面它又可以被认定为“扩展”。 实际上我们没有必要纠结某个代码改动是“修改”还是“扩展”更没有必要纠结它是否违反“开闭原则”。回到开闭原则的设计初衷:只要代码改动没有破坏原有代码的正常运行和原有的单元测试我们就可以认为这是一个合格的代码改动。 我们再来分析一下改动三和改动四:在ApplicationContext类的initializeBeans()方法中向alert 对象中注册 TimeoutAlertHandler;使用Alert类时给check()函数的入参 apiStatInfo对象设置 timeoutCount 属性值。 这两处改动是在方法内部进行的无论从哪个层面(模块、类、方法)来看都不能算是“扩展”而是“修改”。不过有些修改是在所难免的是可以接受的。 在重构之后的 Alent 类代码中核心逻辑集中在Alent类及其各个handler类中。当添加的告警时Alen类完全不需要修改而只需要扩展(新增)一个handler类。如果把 Alent类及其各个handler 类看作一个“模块”那么从模块这个层面来说向模块添加新功能时只需要扩展不需要修改完全满足开闭原则。 我们也要认识到添加一个新功能时不可能做到任何模块、类和方法的代码都不“修改”。类需要创建、组装并且会进行一些初始化操作这样才能构建可运行的程序这部分代码的修改在所难免。我们努力的方向是尽量让修改操作集中在上层代码中尽量让核心、复杂、通用、底层的那部分代码满足开闭原则。 4.如何做到“对扩展开放、对修改关闭” 在上面的 Alert类的例子中我们通过引入一组 handler 类的方式满足了开闭原则。如果读者没有太多复杂代码的设计和开发经验就可能有这样的疑问:这样的代码设计思路我怎么想不到呢?你是怎么想到的呢? 实际上之所以作者能够想到依靠的是扎实的理论知识和丰富的实战经验这需要读者慢慢学习和积累。对于如何做到“对扩展开放、对修改关闭”作者有一些指导思想和具体方法分享给读者。 实际上开闭原则涉及的就是代码的扩展性问题该原则是判断一段代码是否易扩展的“金标准”。如果某段代码在应对未来需求变化时能够到“对扩展开放、对修改关闭”就说明这段代码的扩展性很好。 为了写出扩展性好的代码我们需要具备扩展意识、抽象意识和封装意识。这些意识可能比任何开发技巧都重要。 在编写代码时我们需要多花点时间思考: 对于当前这段代码未来可能有哪些需求变更。如何设计代码结构事先预留了扩展点在未来进行需求变更时不需要改动代码的整体结构新的代码能够灵活地插入到扩展点上完成需求变更从而实现代码的最小化改动。 我们还要善于识别代码中的可变部分和不可变部分。我们将可变部分封装达到隔离变化的效果并提供抽象化的不可变接口给上层系统使用。当具体的实现发生变化时只需要基于相同的抽象接口扩展一个新的实现替换旧的实现上层系统的代码几乎不需要修改。 为了实现开闭原则除在写代设时我们需要时间具备扩展意识、抽象意识、封装意识以外我们还有一些具体的方法可以使用。 代码的扩展性是评判代码质量的重要标准。实际上本文涉及的大部分知识点都是围绕如何提高高代码的扩展性来展开讲解的本文提到的大部分设计原则和设计模式都是以提高代码的扩展性为最终目的。22种经典设计模式中的大部分都是为了解决代码的扩展性问题而总结出来的都是以开闭原则为指导原则而设计的。 有众多的设计原则和设计模式中常用来提高代码扩展性的方法包括多态、依赖注入、基于接口而非实现编程以及大部分的设计模式(如策略模式、模板方法模式和职责链模式等)设计模式这一部分的内容较多后面也会详细讲解。本节通过一个简单例子来介绍如何利用多态、依赖注入、基于接口而非实现编程实现开闭原则。 例如我们希望实现通过Kafka发送异步消息。对于这样一个功能的开发我们抽象定义一组与具体消息队列(Kafka)无关的异步消息发送接口。所有上层系统都依赖这组抽象的接口编程并且通过依赖注入的方式来调用。当需要替换消息队列或消息格式时如将Kafka替换成RocketMQ或将消息的格式从JSON替换为XML因为代码设计满足开闭原则所以替换起来非常轻松。具体的代码实现如下所示。 //这一部分代码体现了抽象意识 public interface MessageQueue(…) public class KafkaMessageQueue implements MessageQueue {…) public class RocketMQMessageQueue implements MessageQueue (…)public interface MessageFromatter{…} public class JsonMessagerromatter implements MessageFromatter {..) public class ProtoBufMessageFromatter implements MessageFromatter { … }public class Demo(private MessageQueue msgQueue;//基于接口而非实现编程public Demo(MessageQueue msgQueue){ //依赖注入this.msqQueue msgQueue;}public void send (Notification notification, Messageformatter msgforatter) {…} } 5.如何在项目中灵活应用开闭原则 上文提到写出支持开闭原则(扩展性好)的代码的关键是预留扩展点。如何才能识别出有可能的扩展点呢? 如果我们开发的是业务系统如金融系统、电商系统和物流系统等要想识别出尽可能多的扩展点就要对业务有足够的了解。只有这样才能预见未来可能要支持的业务需求。如要我们开发的是与业务无关的、通用的、偏底层的功能模块如框架、组件和类库如果想设出尽可能多的扩展点就需要了解它们会被如何使用和使用者未来会有哪些功能需求等。 但是即使我们对业务和系统有足够的了解也不可能识别出所有的扩展点。即便我们的够识别出所有的扩展点但为了预留所有扩展点而付出的开发成本往往是不可接受的。因此我们没必要为一些未来不一定需要实现的需求提前“买单”也就是说不要进行过度设计。 推荐的做法是对于一些短期内可能进行的扩展需求改动对代码结构影响比较大的扩展或者实现成本不高的扩展在编写代码时我们可以事先进行可扩展性设计但对于一些不确定未来是否要支持的需求或者实现起来比较复杂的扩展我们可以等到有需求驱动时再通过重构的方式来满足扩展的需求。 除此之外我们还要认识到开闭原则并不是“免费”的。代码的扩展性往往与代码的可读性冲突。例如上文提供的 Alert类的例子为了更好地支持扩展性我们对代码进行了重构重构之后的代码比原始代码复杂很多理解难度也增加不少。因此在平时的开发中我们需要权衡代码的扩展性和可读性。在一些场景下代码的扩展性更重要我们就适当地“牺牲”一些代码的可读性;在一些场景下代码的可读性更重要我们就适当地“牺牲”一些代码的扩展性。 在上文提到的 Alert类的例子中如果告警规则不是很多也不复杂那么check()函数中的if分支就不会有很多对应的代码逻辑不会太复杂代码行数也不会太多因此使用最初的代码实现即可。相反如果告警规则多且复杂那么check()函数中的if分支就会有很多对应的代码逻辑就会变复杂代码行数也会增加check()函数的可维护性和扩展性就会变差此时重构代码就变得合理了。 6.总结 开闭原则是面向对象设计中最基础的设计原则之一。其核心思想是一个软件实体如类、模块和函数应该对扩展开放对修改关闭。这意味着当应用的需求改变时在不修改软件实体的源代码或二进制代码的前提下可以拓展模块的功能使其满足需求。换句话说强调的是用抽象构建框架用实现扩展细节从而提高软件系统的可复用性及可维护性。 开闭原则并不是要求所有代码都不能修改而是要求将变化的部分尽可能地封装和抽象出来。通过遵循这一原则开发者可以构建出更加稳定、灵活且易于维护的软件系统。