重学设计模式(二、设计原则)

1、软件设计的七大原则

    设计模式不是为每个人准备的,而是基于业务来选择设计模式,要明白一点,技术永远为业务服务,技术只是满足业务需要的一个工具。在软件开发中,应当尽量提高系统的可维护性和可复用性,增加软件的可扩展性和灵活性。

    在了解设计模式之前,我们先了解下软件设计的七大原则,它们分别是:单一职责原则、里氏替换原则、接口隔离原则、开闭原则、依赖倒置原则、迪米特法则和合成复用原则。

    实际上,这些原则的目的只有一个:降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性。

重学设计模式(二、设计原则)

1.1、单一职责原则-SRP

1.1.1、定义

    单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则,由罗伯特·C.马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中提出的。

英文定义:There should never be more than one reason for a class to change。

   一个类改变的原因不应该超过一个(这里的职责被定义为“变化的原因”)。

    单一职责原则:是指一个类(大到模块、小到方法)只负责完成一个职责通俗的说就是一个类、模块、方法不要承担过多的任务,要符合高内聚、低耦合的思想。

    所以我们设计一个类的时候不应该设计成大而全的类,要设计粒度小,功能单一的类。一个类承担的职责越多,它被复用的可能性更小,当一个职责变化时,可能会影响其他职责的运作。

    单一职责原则,就是为了处理“胖”接口的缺点,如果一个类有两个或者两个以上互不想干的功能,那我们就说它违背了单一职责原则(是不内聚的类),应该将其拆分功能单一、粒度更细的类。但是如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

1.1.2、原则解析

    其实在生活的方方面面都体现了我们设计中的很多原则。古人说的不错:狡兔死,走狗烹;飞鸟尽,良弓藏;敌国破,谋臣亡。善谋略、握兵权,功高震主,一个人功能太多,太锋芒毕露,就是比较危险的人物。

    同理,一个类承担过多的职责,也是比较危险的一种行为。一旦涉及到修改,尤其是较大的修改,就可能出现出人意料的bug。

    在JavaEE中的分层架构中,实际上就体现了单一职责原则。对用户操作,可以分解为存储数据的实体类User,完成数据库操作的UserDao,完成业务操作的业务类UserService以及显示用户信息的页面user.jsp。

  • 案例实践

     如果有这样一个抽象类Door。

    一个很纯粹的抽象类,符合单一职责。那现在我们需要为这个门增加一个报警功能alarm方法呢会怎么加/p>

    或者是

    你觉得哪种方式对呢/p>

    其实,上面两种方式都违反了ISP原则。

    首先第一种方式:把Door概念本身具有的行为方法和另外一个概念“报警器”的行为混在一起了。并不是所有的门都需要报警器这个功能,门这个抽象类(或接口)已经被不需要的功能污染了,那么以Door作为基类的子类会因“报警器”这个概念的改变而改变,并使其变“胖”。

    第二种方式:这个AlarmDoor在概念本质上到底是Door还是报警器般使用继承在本质上就是is-a的关系。既然open、close和alarm属于两个不同的概念,根据ISP原则就应该把alarm拆出来。

    注:这样写说明AlarmDoor本质上是Door,又具有报警概念。如果AlarmDoor本质上是报警器,那就应该反过来。

1.1.3、总结及建议

    单一职责原则是开发中最基础、最简单的一个原则,但又是一个最难把握的一个原则,在实际中有时很难界定职责的边界,这个变化原因的粒度也需要结合实际去运用,因为可能每个人理解的职责范围或者考虑方向不一样,得出的结论可能就不一样。就像做菜,说明上是写,盐方少许,那这个少许的标准是多少们和厨师区别大概就在这个度的把握上。

    再比如:在西方文化中,吃饭的工具有刀和叉,他们对刀和叉的分工就很明确,刀负责分离食物,叉负责运送食物。而我们吃饭的工具就是筷子,一双筷子既可以分离食物也可以运送食物。很大程度上,中西文化差异也会造成理解上的差异。

    所以呢,不必严格遵守原则,只是作为参考。实际开发中可以参考以下意见:

    1)一般对一个类或模块职责的分解可以从两个方面去考虑:一个是属性(结构)职责和一个是行为职责。

    2)类依赖过多的其他类,或者代码直接依赖关系过于复杂时,不符合高内聚低耦合的设计思想时,就可以考虑对代码进行拆分;

    3)类名和实际功能关系不大或者没有任何关联时,可以把无关的功能独立出去;

    4)类的代码函数过多影响可读性和代码维护时,可以对代码进行方法级别的拆分;

    总之一句话:接口尽量做到单一职责,类的设计尽量做到只有一个原因引起变化,因为我们的最终目的都是让我们的代码具有更好的复用性、可读性、可维护性、可扩展性……

1.2、开闭原则-OCP

1.2.1、定义

    1988年,Bertrand Meyer在他的著作《Object Oriented Software Construction》中提出了开闭原则(Open-Closed Principle, OCP)。

英文定义:Software entities(classes,modules,function,etc)should be open for extension but closed for modification。

    一个软件实体(如类、模块和函数等)应该对扩展开放,对修改关闭

    开闭原则中的“开”,是指对于组件功能(提供方)的扩展是开放的,是允许对其进行功能扩展的;开闭原则中的“闭”,是指对于原有代码的修改(使用方)是封闭的,即不应该修改原有的代码。

    抽象化是开闭原则的关键,强调的是用抽象构建框架,用实现扩展细节。简单的说:尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来引入新功能,达到开闭原则的要求。

    其他设计原则都可以看作是开闭原则实现的手段和方法。

1.2.2、原则解析

    其实看完上面那些定义就很抽象,因为我也没明白怎么扩展么关闭/span>

  • 什么是为扩展开放修改关闭/p>

    有一句话总结的不错:对扩展开放是为了应对需求的变化,对修改关闭是为了保证代码的稳定性。

    我们添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等,比如:说普遍一点就是通过继承和组合的方式扩展,说的详细一点,实际中我们常见的方式有多态、依赖注入、面向接口编程等等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。

    这里的关闭并不是说完全杜绝了修改,因为不管一个模块有多“封闭”,总会有一些变化,它不会或者无法完全关闭。这就要我们找到代码的稳定点和变化点,将变化控制在一定的范围,事先在这里预留好扩展点,以最小的代价来完成新功能的开发,新代码能够很灵活的插入到扩展点上。

    开闭原则作为最重要的原则之一,我们这里就描述的更详细一点。

  • 为什么抽象是开闭原则的关键/p>

    我们来看一个案例:

重学设计模式(二、设计原则)

    这符合开闭原则吗先这是一个客户端连接服务端的一个例子。客户端是固定的,稳定点是客户端,服务端是不固定的变化点,有一天我需要新增或者更换服务端,那我客户端Client的代码是不是需要较大的修改了。这不符合开闭原则。

    那如何保证整体的稳定性,如何去隔离变化点,使其趋于稳定/span>

重学设计模式(二、设计原则)

    开闭原则的关键就是抽象,我们抽象出变化的部分,将其隔离起来。即客户端类的对象将使用派生服务器类的对象。如果我们希望客户机对象使用不同的服务器类,那么可以创建AbstractServer类的新派生,那客户端的代码几乎不用修改,新增服务器也只需要通过派生新的类去扩展。

  • 案例实践

    假如我们做一个银行的系统,客户要求支持最基本的存款、取款、转账功能。看下面的例子:

    上面这个案例符合开闭原则吗/p>

    首先我们找到变化点,这里的变化点很明显是我们的业务类型,bankService方法就不符合开闭原则,因为它不能完成对银行新业务的支持。如果有一天银行因业务拓展,支持理财业务呢客户来办理银行业务BankBusiness中要新增方法,bankService是不是也要修改,来满足我的新业务呢。

    注:switch语句会在应用程序中的各种函数中反复出现,并不是所有的业务都不能用switch或if/else,只要是稳定点也是可以使用的。

    那如何来实现对业务类型的关闭呢/p>

来源:穆瑾轩

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年6月2日
下一篇 2022年6月2日

相关推荐