目录
一、单一职责原则(Single Responsibility Principle, 简称SRP)
二、开放封闭原则(Open-Close Principle, OCP)
三、里氏替换原则(Liskov Substitution Principle, LSP)
四、依赖倒置原则(Dependence Inversion Principle, DIP)
五、接口隔离原则(Interface Segregation Principle, ISP)
六、迪米特法则(Law of Demeter LoD),又叫做最少知识原则(Least Knowledge Principle, LKP)
一、单一职责原则(Single Responsibility Principle, 简称SRP)
定义:对于一个类,有且仅有一个引起他变化的原因。
通俗讲就是我们不要让一个类承担过多的职责。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会导致类的行为功能发生变化。
SRP遵循的OO设计原则就是:封装变化的部分。找出应用中那些会变化的地方,把他们独立出来并封装,将其和不变的代码隔离。(将相同的变化封装到一个接口或者抽象类中,将不同的变化封装到不同的接口和抽象类中)
二、开放封闭原则(Open-Close Principle, OCP)
定义:项目中按照一定的逻辑规则划分的模块、类、抽象类以及方法。
开闭原则告诉我们的是,面对新的需求,尽量通过扩展软件的实体来实现变化,而不是通过修改已有的代码来完成变化。 可以说开闭原则是对软件实体的未来变化的一种约束性的原则。
好处:
1)提高可复用性,因为我们是对修改关闭,也就是说可以工作的逻辑代码高度集中并且基本不会变的,这部分代码就可以拿来复用的,这也正是我们封装的目标之一。
2)提高可维护性,遵循开闭原则的一个结果就是每次的修改都不会对之前的代码造成任何影响,那么维护人员也就无需关心之前的代码会出问题,只需要把精力放到本次的扩展修改的代码上。
三、里氏替换原则(Liskov Substitution Principle, LSP)
简单的理解就是:凡是父类出现的地方子类就可以出现,而且替换成子类也不会产生任何错误和异常,使用者可能根本不需要关心使用的是父类还是子类。但是反过来不行,有子类出现的地方,父类不一定合适。(这不是屁话吗)
LSP原则的一个特点:子类必须完全实现父类的方法。如果子类不能完全实现父类的方法,那么最好不再继续使用继承关系,使用依赖、组合关系来代替继承。
例如 玩具枪子类 不能实现 父类枪 的射击功能,那么玩具枪这个子类就另外实现一个接口。
四、依赖倒置原则(Dependence Inversion Principle, DIP)
定义:高层模块不应该依赖低层模块,两个都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
高层模块和低层模块的理解:每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。
抽象和细节:在Java中,抽象就是指接口或者抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或者继承抽象类而产生的就是细节。
高层模块就是调用端,低层模块就是实现端。
public class DuckClient {
public static void main(String[] args) {
// Duck是接口, MallardDuck是类
Duck mallardDuck = new MallardDuck();
// QuackBehavior是接口, FlyBehavior是接口
QuackBehavior quack = new Quack();
FlyBehavior flyWithWings = new FlyWithWings();
mallardDuck.setFlyBehavior(flyWithWings);
mallardDuck.setQuackBehavior(quack);
mallardDuck.performQuack();
mallardDuck.performFly();
}
}
从这个例子学到一个原则:多用组合,少用继承。组合尽量持有对抽象的依赖,这样才能够解耦具体实现类。
代码中有这样一段实例:
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(20000); // 单位为ms
factory.setConnectTimeout(5000); // 单位为ms
RestTemplate restTemplate = new RestTemplate(factory);
// 设置转换器
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
converters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(converters);
return restTemplate;
}
如何使用依赖倒置原则,遵循以下几个原则:
- 每个类尽量有接口或者抽象类,接口和抽象类都属于抽象,有了抽象才能依赖倒置。
- 变量的表面类型尽量是接口或者抽象类
- 尽量不要覆写基类的方法, 子类尽量不要覆写抽象类已经实现的方法。
- 开发阶段尽量不要从具体类派生新类,只是在开发阶段,因为在维护阶段是要不断扩展修改的。
五、接口隔离原则(Interface Segregation Principle, ISP)
定义:一个类对另一个类的依赖应该建立在最小的接口上,客户端不应该依赖它不需要的接口
这里的接口有两个含义:
实例接口:可以new的实现类都属于实例接口
类接口:java中就是interface声明的接口或者是抽象类。
主要说的就是:接口做到细化,不要所有的功能都放到一个接口中。接口隔离原则可以给系统带来灵活性的优点,但是接口不能无限制的细分,细化拆分必须在满足单一职责的条件下进行,这个细化的颗粒度的把握往往要凭经验常识或者根据业务逻辑来判断。(我要是有这个常识,我还看你!)
我理解的这个和单一职责原则很像。举的例子实现方式也是很像的,就是把一些行为单独放在一个接口中。然胡不同的实现类去继承这些接口。
问题:但是有个不好的点,我要根据实际的对象去选择实现哪些个接口,还要在实现类中分别去实现这些接口,我觉得这样也挺麻烦的
解答:针对我上面这个问题,单一设计原则中鸭子的示例也提出了这个问题,然后在 依赖倒置原则中给出了解答。
但是这个示例和依赖倒置原则中的有点不一样:
1、 接口隔离原则代码:
说明:本来human就是包括所有的方法,如果所有的方法都写在Human接口中,所有实现Human接口的类都得实现这些方法,即使是假人、机器人等(他们实现了一些自己不需要的方法),所以改成下面的样子,这样假人、机器人实现类就可以只实现相应的接口了
human本身的方法写少了,然后通过继承的方式增加自己的行为。
public interface Intelligence {
public void talk();
public void think();
}
public interface Work {
public void work();
}
public interface Display {
public void display();
}
public interface Action {
public void walk();
}
public interface Human extends Intelligence, Work, Display, Action {
public void sleep();
public void eat();
}
2、依赖倒置原则鸭子的示例
利用抽象类组合了行为的接口,也是原本有很多行为的接口,本身的接口变少了,然后同时组合的方式增加自己的行为。
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void setFlyBehavior(FlyBehavior fb) {
this.flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
this.quackBehavior = qb;
}
public void swim() {
System.out.println("All ducks float, event decoys!");
}
}
六、迪米特法则(Law of Demeter LoD),又叫做最少知识原则(Least Knowledge Principle, LKP)
迪米特法则要求我们尽量减少类之间的交流。如果两个对象之间不必直接通信,那么这两个对象就不应当直接发生任何相互作用。可以通过一个中间类进行联系,即与直接的朋友进行交流。
总结一句话就是:知道的越少越好,知道的越多对你越没有好处。
怎么才能很好的遵循迪米特法则表达的“与直接的朋友交流”呢,在一个对象的方法内,只调用属于以下范围的方法:
- 该对象本身
- 被当做方法参数传进来的对象
- 该方法内所创建或实例化的任何对象
- 该对象本身的任何成员变量对象
例如这样的例子:getA().getB()getC().getD()
这种链式调用的的写法,应该尽量避免。类与类的关系是建立在类之间的,不是方法间,一个方法尽量不要引入一个类中不存在的对象。(我认为这个很难做到)
迪米特法则的优点是可降低系统的耦合度,使类与类之间保持松耦合关系,松耦合的关系同时也提高了复用性。但也有缺点,有可能造成大量中转类或跳转次数的增加,增加系统的复杂性,跳转次数越多系统就越复杂从而越难以维护。