什么样的代码可以称之为好代码?又如何评价代码比较差?每个人也许都有自己的标准,但是在软件设计领域中,有一套通过经验总结出来的,可以有效解决问题的指导思想和方法论,遵从这些原则,可以事半功倍,反之则有可能带来一些麻烦。
SOLID
SOLID
是五种设计原则首字母的缩写,有趣的是这个单词本身就有稳定
的含义,遵从这些设计原则,有助于我们设计出更灵活,易于拓展和维护的软件系统。
-
Single Responsibility Principle(SRP):单一职责原则
-
Open Close Principle(OCP):开闭原则原则
-
Liskov Substitution Principle(LSP):里氏替换原则
-
Interface Segregation Principle(ISP):接口隔离原则
-
Dependency Inversion Principle(DIP):依赖倒置原则
SOLID原则之间并不是相互孤立的,彼此间存在着一定关联,一个原则可以是另一个原则的加强或基础;违反其中的某一个原则,可能同时违反了其他原则。其中,开闭原则和里氏代换原则是设计目标;单一职责 原则、接口分隔原则和依赖倒置原则是设计方法。
单一职责原则(SRP)
尽量让每个类只负责软件中的一个功能,并将该功能完全封装在该类中
这条原则的目的是为了减少类的复杂度。其实只有当程序规模不断扩大,变更不断增加后,违反单一职责的问题才会逐步显现出来,到了某个时间,类会变得过于庞大,以至于无法记清其中的细节,查找代码也会变得非常缓慢,必须浏览整个类或者整个程序才能找到需要的东西,需要看的东西实在太多,会让人感觉到对代码失去控制。另外的一个问题是,如果一个类负责的职责过多,那么当需求发生变更时,极有可能需要改动你并不想改动的部分,导致影响范围不可控。
单一职责可以称之为最简单的设计原则,简单到稍有经验的程序猿即使从未了解过设计原则和设计模式的相关知识,也从未听说过单一职责的原则,在进行代码设计时也会不自觉的遵守这个原则,因为在软件开发迭代的过程中,谁也不希望因为修改了一个功能,而导致其他功能出现故障。虽然单一职责是如此的简单,但是它又是最容易被违背的设计原则之一,即使经验很丰富的开发人员写出的代码也会有违背这一原则的存在。
开闭原则原则(OCP)
软件功能应该对拓展开放,对修改关闭。
对拓展开发意味着当有新的需求或者变更发生时,可以进行拓展以适应新的情况。对修改关闭意味着代码一旦设计开发完成,就可以独立的进行工作,而不需要对代码内部进行修改。
可拓展性是衡量软件质量的重要指标,在软件的生命周期中,需求的新增与变更是不可避免的,如果有某种方法,既可以拓展软件功能而不需要修改原有的代码,那这在软件开发和交付时是梦寐以求的。
很多设计模式都是以达到OCP为目标的,例如装饰者模式,可以在不改变被装饰对象的情况下,通过包装一个类来拓展功能;策略模式通过制定策略接口,让不同的策略实现成为可能;适配器模式在不改变原有类的基础上,适配新的功能;观察者模式可以灵活的添加/删除观察者来拓展系统功能。
里氏替换原则(LSP)
程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换,即子类应该可以替换任何基类能够出现的地方,并且经过替换后,代码还能正常工作。
违反LSP原则最为频繁的一个地方就是子类方法覆盖父类方法,并且更改了其中的含义,而这在里氏替换时会发生意想不到的问题,发生这种问题往往是错误的继承关系导致的。经典的设计正方形-矩形问题就反映了这个问题。
按照常规理解,正方形是一个矩形,但是如果将正方形设计成矩形的子类,就会出现一些意料之外的问题。
定义一个矩形类
public class Rectangle {
int length;
int width;
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
public int getArea() {
return length * width;
}
}
子类正方形继承父类矩形,由于正方形的长宽是相同的,重写两个 setter 方法保证长宽一致:
public class Square extends Rectangle {
public void setLength(int length) {
this.length = length;
this.width = length;
}
public void setWidth(int width) {
this.width = width;
this.length = width;
}
}
这时我们在对矩形进行测试式极有可能出现问题,我们生产一个正方形,并且指派一个矩形质检员来负责检查这个正方形是否合格。质检员把长设为 4,宽设为 3,检查最后得到的面积是否为 12
public static boolean test(Rectangle rectangle) {
rectangle.setLength(4);
rectangle.setWidth(3);
return rectangle.getArea() == 4 * 3; // Oops... 9 != 12
}
问题出现了,矩形质检员并不知道这是一个正方形,他使用传统的质检矩形的方法来检查竟然发现这个正方形连矩形也不是,难道正方形不是矩形?
其实对这个问题进行分析可以发现,矩形这个类同时包含了正方形和长方形两个语义,对于矩形而言,长宽是特征属性,正是长款决定了矩形是正方形还是长方形。而对于求面积这个功能并不是矩形所独有的功能,应该是所有封闭图形都有的功能。
那么如何打破正方形不是矩形的悖论呢?抽象是解决问题的一个方法。
长方形类和正方形类作为子类继承抽象矩形类。对于长方形,定义两个 final 修饰的字段 length & width 表示长宽,这是为了保证特征属性的不可变。对于正方形,定义一个 final 修饰的字段 sideLength 表示边长,并且构造函数只传入一个参数以保证其作为正方形的正确性。
分别重写父类的两个抽象方法和接口的一个抽象方法:
interface ClosedFigure {
int getArea();
}
abstract class AbstractRectangle implements ClosedFigure {
abstract int getLength();
abstract int getWidth();
}
重写抽象矩形类作为父类,并实现 ClosedFigure 接口,这个接口的功能是求面积。求面积这个功能并不是矩阵本身具有的,而是它作为一个封闭图形能够实现的功能,所以需要用接口来实现。而抽象矩形定义两个方法,分别用来获取长和宽。
长方形类和正方形类作为子类继承抽象矩形类。对于长方形,定义两个 final 修饰的字段 length & width 表示长宽,这是为了保证特征属性的不可变。对于正方形,定义一个 final 修饰的字段 sideLength 表示边长,并且构造函数只传入一个参数以保证其作为正方形的正确性。
分别重写父类的两个抽象方法和接口的一个抽象方法:
class Rectangle extends AbstractRectangle implements ClosedFigure {
private final int length;
private final int width;
Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
@Override
int getLength() {
return length;
}
@Override
int getWidth() {
return width;
}
@Override
public int getArea() {
return length * width;
}
}
class Square extends AbstractRectangle implements ClosedFigure {
private final int sideLength;
Square(int sideLength) {
this.sideLength = sideLength;
}
@Override
int getLength() {
return sideLength;
}
@Override
int getWidth() {
return sideLength;
}
@Override
public int getArea() {
return sideLength * sideLength;
}
// 追加功能,获取边长
int getSideLength() {
return sideLength;
}
}
接口隔离原则(ISP)
接口隔离原则要求一个类对另一个类的依赖性应当建立在最小的接口上,一个类只能有一个查询方法的接口。这样做可以避免角色冲突、污染代码等问题。同时,提供调用者需要的方法、屏蔽不需要的方法也是接口隔离的原则之一。
遵循ISP的接口设计在依赖关系和语义表达上会更加精准,最大的好处是可以将外部依赖减少到最小,只需要依赖自己需要的东西而不需要关注自己不用的东西,这样可以显著的降低模块之间的耦合度。
依赖倒置原则(DIP)
模块之间交互应该依赖抽象,而不是实现。高层模块不应该依赖底层模块,应该依赖于抽象。抽象不应该依赖细节,细节应该依赖于抽象。
一个类如果需要依赖于其他类协助完成功能,不应该直接依赖于特定类的实现,而是应该依赖于抽象,这也就是经常说的“面向接口编程”,面向接口编程正是DIP的一个体现。
遵循DIP会大大提高系统的灵活性。如果类只关心它们用于支持的特定 契约,而不是特定类型的组件,就可以快速而轻松地修改这些低级服务 的功能,同时最大限度地降低对系统其余部分的影响。
在Java应用中使用Logger框架有很多选择,比如log4j、logback、 common logging等。每个Logger的API和用法都稍有不同,如果要切换 不同的Logger框架,会非常复杂,可能要改动很多地方。产生这些问题 的原因是我们直接依赖了Logger框架,应用和Logger框架强耦合在一起了,而如果我们的业务代码中依赖SL4J而不是具体的实现,那么在需要切换日志框架时,只需要将相应的日志框架引入即可。
总结
设计原则能够知道我们编写出更好的代码,但是不要教条,过度设计要不得,软件是一种平衡的艺术,我们不是为了设计原则而编码,他只是背后的指导思想,我们的目的是构建可用的软件系统,并尽量减少系统的复杂度,在不能满足所有的设计原则时,要懂得适当的取舍。
标签:正方形,原则,width,int,面向对象,length,设计,矩形 From: https://www.cnblogs.com/wrxiang/p/17473121.html