单一职责原则
单一职责原则(Single Responsibility Principle,SRP)简而言之就是对于一个类或者接口, 引起其改变的应该只能有一个原因. 比如要将负责属性和行为的类分开.
里氏替换原则
定义:所有引用基类的地方必须能透明地使用其子类的对象. 只要父类出现的地方, 子类就可以出现, 而且替换为子类不会产生任何错误或者一场. 但是反过来不一定可行.
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
前置条件
当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
class Father {
public Collection doSomething(HashMap map){
StdOut.println("父类被执行...");
return map.values();
}
}
class Son extends Father{
public Collection doSomething(Map map) {
StdOut.println("子类被执行");
return map.values();
}
}
子类方法和父类方法, 方法名相同, 返回类型相同, 但是参数不同, 所以不是Override, 而是Overload. 在这种情况下, 如果传入HashMap
, 子类的doSomething()
不会被执行. 这是正确的, 因为子类并没有重写父类方法, 而是重载父类方法, 所以如果父类的前置条件(形参) 范围宽于子类则不正确.
public static void main(String[] args){
Father f=new Father();
HashMap map=new HashMap();
f.doSomething(map);
}
父类被执行
public static void main(String[] args){
//父类出现的地方都可以被子类代替, 且不会改变逻辑
Son f=new Son();
HashMap map=new HashMap();
f.doSomething(map);
}
父类被执行
public static void main(String[] args){
//子类出现的地方, 父类不一定可以代替
Son f=new Son();
Map map=new HashMap();
f.doSomething(map);
}
子类被执行
class Father {
public Collection doSomething(Map map){
StdOut.println("父类被执行...");
return map.values();
}
}
class Son extends Father{
public Collection doSomething(HashMap map) {
StdOut.println("子类被执行");
return map.values();
}
}
public static void main(String[] args){
Father f=new Father();
HashMap map=new HashMap();
f.doSomething(map);
}
父类被执行
public static void main(String[] args){
//父类出现的地方都可以用子类代替
Son f=new Son();
HashMap map=new HashMap();
f.doSomething(map);
}
子类被执行
可以注意到, 此时子类方法被执行了, 而子类并没有重写父类的相应的方法, 而是重载了父类的方法.
public static void main(String[] args){
Son f=new Son();
Map map=new HashMap();
f.doSomething(map);
}
父类被执行
后置条件
当子类的方法实现或覆写父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格或相同。
注意点
- 在类中调用其他类时务必使用其父类或者接口, 如果不能使用父类或者接口, 则说明类的设计已经违背了LSP原则.
- 如果子类不能完整地实现父类的方法, 或者父类的一些方法在子类中完全和父类业务逻辑不同, 则建议不使用继承关系, 而是用依赖, 聚集, 组合等关系代替.
依赖倒置原则
Dependence Inversion Principle
- 高层模块不应该依赖低层模块
- 抽象不应该依赖细节
- 细节应依赖抽象
对于Java来说.
- 模块间的依赖通过抽象产生,实现类之间不发生直接依赖关系, 其依赖关系是通过接口或者抽象类产生的
- 接口或这抽象类不依赖于实现类
- 实现类依赖接口或抽象类
这样也有利于并行开发, 即使只完成了一部分工作, 仍可以进行单元测试.
这也符合现代开发的流程, 先写好单元测试类, 再写实现类.
规则
- 每个类尽量都有接口或抽象类, 或者抽象类和接口都具备
- 变量的表面类型尽量是接口或者是抽象类(如果使用类的clone方法, 就必须使用实现类)
- 任何类都不应该从具体类中派生(如果实在需要继承自具体类, 尽量不要超过两层的继承)
- 尽量不要覆写基类的方法
总体而言, 依赖倒置原则是六大原则中最难实现的, 也是实现开闭原则的重要途径. 总体而言, 把握住面向接口编程即可.
接口隔离原则
Interface Segregation Principles(ISP)
- 客户端不应该依赖它不需要的接口
- 类似的依赖关系应该建立在最小的接口上
接口应该细化, 不要使用过于臃肿的接口. 客户端需要什么接口就提供什么接口, 将不需要的接口剔除掉. 不要将太多的方法放在同一个接口之中.
但是接口设计也要有度, 不可过度设计, 这个度往往根据经验和常识判断.
迪米特法则
Law of Demeter(LOD) , 最少知识原则(Least Knowledge Principle))
一个对象应该对其它对象有最少的了解, 另一个解释是只与直接的朋友通信.
朋友类: 出现在成员变量, 方法的输入输出参数中的类称为成员朋友类, 而出现在方法体内部的类不属于朋友类
开闭原则
Open Close Principle(OCP)
一个软件实体如类, 模块和函数等应该对扩展开放, 对修改关闭.
实例
interface IBook{
public String getName();
public int getPrice();
public String getAuthor();
}
class NovelBook implements IBook{
private String name;
private int price;
private String author;
public NovelBook(String name, int price, String author){
this.name=name;
this.price=price;
this.author=author;
}
@Override
public String getName() {
return name;
}
@Override
public int getPrice() {
return price;
}
@Override
public String getAuthor() {
return author;
}
}
如果将来要搞打折, 一般可能会用以下两个方法来解决:
- 在接口中添加
getOffPrice()
方法. 但是这需要对每一个实现IBook
接口的实现类都添加该方法, 工作繁琐. 且接口应该是稳定且可靠的, 不应该经常发生变化. - 修改实现类, 直接在
getPrice()
中实现打折处理, 但是如果仍需要知道原价是多少, 就会出问题. - 通过扩展实现
添加一个子类
class OffNovelBook extends NovelBook{
public OffNovelBook(String name,int price, String author){
super(name,price,author);
}
@Override
public int getPrice(){
int selfPrice=super.getPrice();
int offPrice=selfPrice*90/100;
return offPrice;
}
}