里氏替换原则
它是一个与继承相关的原则,如何正确地使用继承呢?假设有一个父类A,和它的两个子类B和C,如果要修改A的话,就有可能会影响到B和C,换言之就是:父类修改时,必须考虑所有的子类,因为所有子类都有可能因为父类的修改出故障。因此为了正确地使用继承,就有了里氏替换原则
里氏替换原则用几句话概括:
- 所有引用了父类对象的地方都可以透明地使用其子类对象来替换而不会有任何异常,反之不成立。
看看下面这个代码
public class LiExchange1 {
public static void main(String[] args) {
new Animal().like();
}
/**
* 运行结果:
* 我喜欢Animal
*/
}
class Animal {
public void like() {
System.out.println("我喜欢" + getClass().getSimpleName());
}
}
class Dog extends Animal {
}
main方法中引用了父类对象,这个时候,我直接将其替换成子类对象,再运行一次:
public class LiExchange1 {
public static void main(String[] args) {
new Dog().like();
}
/**
* 运行结果:
* 我喜欢Dog
*/
}
class Animal {
public void like() {
System.out.println("我喜欢" + getClass().getSimpleName());
}
}
class Dog extends Animal {
}
喜欢所有动物的人,肯定也喜欢小狗,因此引用父类的地方可以直接替换成子类而不出现任何故障;然而喜欢小狗的人,不一定会喜欢所有动物,从这里也可以理解里氏替换原则的本意。并且这个例子中,Dog类里面没有重写Animal类的like方法,如果重写了(假设给他加个参数),那么再直接new Dog().like();就会报错,因为like有参数了,解决方案如下:
- 子类尽量不要重写父类的方法
- 子类如果必须要重写父类的方法,可以取消继承,并通过依赖、聚合、组合来解决问题
- 也可以让子类和父类共同去继承一个更大的基类,以此减小两个类的耦合性
开闭原则
定义:一个软件实体、类、模块或者函数,应该对扩展(对提供方)开放,对修改(使用方)关闭。用抽象构建框架,用实现扩展细节;尽量通过扩展(增加一个类、增加一个方法)来实现改变,而不是修改。
来看一个不满足开闭原则的Demo
public class OpenClose1 {
public static void main(String[] args) {
ShoppingCar shoppingCar = new ShoppingCar();
shoppingCar.buy(new Book());
shoppingCar.buy(new Beef());
/**
* 运行结果:
* 买了书
* 买了牛肉
*/
}
}
class ShoppingCar{//购物车类(使用方、客户端)
public void buy(Goods goods){//接收Goods对象,根据mNum来买相应的Goods
if(goods.mNum==1){
System.out.println("买了书");
}else if(goods.mNum==2){
System.out.println("买了牛肉");
}
}
}
class Goods{//商品类,是基类
int mNum;//商品编号
}
class Book extends Goods{//书类
Book(){
super.mNum = 1;
}
}
class Beef extends Goods{//牛肉类
Beef(){
super.mNum = 2;
}
}
为什么说这个代码不满足开闭原则?如果还要再买一个电脑,这个时候要增加一个Computer类(从提供方扩展的,这个没毛病)然后要修改ShoppingCar类里面buy方法的if-else if逻辑(对使用方也修改了,这就有问题了),因此上面这个例子就没有满足开闭原则,要对它进行改进如下:
public class OpenClose1 {
public static void main(String[] args) {
ShoppingCar shoppingCar = new ShoppingCar();
shoppingCar.buy(new Book());
shoppingCar.buy(new Beef());
/**
* 运行结果:
* 买了书
* 买了牛肉
*/
}
}
class ShoppingCar {//购物车类(使用方、客户端)
public void buy(Goods goods) {//接收Goods对象,根据mNum来买相应的Goods
goods.buy();
}
}
abstract class Goods {//商品类,是基类
abstract void buy();
}
class Book extends Goods {//书类
@Override
void buy() {
System.out.println("买了书");
}
}
class Beef extends Goods {//牛肉类
@Override
void buy() {
System.out.println("买了牛肉");
}
}
if-else逻辑不要了,商品编号不要了,把商品这个基类定义为抽象类,并增加buy抽象方法,现在看看,再买一台电脑,代码改怎么写:
public class OpenClose1 {
public static void main(String[] args) {
ShoppingCar shoppingCar = new ShoppingCar();
shoppingCar.buy(new Book());
shoppingCar.buy(new Beef());
shoppingCar.buy(new Computer());
/**
* 运行结果:
* 买了书
* 买了牛肉
* 买了电脑
*/
}
}
class ShoppingCar {//购物车类(使用方、客户端)
public void buy(Goods goods) {//接收Goods对象,根据mNum来买相应的Goods
goods.buy();
}
}
abstract class Goods {//商品类,是基类
abstract void buy();
}
class Book extends Goods {//书类
@Override
void buy() {
System.out.println("买了书");
}
}
class Beef extends Goods {//牛肉类
@Override
void buy() {
System.out.println("买了牛肉");
}
}
class Computer extends Goods{
@Override
void buy() {
System.out.println("买了电脑");
}
}
购物车这个使用方里面不需要再做任何修改,只需要在提供方里增加一个Computer类并实现抽象buy方法就ok了,完全满足了开闭原则
迪米特法则(最少知道原则)
定义:只与直接朋友通信
先来看看什么是直接朋友:
public class Dimit1 {
public Scanner mScanner = new Scanner(System.in);
}
public class Dimit1 {
public void a(StringBuilder stringBuilder){
}
}
public class Dimit1 {
public Integer b(){
return 100;
}
}
以上三段代码
- 第一段代码中,一个Scanner类的对象作为了Dimit1这个类的成员变量,就称Scanner对象是Dimit1对象的直接朋友
- 第二段代码中,一个StringBuilder类的对象作为Dimit1这个类的一个成员方法的参数,也称StringBuilder对象是Dimit1对象的直接朋友
- 第三段代码中,一个Integer类的对象作为Dimit1这个类的一个成员方法的返回值,也成Integer对象是Dimit1对象的直接朋友
如果是这样的:
public class Dimit1 {
public void c(){
Scanner sc = new Scanner(System.in);
}
}
Scanner对象在某个方法内被声明,那Scanner对象就不是Dimit1对象的直接朋友,对Dimit1来说,Scanner是陌生的类,陌生的类,最好不要以局部变量的形式出现在类的内部。
因此,迪米特法则理解出来的意思就是:被依赖的类不管如何复杂,都尽量在逻辑封装在类的内部,对外只提供public方法,不再泄露任何信息。我想用某个类的功能,那么就只要调它对外提供的public方法就行了,如果这个类没有我想要的功能的方法,那就不去依赖这个类。
下面举个例子,是一个不满足迪米特法则的例子:
import java.util.ArrayList;
import java.util.List;
/**
* @authorADMIN 创建时间:2020/4/23
*/
public class Dimit1 {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllStuAndTea(new StudentManager());
/**
* 运行结果:
* *******************老师如下*********************
* 老师:0
* 老师:1
* 老师:2
* 老师:3
* 老师:4
* *******************学生如下*********************
* 学生:0
* 学生:1
* 学生:2
* 学生:3
* 学生:4
* 学生:5
* 学生:6
* 学生:7
* 学生:8
* 学生:9
*/
}
}
class Student {//学生类
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Teacher {//老师类
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
/**
* 该类有个方法返回值有Student类型,
* 因此StudentManager类和Student类是直接朋友,
* 该类的所有代码都符合迪米特法则
*/
class StudentManager {//学生管理类
//增加一些学生并返回list
public List<Student> getStu() {
List<Student> list = new ArrayList<Student>();
for (int i = 0; i < 10; i++) {
Student student = new Student();
student.setId(i);
list.add(student);
}
return list;
}
}
/**
* 由于没有给教师管理类,而这个学校管理类,既能管理学生,
* 又能管理教师,索性就用这个类管理教师。getTea方法的返回值类型
* 有Teacher,因此SchoolManager类和Teacher类是直接朋友,但是,
* 和Student类并不是直接朋友
*/
class SchoolManager {//整个学校的大的管理类
//增加一些教师并返回list
public List<Teacher> getTea() {
List<Teacher> list = new ArrayList<Teacher>();
for (int i = 0; i < 5; i++) {
Teacher teacher = new Teacher();
teacher.setId(i);
list.add(teacher);
}
return list;
}
public void printAllStuAndTea(StudentManager studentManager) {//将所有学生和老师都打印出来
List<Teacher> teacherList = getTea();
System.out.println("*******************老师如下*********************");
for (int i = 0; i < teacherList.size(); i++) {
System.out.println("老师:" + teacherList.get(i).getId());
}
//代码到这里一直都遵守迪米特法则
//现在开始,下面这行代码有问题了,本类和Student类不是直接朋友,因此Student类型不能作为局部变量
//没有遵循迪米特法则
List<Student> studentList = studentManager.getStu();
System.out.println("*******************学生如下*********************");
for (int i = 0; i < studentList.size(); i++) {
System.out.println("学生:" + studentList.get(i).getId());
}
}
}
从这个代码看出,打印所有学生的功能,应该是要让StudentManager来实现,既然我和StudentManager是直接朋友,你怎么实现,不要告诉我,我只管来调用就OK了。然后按照迪米特法则进行修改:
import java.util.ArrayList;
import java.util.List;
/**
* @authorADMIN 创建时间:2020/4/23
*/
public class Dimit1 {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllStuAndTea(new StudentManager());
/**
* 运行结果:
* *******************老师如下*********************
* 老师:0
* 老师:1
* 老师:2
* 老师:3
* 老师:4
* *******************学生如下*********************
* 学生:0
* 学生:1
* 学生:2
* 学生:3
* 学生:4
* 学生:5
* 学生:6
* 学生:7
* 学生:8
* 学生:9
*/
}
}
class Student {//学生类
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Teacher {//老师类
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
/**
* 该类有个方法返回值有Student类型,
* 因此StudentManager类和Student类是直接朋友,
* 该类的所有代码都符合迪米特法则
*/
class StudentManager {//学生管理类
//增加一些学生并返回list
public List<Student> getStu() {
List<Student> list = new ArrayList<Student>();
for (int i = 0; i < 10; i++) {
Student student = new Student();
student.setId(i);
list.add(student);
}
return list;
}
public void printAllStu(List<Student> studentList){
studentList = getStu();
System.out.println("*******************学生如下*********************");
for (int i = 0; i < studentList.size(); i++) {
System.out.println("学生:" + studentList.get(i).getId());
}
}
}
/**
* 由于没有给教师管理类,而这个学校管理类,既能管理学生,
* 又能管理教师,索性就用这个类管理教师。getTea方法的返回值类型
* 有Teacher,因此SchoolManager类和Teacher类是直接朋友,但是,
* 和Student类并不是直接朋友
*/
class SchoolManager {//整个学校的大的管理类
//增加一些教师并返回list
public List<Teacher> getTea() {
List<Teacher> list = new ArrayList<Teacher>();
for (int i = 0; i < 5; i++) {
Teacher teacher = new Teacher();
teacher.setId(i);
list.add(teacher);
}
return list;
}
public void printAllStuAndTea(StudentManager studentManager) {//将所有学生和老师都打印出来
List<Teacher> teacherList = getTea();
System.out.println("*******************老师如下*********************");
for (int i = 0; i < teacherList.size(); i++) {
System.out.println("老师:" + teacherList.get(i).getId());
}
studentManager.printAllStu(studentManager.getStu());
}
}
合成复用原则
定义:尽量用合成/聚合,而不是继承,也就是说,A类想要用B类的方法,最容易想到的就是让A去继承B类。但是,最好不要这样。因为A和B之间或许并没有什么联系,一旦继承,两个类的耦合度增加,B类以后如果做出修改,就有可能对A类造成影响。因此,A类想用B类的方法,尽量采用合成/聚合的方式。
这个法则比较简单,直接来个UML类图应该就能理解了。