一、类与类之间的关系:
1.B类继承A类
2.关联关系
A 在 字段位置。
箭头指向问题:被关联的是被指向的。(B关联A A被关联 箭头指向A)
3.依赖关系
A作为B的形参或者局部变量,则成为B 依赖于 A。
4.关联关系细分为组合和聚合
关系强:组合(如鸟和翅膀) 实心菱形
关系弱:聚合(如大雁和雁群)空心菱形
二、七大原则
1.单一职责原则
每个方法、每个类、每个框架都只负责一件事情。
比如:
Math.round().只负责完成四舍五入的功能,其他的不管。
Reader类,只负责读取文本文件。
SpringMVC 只负责简化MVC的开发(框架)。
package sevenprinciple;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
public class AppTest {
public static void main(String[] args) throws Exception {
//统计一个文本文件中,有多少个字符。
//使用字符流 不使用字节流
//Reader默认查询的码表是与操作系统一致的码表,我们的操作系统是中文的
//所以Reader就会使用GBK码表
//GBK码表一个汉字占2个字节,且汉字的两个字节都是以1开头的。
//读取到记事本中的数字--->gbk--->北--->unique--->
//字符流会查码表,字节流不会查码表。操作文本文件用字符流。
Reader in = new FileReader("C:\\Users\\李昊\\Desktop\\1.txt");
int n;
int count = 0;
while((n=in.read())!=-1){
count++;
}
System.out.println(count);
in.close();
}
}
问题分析:
在此编程过程中,一个方法中,不仅进行了整行的读取,同时还进行了单词的分割,这不符合单一职责原则。
以上代码违反了单一职责原则,缺点是:
1.代码的重用性不高,如果有其他需求,必须需要统计一个文件中的句子数量,则必须把文件加载到字符串中的代码,再写一遍。
2.可读性低,别人一看这个方法,首先会被具体的算法搞晕,看代码的人根本看不出这个代码要干什么。
修改过程:
抽取出方法,仅进行文件的读取工作。
进一步处理:
现在的代码就符合单一职责了。优点如下:
1.代码重用性提高了。
2.代码可读性提高了,此时的代码,就像一个大纲一样的。
3.降低耦合性 每个方法具有明确分工。明确设计原则的背后是分。
2.开闭原则
a.对扩展开放
b.对修改关闭
比如: 有一个刮胡刀,刮胡刀的作用就是刮胡子,现在想让刮胡刀具备吹风机能力。
违反开闭原则的做法就是,把吹风功能加上了,可能不能刮胡子了。
符合开闭原则的做法就是:把吹风功能加上了,且没有影响以前的刮胡子功能。
补充:
如果,一个类从头到尾都是你自己创造编写的,那么你可以随时随地修改源代码,因为作者就是你。
如果一个类不是你写的,而是别人写的,那就不能修改别人的代码了,而要符合开闭原则。
举例:
变化来了:
现在所有汽车需要打8折!!
违反开闭原则的做法就是,直接打开Car的源代码,在getPrice上修改
符合开闭原则的做法是,始终保持car的源代码不会被修改。我们可以这样做:创建一个Car的子类重写Car的getPrice方法。
3.接口隔离原则
使用多个专门的接口比使用一个单一的接口要好。
反例:
正例:
4.依赖倒置原则
概念:上层不能依赖于下层,他们都应该依赖于抽象。
什么是上层?什么是下层?
调用别的方法的,就是上层,被其他方法调用的,就是下层。
变化来了,客户端不仅仅需要喂狗,还需要喂猫!
客户端给自己定义一个猫类。
如果修改,违反了开闭原则:
违反依赖倒置的图解
此时,这种代码违反了依赖倒置,因为,每当下层变动时,上层都要跟着一起变动。(新增一个tiger类 person 就要增加一个喂养tiger的方法。)
我们希望的是,当下层新增一个动物时,上层应该“不知道”,上层代码应该不用改动。
修改代码:让新增加的类 实现animal接口。
符合依赖倒置原则的图解:
下层代码变动时,上层代码不发生变动。箭头本来是往下,后来箭头倒过来了。将依赖进行了倒置。 接口是一个抽象层,上层就依赖于抽象层了,下层也依赖于抽象了(实现接口。)这样做的好处是,只要实现类统统实现接口,就不需要修改上层代码了。这样就把上下层“分”开了。
5.迪米特法则(最少知道原则)
一个类,对于其他类,要知道的越少越好。
只和朋友通信。
反例
反例说明的问题:此时这个Person 对于Computer的细节就知道的太多了。
对于Person而言,只需要知道关机按钮在哪就行,不需要知道如何保存数据,如何关闭进程。如何断电等等这些细节。这样的话,代码的复杂度就提升了。万一用户使用不当,就有可能造成更大的损失。
修改代码:
在Computer内部设定shutDown方法,而不在Person类中暴露,直接调用。
什么是朋友?
a.类中的字段 b.方法的参数 c.方法的返回值 d. 方法中实例化 new 出来的对象
是朋友什么方法都能调用。
举例:不是朋友的例子:
图中的bar就不是朋友,因为并不是方法中实例化new的对象,而是通过f 调用的对象。
6.里式替换原则
任何能使用父类对象的地方,都应该能透明地替换为子类对象。也就是说子类对象可以随时随地替换父类对象,且替换完以后,语法不会报错,业务逻辑也不会出现问题!
方法重写:在子类和父类中,出现了返回类型相同、方法名相同、方法参数相同的方法时,构成方法重写。
方法重写的两个限制:
1.子类重写父类的方法时,子类方法的访问修饰符不能比父类的更严格。
2.子类重写父类方法时,子类方法不能抛出比父类更多的异常。
为什么要有以上这两个限制?就是为了保证在子类对象替换父类对象后,语法不会报错。为了保证代码符合里式替换原则。
继承的作用:
1.提高代码的重用性。
2.多态的前提。
两个类能不能发生继承关系的依据是什么 ?
a.主要看有没有“ is a ”关系。
b.在两个类有了is a 关系之后,还要考虑子类对象在替换了父类对象之后,业务逻辑是否变化。如果变化,则不能发生继承关系。
正方形和长方形有 “ is a ” 关系。那我们能不能让正方形类就直接去继承长方形类呢?现在不能了!!
为什么呢? 因为还要考虑业务场景,在特定的业务场景下,正方形能替换了长方形以后,业务逻辑是否变化!
正方形 长方形 那些事儿。
7.组合优于继承
继承:一个类优于另外一个类。
我们已经知道,类和类之间有3中关系:
a.继承
b.依赖
c.关联
我们又知道,其中“关联”可以细分为:
a.组合
b.聚合
所谓的组合,是关系强,聚合是关系弱。
组合优于继承中的组合,其实就是指的关联关系。
需求:制作一个集合,要求该集合能记录曾经加过多少个元素,不是统计某一时刻集合中有多少个元素。
举例:
经计算,结果为6.理由:addAll 回调了add方法。所以,这样的代码没有解决需求。
针对于a包中的问题,addall会回调add方法,我们修改代码如下,把addAll删除掉,不要重写父类Hashset的addAll了,反正父类的addALL本身就会去回调add.
此时,这个代码看起来好像很完美,几乎是满足需求了。问题是: 目前这个代码必须依赖于这样一个事实,就是HashSet的addAll方法必须去回调add方法。
万一将来的jdk版本中, HashSet的addALL实现代码,突然不再回调add方法了,则在将来的这个jdk版本中,我们自定义的这个mySet就被撼动。
比如HashMap 在1.6 1.7 1.8中 换了3次!
针对于以上问题,Myset必须依赖于这样一个事实,addAll必须回调add, 但是jdk未来的版本,不会做这个保证!修改代码如下:我们自己亲自重写addAll,不再让count累加c.size()了,而是保证addAll 一定会回调add.
问题是:
1.如果在新的jdk版本中,Hashset突然多了一个元素加入集合的入口方法,addSome,这个addSome 是我们始料未及的,我们没有重写addSome方法,但在新版本中,我们的Myset也继承了addSome方法,当使用addSome方法添加元素时,根本不会去统计元素的数量。
2.我们重写了addAll方法和add方法,要知道,在整个Hashset的所有方法中,难免有一些其他方法,会依赖于addAll方法会依赖于add方法。我们没头没脑的重写了别人类中的某些方法,就会导致其他依赖于这些方法的方法,出现问题。
修改代码如下:
1.不再重写add和addAll方法。
2.我们额外制作2个代替add和addAll的方法 add2 和addAll2,还要在类中生成API文档,在文档中说明,每当要使用add 和addAll 的时候,都要去调用add2 和addAll2
出现问题:
1.目前这种情况对用户要求有点过分,用户必须看类的api文档,看完了还要乖乖地使用add2 和addAll2.
2.更致命的问题是,就是那么寸,在jdk新版本中,HashSet恰恰多了一个api,叫add2 和addAll2.
继承,已经尽忠了…
针对于上述问题,修改代码如下:
1.MySet 不再继承HashSet .
2.取而代之,让MySet 和HashSet发生关联关系(组合)
由于没有重写add方法,所以,count 不会重新把每个元素计入。
解决了以下问题:
1.没有重写,addALL 和add 问题解决。也就是计数不会出现错误。同时,也不会出现其他方法调用add 和addAll方法时出现错误的问题。
2,.由于组合中无法提供addSome方法,所以addSome方法问题得到解决。
问题:
1.难道以后都不能使用继承了吗?
2.难道以后都不能进行方法重写了吗?
如果父类作者和子类的作者不是同一个人时,不继承。那么父类作者,不知道未来的子类会重写自己的哪个方法。那么子类作者,也不知道未来的父类会加入什么新方法。
如果父类的作者和子类的作者就是同一个人,那就可以放开手脚去使用继承了!自己当然知道,每个方法都是什么作用。作者可以同时控制父类和子类。
我们自己写代码,继承、重写、随便使用。
如果我们仅仅是为了复用代码,而继承别人的类,难免出现“沟通”上的问题。
现实生活中出现的反例:Stack 继承了Vector