欢迎光临散文网 会员登陆 & 注册

软件设计的基本原则:

2023-03-31 23:20 作者:bili_95355562225  | 我要投稿

在介绍设计模式之前,先了解一下软件设计的基本原则,这些原则无数热爱编程的前辈经过多年的摸索总结出了的,也是设计模式的理论基础

1. 单一职责原则(Single Responsibility Principle,SRP)

类的职责要单一。就一个类而言,应该仅有一个引起它变化的原因,就是一个类应该只负责一个职责

这个说起来简单,实际项目中,没那么简单,要考虑模块颗粒度大小的问题

太大耦合度会变高,太小类会变多变的繁琐。

核心思想:类的职责要单一

2. 开闭原则(Open Close Principle,OCP)

一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

核心思想:对扩展开放,对修改关闭。

如新增业务需求时通过新增类来实现,不修改之前的代码或很少改动。

3. 依赖倒置原则(Dependence Inversion Principle,DIP)

1、高层模块不应该依赖底层模块,二者都应该依赖抽象。

  2、抽象不应该依赖细节,细节应该依赖抽象。

  3、依赖倒置的中心思想是面向接口编程。

  4、依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多。

  5、使用接口或抽象类的目的是指定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类来完成。

依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。我们在项目中使用这个原则要遵循下面的规则:

  1、每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备

  2、变量的表面类型尽量是接口或者抽象类

  3、任何类都不应该从具体类派生

  4、尽量不要覆写基类的方法

  5、如果基类是一个抽象类,而这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会有一定的影响

  6、结合里氏替换原则使用

高层模块不应该依赖底层模块,两个都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。后面这句话换个方式说就是针对接口编程,不要对实现编程。

核心思想:代码应该依赖于抽象,而不是具体实现。

4. 里氏替换原则(Liskov Substitution Principle,LSP

子类型必须能够替换掉它们的父类型。

  里氏替换至少包含一下两个含义:

    1、里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。

    2、如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。

  1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法

  2、子类可以增加自己特有的方法

  3、当子类的方法重载父类的方法时,方法的形参要比父类方法的输入参数更宽松

  4、当子类的方法实现父类的抽象方法时,方法的返回值应比父类更严格

优点:

  可以大大减少程序的bug以及增强代码的可读性

核心思想:子类可以替换父类。子类不要重写父类的非抽象方法。

5. 接口隔离原则(Interface Segregation Principle,ISP)

接口尽量细化,同时接口中的方法尽量少。

1、客户端不应依赖它不需要的接口

2、类间的依赖关系应该建立在最小的接口上

  其实通俗来理解就是,不要在一个接口里面放很多的方法,这样会显得这个类很臃肿。接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加灵活轻便。或许有的人认为接口隔离原则和单一职责原则很像,但两个原则还是存在着明显的区别。单一职责原则是在业务逻辑上的划分,注重的是职责。接口隔离原则是基于接口设计考虑。例如一个接口的职责包含10个方法,这10个方法都放在同一接口中,并且提供给多个模块调用,但不同模块需要依赖的方法是不一样的,这时模块为了实现自己的功能就不得不实现一些对其没有意义的方法,这样的设计是不符合接口隔离原则的。接口隔离原则要求"尽量使用多个专门的接口"专门提供给不同的模块。

核心思想:尽量细化接口,接口中的方法尽量少,同时要注意适度原则,过大的话会增加耦合性,而过小的话会增加复杂性和开发成本。

6. 迪米特法则(Law of Demeter,LoD)/最少知识原则(Least Knowledge Principle,LKP)

一个类对自己依赖的类知道的越少越好;一个对象应该对其他对象有最少的了解。

每个类都应该尽量降低成员的访问权限,强调了类之间的松耦合。

也叫最少知识原则。迪米特法则的定义是只与你的直接朋友交谈,不与"陌生人"说话。如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该应用。其目的是降低类之间的耦合度,提高模块的相对独立性。

  迪米特法则中的朋友是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

优点:

  1、降低类之间的耦合度,提高模块的相对独立性。

  2、由于亲和度降低,从而提高了类的可复用率和系统的扩展性。

缺点:

  过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

使用迪米特法则需要注意:

  1、在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。

  2、在类的结构设计上,尽量降低类成员的访问权限。

  3、在类的设计上,优先考虑将一个类设置成不变类。

  4、在对其他类的引用上,将引用其他对象的次数降到最低。

  5、不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。

  6、谨慎使用序列化(Serializable)功能。

核心思想:一个软件实体应当尽可能少的与其他实体发生相互作用。

只暴露该暴露的方法

7. 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)

尽量使用合成/聚合,尽量不要使用类继承。

合成(组合)表示一种强的“拥有”关系,体现了严格的部分与整体的关系,他们的生命周期相同;

而聚合表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。

继承带来的问题:

  1. 子类继承了父类所有的行为,会让子类无意的暴露的不必要的接口,破坏封装性。

  2. 如果继承层级比较多,那么代码的复杂度、可阅读型就可想而知的难了。

  3. 另外一个点,就是非常不好做单元测试。

如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。反之,系统越不稳定,继承层次很深,继承关系复杂,我们就尽量使用组合来替代继承。

核心思想:尽量使用合成/聚合,尽量不要使用类继承。

总结:

单一职责原则告诉我们实现类要职责单一;

依赖倒置原则告诉我们要面向接口编程;

里氏替换原则告诉我们不要破坏继承体系;

接口隔离原则告诉我们在设计接口的时候要精简单一;

迪米特法则告诉我们要降低耦合。

而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。


软件设计的基本原则:的评论 (共 条)

分享到微博请遵守国家法律