面向对象的三大特性和五大原则

焦旭峰 于 June 7, 2021

面向对象的三大特性和五大原则

面向对象的三大特性和五大原则是非常重要的内容,我们需要在使用面向对象编程中结合自己的理解,从实际生活中的例子出发,去思考三大特性和五大原则。

1、三大特性

1.1、封装

 封装就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

  封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。

例:在java语言中,可以通过private关键字修饰成员变量,达到属性私有化的目的,同时对外提供简单的接口比如:成员变量的set和get方法。

package com.bjpowernode.domain;

public class Student {

    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

  面向对象就是使用程序处理事情时以对象为中心去分析问题,与面向过程不同,面向过程关心处理的逻辑、流程等问题,而不关心事件主体。而面向对象即面向主体,所以我们在解决问题时应该先进行对象的封装(对象是封装类的实例,比如张三是人,人是一个封装类,张三只是对象中的一个实例、一个对象)。比如我们日常生活中的小兔子、小绵羊都可以封装为一个类。

  比如兔子的属性是两只耳朵、四只腿、一双眼睛、三瓣嘴等;行为(功能)有跑、跳、吃素等。定义兔子类时,属性定义为成员变量,行为定义为方法。

1.2、继承

​ 继承机制允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。类似下面这个图:

image

​ 我们在上面已经封装了兔子这个类,其他动物也一样可以进行封装。在封装过程中我们发现兔子、绵羊这两个类具有相似的功能或特性如吃草,所以我们可以抽取共有特征和方法形成高一层的类,如这里的食草动物、食肉动物。继承之间是子父类的关系。继承机制可以很好的描述一个类的生态,也提高了代码复用率

​ 在实际应用中,凡是采用“is a”能描述的,都可以继承。在Java中Object类是所有类的超类

例如:人类是父类,学生类和工人类都是人类的子类。

//人类
class Person{
    String name;
    int age ;
}
//学生类
class Student extends Person{
    void study(){
        System.out.println("student study..." + age);
    }
}
//工人类
class Worker extends Person{
    void work(){
        System.out.println("worker work..." + age);
    }
}

1.3、多态

多态是指一个类实例(对象)的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

多态的优点:

  • 1、 消除类型之间的耦合关系
  • 2、可替换性
  • 3、可扩充性
  • 4、接口性
  • 5、灵活性
  • 6、简化性

多态存在的三个必要条件:

  • 继承
  • 重写(子类继承父类后对父类方法进行重新定义)
  • 父类引用指向子类对象

​ 简言之,多态其实是在继承的基础上的。比如说今天我们要去动物园参观动物,那么你说我们去参观兔子、参观绵羊、参观狮子、参观豹子都是对的,但你不能说我们去参观汽车。在这个例子中,子类具有多态性:除了使用自己的身份,还能充当父类。

例:

// 宠物类类
public class Pet{
	// 吃的行为
	public void eat(){
	
	}
}

//猫类
public class Cat extends Pet{
	// 吃
	public void eat(){
		System.out.println("猫咪喜欢吃鱼,吃的很香!!!");
	}
}

// 狗类
public class Dog extends Pet{
	// 吃
	public void eat(){
		System.out.println("狗狗喜欢啃骨头,吃的很香。");
	}
}

// 主人类
public class Master{	
	public void feed(Pet pet){ 
		// 编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找到了,编译通过
		// 运行的时候,底层实际的对象是什么,就自动调用到该实际对象对应的eat()方法上。
		// 这就是多态的使用。
		pet.eat();
	}
}

/*
	测试类
*/
public class Test{
	public static void main(String[] args){
		// 创建主人对象
		Master zhangsan = new Master();
		// 创建宠物对象
		Dog zangAo = new Dog();
		// 主人喂
		zhangsan.feed(zangAo);
		// 创建宠物对象
		Cat xiaoHua = new Cat();
		// 主人喂
		zhangsan.feed(xiaoHua);
	}
}

2、五大原则

2.1、单一职责原则(SRP)

一个类应该有且只有一个去改变它的理由,这意味着一个类应该只有一项工作。

  比如在职员类里,将工程师、销售人员、销售经理这些情况都放在职员类里考虑,其结果将会非常混乱,在这个假设下,职员类里的每个方法都要if else判断是哪种情况,从类结构上来说将会十分臃肿。

2.2、开放封闭原则(OCP)

对象或实体应该对扩展开放,对修改封闭。

扩展即扩展现行的模块,当我们软件的实际应用发生改变时,出现新的需求,就需要我们对模块进行扩展,使其能够满足新的需求!

  更改封闭即是在我们对模块进行扩展时,勿需对源有程序代码和DLL进行修改或重新编译文件!这个原则对我们在设计类的时候很有帮助,坚持这个原则就必须尽量考虑接口封装,抽象机制和多态技术

2.3、里氏替换原则(LSP)

  在对象 x 为类型 T 时 q(x) 成立,那么当 S 是 T 的子类时,对象 y 为类型 S 时 q(y) 也应成立。(即对父类的调用同样适用于子类)

在这个原则中父类应尽可能使用接口或者抽象类来实现!

​ 子类通过实现了父类接口,能够替换父类的使用地方! 通过这个原则,我们客户端在使用父类接口的时候,通过子类实现! 意思就是说我们依赖父类接口,在客户端声明一个父类接口,通过其子类来实现 这个时候就要求子类必须能够替换父类所出现的任何地方,这样做的好处就是,在根据新要求扩展父类接口的新子类的时候而不影响当前客户端的使用

2.4、依赖倒置原则(DIP)

​  高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。具体实现应该依赖于抽象,而不是抽象依赖于实现。

  可以这样理解,上面我举例子的时候先说了兔子和绵羊,然后才推出食草动物。但如果我们继续认识了牛、马等食草动物,我们会发现我们需要不断调整食草动物的描述,这样程序会变得僵化,所以我们不应该让子类依赖于实体,不应该让父类模块依赖于子类模块。所以我们需要将食草动物设计为抽象类,即抽象类或接口。这样下层只需要实现相应的细节而不会影响父类。

2.5、接口隔离原则(ISP)

不应强迫客户端实现一个它用不上的接口,或是说客户端不应该被迫依赖它们不使用的方法,使用多个专门的接口比使用单个接口要好的多!

  比如,为了减少接口的定义,将许多类似的方法都放在一个接口中,最后会发现,维护和实现接口的时候花了太多精力,而接口所定义的操作相当于对客户端的一种承诺,这种承诺当然是越少越好,越精练越好,过多的承诺带来的就是你的大量精力和时间去维护!