单一职责原则
有且只有一个原因会引起类的变化,即是说,一个类只会对一个职责内的事情负责。比如说权限类,那么他只负责权限内的所有事情,其他登录或者角色等相关的一些东西他不会干涉,也不要把权限的事情放到其他的类里面。一定要记住,手不要伸的太长,否则的话,相关的代码分散到系统的各个地方,维护的代码实在太多,随着你系统的扩张,维护的代码也会呈几何增长。
想象下中医药房,每个抽屉只放了一种药材,抓方子的时候很方便,直接抓起来称重量就可以了,如果多种药材放在了一起,就十分的麻烦了。
接口隔离原则
- 接口的定义:
- 1.实例接口:其实就是Java中的类,而你的具体事例必须遵守类,也就是调用方法,属性这些东西。你A类总不能调用B类的方法和属性变量把?
- 2.类接口:也就是Java中interface关键字定义的接口。
- 隔离的定义:
- 1.客户端不应该依赖它不需要的接口
- 2.类间的关系应该是建立在最下弄得接口上。
两句话汇总起来就是:对于类接口来说客户端应该只需要它需要的接口,这个接口只做了客户端期望的事情,也就是说接口必须保持小而纯净,避免臃肿。而对于实际接口来说,一个接口中的方法必须尽量的少。 - 这边和单一职责的区别就是单一职责原则要让类和接口的职责单一,注重的是职责,是业务上的划分,而接口隔离指的是接口职责来说的,也就是你的接口职责必须单一(接口只做自己该做的事情,需要小而纯净)。
举个例子,你去吃饭,看到有收银员,厨师,服务员,洗碗阿姨等,类比接口隔离,这些人是一个一个的接口,而他们只做自己负责的事情,就是保持了接口的纯净。收银员只收钱而不会去洗碗。他们各司其职,而你需要干什么事情了就只会去找对应的人。
- 那么我们如何保证接口的纯净呢?
- 1.接口要尽量的小
这是接口隔离原则的核心定义,不要出现臃肿的接口,但是小也是有限度的,细化接口可以帮助我们将代码逻辑隔离,但是同时也带来了接口泛滥导致难以维护的代价。 - 2.接口要高内聚:
高内聚就是要提高类,接口,模块的处理能力,也就是尽量减少对外公开方法,当你对外方法越少,变更的风险就越少。 - 3.定制服务
定制服务就是单独为一个个体提供优良的服务,类比上面的例子就是收银员可能对你是收银,退款服务,而对他老板来说就是报账,核账服务了。因为不同的个体的需求是不同的,就算目前一样,但是对于以后来说你能保证永远一样么? - 4.接口的设计是有限度的
- 1.接口要尽量的小
里氏替换原则
里氏替换原则原则建立在继承之上,牢牢记住A extends B,B可以是实现类或者是抽象类。
目的是为了让子类更好的复用自父类继承而来的方法。通俗说在任何父类的地方都可以被替换成子类,并且程序不会有报错和出错。有四条规则规定了继承之间的限制:
- 1.之类必须完全实现父类的方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
- 2.子类可以有自己的个性
- 3.覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比- 父类方法的输入参数更宽松。(即只能重载不能重写)
- 4.覆盖或者实现父类的方法时,方法的后置条件(即方法的返回值)要比父类更严格
解释这4个限制:
- 首先,调用实例方法必须是可以被new的,因此A中必然是实现了B的抽象方法。子类不能覆写父类已实现的方法。父类中已实现的方法其实是一种已定好的规范和契约,如果我们随意的修改了它,那么可能会带来意想不到的错误。
- 第二,子类当然可以有自己独有的方法
- 第三,由于在父类的地方可以被无痕的替换为子类,因此子类最好不要去覆盖父类中的方法,如果真的要覆盖的话。那么,子类的输入参数的范围就必须大于父类输入参数的范围。当真正调用的时候,你传入的是HashMap的实例,就重载调用父类的method方法,而不会去调用子类的method方法。这样程序原本的规则就没有任何的变化。举个例子。
- 第四,子类的覆盖方法的返回值必须是父类方法的子类。
//对于第三个限制的例子:
public class Test1 {
public static class Father{
public Collection doSomething(HashMap map){
System.out.println("父类被执行了。。。");
return map.values();
}
}
public static class Son extends Father{
//方法参数类型
public Collection doSomething(Map map){
System.out.println("子类被执行了。。。");
return map.values();
}
}
public static void main(String[] args) {
Father s = new Father();
HashMap map = new HashMap();
s.doSomething(map);
Son s1 = new Son();
HashMap map1 = new HashMap();
s1.doSomething(map1);
}
}
/*
* 结果为:
* 父类被执行了。。。
* 父类被执行了。。。
* */
依赖倒置原则
定义:高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象
- 什么是高层模块,底层模块?
底层原子性的操作就是底层模块,以底层模块为基础构建新的模块的就是高层模块 - 抽象和细节?
抽象就是java中的抽象类和接口,细节就是具体的实现类。
public class Java {
public void say(){
System.out.println("程序员用java语言在编程!");
}
}
public class Programmer {
//高层逻辑依赖了底层的原子逻辑,此时两个类耦合了,没有任何扩展而言
public void programming(Java java){
java.say();
}
}
此时程序员类只能说Java语言,不能说其他语言,但是通过学习是可以掌握其他语言技能的。所以和实际的设计不符合
public interface Language {
public void say();
}
public class Java implements Language{
@Override
public void say(){
System.out.println("程序员用java语言在编程!");
}
}
public class Python implements Language{
@Override
public void say() {
System.out.println("程序员用Python语言在编程!");
}
}
public class Programmer {
public void programming(Language language){
language.say();
}
}
测试:
public class Test {
public static void main(String[] args) {
Programmer programmer = new Programmer();
programmer.programming(new Java());
programmer.programming(new Python());
}
}
输出:
程序员用java语言在编程!
程序员用Python语言在编程
依赖倒置原则的核心就是面向接口(language),这个例子中,高层模块(Programmer)不应该依赖底层模块(Java,Python),同时,细节应该依赖与抽象。
依赖倒置拥有良好的扩展和健壮性,以后不管是什么语言只要继承了Language,程序员既可以使用这么语言。并且程序员不需要做任何的改变。
依赖其实就是类中的实现的继承关系,那么什么是倒置呢?
现实中,我们都是依托于客观具体的事务存在的,摸得着看得见的,比如电脑,汽车等等,在上面的例子就是具体的Java,Python类,而依赖倒置就是扭转了这种思想,不要依赖于具体实例,而是拥抱抽象,也就是接口,上面的例子就是Language接口。这就是倒置的由来。
迪米特法则
定义:迪米特法则(Law of Demeter)又叫做最少知识原则(Least Knowledge Principle 简写为LKP),就是说一个对象应当对其他对象有尽可能少的了解(不和陌生人说话)。
- ①只与你直接的"朋友"们通信
- ②不要和"陌生人"说话
- ③每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
关于"朋友"的定义:
首先来解释下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,当前对象本身(this)、成员变量、以参数形式传入当前对象方法中的对象、方法返回值中的类,当前对象创建的对象为直接的朋友。
简单的说就是不要过多的去调用在方法中定义的类的方法,反过来,类不要过多的暴露方法给外部,暴露的越多,类之间的耦合度就越高,当类改动的越大,风险就越大。过多的方法之间的调用,最后任何一方的改动就会影响到另外一方,就越复杂。
//明星
public class Star {
private Broker broker;
public Star(Broker broker){
this.broker = broker;
}
public Broker getBroker(){
return broker;
}
public void setBroker(Broker broker){
this.broker = broker;
}
public void play(String activity){
Merchant merchant = new Merchant(activity);
broker.reservation(merchant);
System.out.println("明星去演出了");
}
}
//经纪人
public class Broker {
public void reservation(Merchant merchant){
System.out.println("经纪人预约");
merchant.business();
}
}
//商家
public class Merchant {
private String activity;
public Merchant(String activity){
this.activity = activity;
}
public void business(){
System.out.println("和商家洽谈"+activity+"活动");
}
}
public class Test {
public static void main(String[] args) {
Star star = new Star(new Broker());
star.play("慈善");
}
}
结果:
经纪人预约
和商家洽谈慈善活动
明星去演出了
上面的代码没有任何问题可以输出,但是仔细想想,现实中,有那个明星演出预约是自己接触商家?直接都是让经纪人一个人去谈,然后谈妥了。钱到位了,那么明星才真正的开始演出的。再说,明星那么忙,怎么可能每个演出都去和商家接触一下,那不是要忙死了?因此,这里明显的明星和商家不恰当的耦合了,他不应该接触商家,代码改为如下。
//明星
public class Star {
private Broker broker;
public Star(Broker broker){
this.broker = broker;
}
public Broker getBroker(){
return broker;
}
public void setBroker(Broker broker){
this.broker = broker;
}
public void play(String activity){
//直接和经纪人说去洽谈演出,和谁接触我不管
broker.reservation();
System.out.println("明星去演出了");
}
}
//经纪人
public class Broker {
private Merchant merchant;
public Merchant getMerchant(){
return merchant;
}
public void setMerchant(Merchant merchant){
this.merchant = merchant;
}
public void reservation(){
System.out.println("经纪人预约");
merchant.business();
}
}
//商家
public class Merchant {
private String activity;
public Merchant(String activity){
this.activity = activity;
}
public void business(){
System.out.println("和商家洽谈"+activity+"活动");
}
}
public class Test {
public static void main(String[] args) {
Merchant merchant = new Merchant("慈善");
Broker broker = new Broker();
broker.setMerchant(merchant);
Star star = new Star(broker);
star.play("慈善");
}
}
结果:
经纪人预约
和商家洽谈慈善活动
明星去演出了
这样一来,明星不和商家接触,只和关系亲密的经纪人接触就可以了,好处就是当活动有任何的变更时,影响范围只会波动到经纪人,并不会对明星有任何的影响,明星和商家直接就没有耦合关系了。
迪米特法则的核心就是降低系统之间类的耦合度。只有弱耦合了,类的复用性才能提高,相对的,会有中间类的出现,如上面的例子的经纪人就是一个中间人的角色,导致了请求在系统中的多次跳转,一定程度上提高了系统的复杂性,但是因为三者之间职责的清晰,在一定程度上来说是可以接受的。
开闭原则
开闭原则由开和闭两点组成。
- 开:对拓展是开放。
- 闭:对修改是关闭。
一个软件实体的任何变更,不应该是建立在修改的基础之上,我们应该是对软件实体进行拓展来达到变更需求的目的的。
其中的变化可以是需求、类及方法,上线的产品必然是经过大量的测试验证的,是一种趋于稳定的状态,如果把原来的东西修修改改,必然是要做大量回归测试,不仅耗时耗力,而且修改的多了,你也不能保证每次修改都可以兼容上次修改。
开闭原则是一个抽象的概念,上述五大原则和设计模式就是对于开闭原则进行的实际落实的成果,而其中实现开闭原则的最核心的思想就是抽象,把一切可能的变化都抽象出来。那么不管怎么变都可以对变化进行扩展。所以我们需要遵守依赖倒置原则,必须依赖接口而不是细节。
最重要的就是我们不应该修改,而是创建。