代码编写原则

SOLID

程序设计领域,SOLID单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是指面向对象编程和面向对象设计的五个基本原则。

简称 英文全名 中文全名
SRP Single Responsibility Principle 单一功能原则
OCP Open Closed Principle 开闭原则
LSP Liskov Substitution Principle 里氏替换原则
ISP Interface Segregation Principle 接口隔离原则
DIP Dependency Inversion Principle 依赖反转原则

单一职责原则

单一职责原则规定一个接口或类应该有且只有一个引起它变化的原因,也就是一个接口或类只有一个职责,它就负责一件事情。

单一职责最难划分的就是职责,因为其没有一个量化的标准。

对于接口,我们在设计的时候一定要做到单一,但对于实现类就需要多方面考虑了。生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类,然后再使用聚合或组合的方式耦合在一起,人为制造了系统的复杂性。所以原则是死的,人是活的,灵活运用!不过建议接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。

开闭原则

开闭原则规定一个软件实体类、模块和函数应该对扩展开放,对修改关闭。其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。

开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。

里氏替换原则

里氏替换原则规定所有引用基类的地方 必须能透明地使用其子类的对象。通俗点说,就是父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。

里氏替换原则实现了开闭原则中的对扩展开放。实现开闭原则的关键步骤是抽象化,父类与子类之间的继承关系就是一种抽象化的体现。因此,里氏替换原则是实现抽象化的一种规范。违反里氏替换原则意味着违反了开闭原则,反之未必。里氏替换原则是使代码符合开闭原则的一个重要保证。

一般来说,只要有可能,就不要从具体类继承。在一个由继承关系形成的等级结构中,树叶节点都应当是具体类,树枝节点都应该是抽象类或者接口。
里氏替换原则是使代码符合开闭原则的一个重要的保证,其包含了四层含义:

  1. 子类必须完全实现父类的方法
    在类中调用其他类时务必使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
    如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸形”,则建议断开父子继承关系,采用依赖、聚集、组合灯关系替代继承。
  2. 子类可以有自己的个性
    子类当然可以有自己的方法和属性,这也就是里氏替换原则为什么可以正着用,但是不能反过来用的原因。
  3. 覆盖或实现父类的方法时输入参数可以被放大
    方法中输入的参数称为前置条件,方法返回值称为后置条件。子类中的前置条件必须与超类中被覆写方法的前置条件相同或者更加宽松。
class Father {
    public void display(Map map) {
        System.out.println("父类执行。。。");
    }
}

class Son extends Father {
    // 缩小输入参数类型
    public void display(HashMap map) {
        System.out.println("子类执行。。。");
    }
}

@Test
public void testA() {
    // 模拟执行父类
    Father f = new Father();
    HashMap map = new HashMap();
    f.display(map);//执行结果:父类执行。。。
}

@Test
public void testB() {
    // 模拟子类替换父类
    Son s = new Son();
    HashMap map = new HashMap();
    s.display(map);//执行结果:子类执行。。。
}
/*子类没有覆写父类的方法的前提下,子类方法被执行了,这会引起逻辑混乱。*/
  1. 覆写或实现父类的方法时输出结果可以被缩小
    子类中的后置条件必须与超类中被覆写方法的后置条件相同或者被缩小。

拓展:

“契约优先”的原则,也就是先定义出WSDL接口,制定好双方开发协议,然后再各自实现。里氏替换原则也要求制定一个契约,就是父类或接口,这种设计方法也叫做Design by Contract(契约设计),与里氏替换原则有着异曲同工之妙。

接口隔离原则

接口隔离原则规定客户不应该依赖它们用不到的方法,只给每个客户它所需要的接口。换句话说,就是不能强迫用户去依赖那些他们不使用的接口。

  1. 接口的设计原则:接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口,使用多个专门的接口比使用单一的总接口要好。
  2. 接口的继承原则:如果一个接口A继承另一个接口B,则接口A相当于继承了接口B的方法,那么继承了接口B后的接口A也应该遵循上述原则:不应该包含用户不使用的方法。反之,则说明接口A被B给污染了,应该重新设计它们的关系。
  3. 通过多重继承分离接口:多重继承可以有两个方式,第一种方式是同时实现两个接口,属于多重接口继承;第二种方式是实现一个接口,同时继承一个具体类,实际上也是一种多重继承。
  4. 通过委托分离接口:使用聚合和组合来将部分实现交给已知类实现,自己再实现部分接口。

依赖反转原则

依赖反转原则规定:

  1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
  2. 抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口。

Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字new产生一个对象。依赖倒置原则在Java语言中的表现就是:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
  • 接口或抽象类不依赖于实现类。
  • 实现类依赖接口或抽象类。

迪米特法则

迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP)规定一个对象应该对其他对象有最少的了解。

《设计模式之禅》将迪米特法则也归并为基本原则,部分资料也将其归并为SOLID,其中的L指里氏替换原则和迪米特法则两种。

其它好的编程原则

DRY(不要自我重复)

避免重复原则(DRY - Don’t repeat yourself):编程的最基本原则是避免重复。在程序代码中总会有很多结构体,如循环、函数、类等等,一旦你重复某个语句或概念,就会很容易形成一个抽象体。

Abstraction Principle(抽象原则)

抽象原则(Abstraction Principle):与DRY原则相关。要记住,程序代码中每一个重要的功能,只能出现在源代码的一个位置。

KISS(保持简单)

KISS (Keep it simple, stupid!):简单(并避免复杂性)应该始终是一个关键目标。简单的代码需要更少的时间来编写,有更少的bug,而且更容易修改。

Avoid Creating a YAGNI(不要开发你目前用不到的功能)

不要开发你目前用不到的功能(Avoid Creating a YAGNI - You aren’t going to need it):除非你真正需要用到它,否则不要轻易加上那些乱七八糟用不到的功能。

Do the simplest thing that could possibly work(用最简单的方法让程序跑起来)

在开发时有个非常好的问题你需要问问自己,“怎样才能最简单的让程序跑起来?”这能帮助我们在设计时让程序保持简单。

Don’t make me think(不要让我动脑子)

这实际上是Steve Krug 关于web界面操作的一本书的书名,但也适用于编程。主旨是,程序代码应该让人们花最小的努力就能读懂和理解。如果一段程序对于阅读者来说需要花费太多的努力才能理解,那它很可能需要进一步简化。

Write Code for the Maintainer(为维护者写程序)

任何值得你编写的程序在将来都是值得你去维护的,也许由你维护,也许由他人。在将来,当你不得不维护这些程序时,你对这些代码的记忆会基本上跟一个陌生人一样,所以,你最好还是当成一直在给别人写程序。一个有助于你记住这个原则的办法是“写程序时时刻记着,这个将来要维护你写的程序的人是一个有严重暴力倾向,并且知道你住在哪里的精神变态者”。

Principle of least astonishment(最少意外原则)

最少意外原则通常是使用在用户界面设计上,但这个原则同样适用于编写程序。程序代码应尽可能的不要让阅读者感到意外。也就是说应该遵循编码规范和常见习惯,按照公认的习惯方式进行组织和命名,不符常规的编程动作应该尽可能的避免。

Minimize Coupling(最小化耦合关系)

一个代码片段(代码块,函数,类等)应该最小化它对其它代码的依赖。这个目标通过尽可能少的使用共享变量来实现。“低耦合是一个计算机系统结构合理、设计优秀的标志,把它与高聚合特征联合起来,会对可读性和可维护性等重要目标的实现具有重要的意义。”

Maximize Cohesion(最大化内聚性)

具有相似功能的代码应该放在同一个代码组件里。

Hide Implementation Details(隐藏实现细节)

隐藏实现细节能最小化你在修改程序组件时产生的对那些使用这个组件的其它程序模块的影响。

Avoid Premature Optimization(避免过早优化)

只有当你的程序没有其它问题,只是比你预期的要慢时,你才能去考虑优化工作。只有当其它工作都做完后,你才能考虑优化问题,而且你只应该依据经验做法来优化。“对于小幅度的性能改进都不该考虑,要优化就应该是97%的性能提升:过早优化是一切罪恶的根源”—Donald Knuth。

Code Reuse is Good(代码复用)

这不是非常核心的原则,但它跟其它原则一样非常有价值。代码复用能提高程序的可靠性,节省你的开发时间。

Separation of Concerns(职责分离)

不同领域的功能应该由完全不同的代码模块来管理,尽量减少这样的模块之间的重叠。

Embrace Change(拥抱变化)

这是Kent Beck的一本书的副标题,它也是极限编程和敏捷开发方法的基本信条之一。很多的其它原则都基于此观念:面对变化,欢迎变化。事实上,一些经典的软件工程原则,例如最小化耦合,就是为了让程序更容易面对变化。不论你是否采用了极限编程方法,这个原则对你的程序开发都有重要意义。

参考资料:

求赞

猜你喜欢

转载自blog.csdn.net/fanxiaobin577328725/article/details/78691382