面向对象本质

Author JYBlog

Email [email protected]

本博客GitHub开源(jcNaruto/JYBlog),

JYBlog每周五,周三公众号等多平台同步更新,欢迎讨论交流~

#.补充

面向过程在开发中也可以使用结构体等操作来实现高内聚低耦合,也可以写出遵循开闭原则的代码,抽象的看面向对象,有点像形而上的编程思维,也是编程范式之一,也就是在编写动态迭代的软件工程中面向对象的编程思维会提高工程可扩展性,维护性,甚至降低工作量,高手使用面向过程一样也可以写出扩展性,可维护性很好的代码(工作量会大些,但可能会带来性能或其他方面的收获),再者映射到具体的面向对象的语言,例如Java语言实现了面向对象的编程思维,使得开发者更加方便的接触面向对象思维。

1.概述

面向过程将解决问题的步骤写在函数里面,让计算机有步骤的按顺序解决问题,在大型的软件工程开发中这样的过程化会造成维护困难,因为在软件开发中需求的改动是常见的事,例如AB两人同时开发一个软件,要旋转三个图形,正方形,三角形,圆形,A采用面向过程开发,B采用面向对象开发

//A开发
rotate(shapeName){
    //旋转
}
//B开发
class Square{
    ....
    public void rotate(){
        //旋转
    }
}
class Triangle{
    ....
    public void rotate(){
        //旋转
    }
}
class Round{
    ....
    public void rotate(){
        //旋转
    }
}

此时你会发现面向过程的优越性,代码量要少的多,但是现在需求改变,要添加一个六边形,且六边形的旋转方式与之前三个不一致

//A开发新增的需求
rotate(shapeName){
    if(hexagon){
        //六边形旋转
    }else{
       //旋转 
    }
}

面向过程在需求动态改变的时候要修改已测试过的代码,也就是随着需求的每次更改都要重新测试,十分耗费资源

例如互联网中产品的迭代伴随着一生,比如某产品到现在还要因为迭代而测试多余的代码会使得整个项目变得异常臃肿缓慢

//B开发新增的需求
.....
class Hexagon{
     ....
    public void rotate(){
        //旋转
    }
}

面向对象只需要在增加一个类就行了,此时面向对象的优越性就体现出来,可以不修改已近测试过的代码,需求是会不停的更改的,每改一次就只需要测试新增的类即可

也就是说在软件工程动态的迭代的过程中,面向对象的优越性体现出来,但是此时还存在一个问题,面向对象的代码量比面向过程要高的多,为了解决这个问题可以使用面向对象的继承特性,同时为了降低程序的耦合性和提高代码的内聚性面向对象中还有封装以及提高了程序可扩展性的多态。

2.封装(Encapsulation)

class Student{
    public int age;
}

class Teacher{
    
    public void score(){
        Student student = new Student();
 		student.age = -99;//不安全       
    }
}

以上就是不封装的结果,完全脱离实际业务情况,并且student的age filed暴露给了所有的其他对象,为了解决此种情况,一般推荐将你的实例变量标记为private,然后对每个filed提供唯一的public的setter(设置),getter(获取)方法,强制其他的对象在设置或者获取该field时一定要经过对应的setter,getter方法,并且在setter或者getter方法内部你还可以根据业务需求进行一定的逻辑判断

class Student{
    private int age;
    
    public void setAge(int age){
    	if(age > 105 || age <0){
            thorw new RuntimeException("age invalid");//在set方法内做业务逻辑校验
    	}
        this.age = age;
    }
    
    public int getAge(){
        return age;
    }
}

class Teacher{
    
    public void score(){
        Student student = new Student();
 		student.setAge(12);     
    }
}

注意

  1. getter,setter方法的命名是有规范的(规范就是最好遵守,不遵守也可以,但是可能会付出高昂的代价,软件工程中可能就是要自己造所有的轮子),xxx getPropertyName(), void setPropertyName(xxx propertyName),如果是filed 是boolean类型get方法命名为 boolean isPropertyName(),并且有getter和setter方法的filed可以称之为属性
  2. 实际的开发中,很少会在setter和getter方法做校验,因此有人便提出可以省略去getter和setter直接将filed设置为public,这是错误的观点,因为即使这两个方法只是直接返回或者设置了filed的值,但还是为field提供了统一访问路径,假如某一天新需求是要对某个参数做校验,直接在setter方法中设置即可,但是如果直接外暴filed的public属性,无数的xxx.field很难在进行校验。也就是说getter,setter方法提高了程序的内聚性降低了模块的耦合性,即使没有校验逻辑也要加上。如果一个class的所有属性对应了setter,getter方法,则该class可以称之为Java bean,多用于传输数据。
  3. 一般敏感数据或者外部不不必感知的复杂逻辑方法也要使用private封装,以降低耦合度,降低使用成本。

3.继承(Inheritance)

回到之前开发图形旋转的例子,多个类中有重复的rotate方法,此时就可以抽取一个父类,父类中具有那个rotate方法,子类通过extends就可以继承父类,然后具有那个rotate方法,但是六边形的旋转方式不一样,这时就可以通过@Override来重新实现自己的rotate方法。

注意:

  1. 继承表面上看是代码复用技术使模块具备复用性同时也在子父类之间构建了某种协议,但普通类继承和抽象类继承和interface implements配合在一起使用,可以用来设计出规范性,复用性很强的类架构。

  2. 今后如果在更改被抽取的代码的话,只需更改这一处即可,并且只需要重新编译父类即可。

  3. java 单继承,因为要避免多继承的致命方块问题,但是采用了接口来弥补了单继承的不足。

  4. super表示父类,super.filedName访问父类filed,super()为调用父类构造方法,任何类的构造方法第一行都是这个,如果没有编译器会自动加上。

  5. 继承有一个局限就是子类无法访问父类的private 字段或者方法,改成protected,使其可以让子类或者子子类访问。

  6. Object是Java中的总父类,是对万事万物的抽象

  7. 区分组合和继承,组合就是将引用类型变量作为field,is 用继承,has用组合,通过李氏替换原则判断是否满足is关系,既任何父类出现的地方对应的子类都能够出现,成功编译运行。

  8. final修饰class和修饰方法对继承的影响。

  9. 可以将子类向上转型为父类,称为向上转型,也可以将父类转为子类,但可能会报错,因为子类扩展的方法父类没有,向下转型的时候可以用instanceof做判断运算符左边的实例是否为右边的类型或者为右边的子类

    class Person{
        public int age;
        public String name;
        
        public void say(){
            System.out.println(age + "=====" + name);
        }
    }
    
    class Student extends Person{
        public int score;
        
        @Override
        public void say(){
            System.out.println(age + "=====" + name + "=======" + score);
        }
    }
    
    public class InheritTest {
    	public static void main(String[] args){
            Person upStudent = new Student();//向上转型,ok
            Student downStudent1 = new Person();//向下转型,ClassCastException
            
            if(upStudent instanceof Student){//instanceof 运算符,可以判断左边的实例是否为右边的类型或者为右边的子类   
                Student downStudent2 = (Student)upStudent;//此时向下转型成功
            }
            
            //调用的是Person还是Student的say方法
            upStudent.say();
            //实际上调用的是Student的方法,这就是多态
    	}
    }
    

    继承的使用成本十分低,但是这带来一个负面问题,滥用继承,出现方法污染和方法爆炸,方法污染是指父类具备的功能传给子类,但是子类不具备该功能,方法爆炸是指某个子类方法过多,不宜与代码编写和调式,因此谨慎使用继承,避免滥用

4. 多态(Polymorphic)

子类向上转型之后,子类同时也覆写了对应的方法,此时调用的是子类中的方法,但是变量的标识符确实父类类型的,因此得出结论Java的实例方法调用是基于运行时的实际类型的动态调用,而不是申明的变量类型,这就是多态。其含义就是运行期动态的决定调用的是子类的方法还是父类的方法。

多态强大的功能允许不修改父类代码和业务逻辑代码,就通过扩展子类来实现功能的扩展。

举个栗子

不同学院的保研排名计算方式不一致,各个学院有各个学院的算法

class Push{
    public int[] scores;
    
    public void getFinalScore(){
        System.out.println(scores*0.1 + scores*0.4 + scores*0.5);
    }
}

class MathStudent extends Push{
    @Override
     public void getFinalScore(){
        System.out.println(scores*0.9 + scores*0 + scores*0.1);
    }
}

class CSStudent extends Push{
    @Override
     public void getFinalScore(){
        System.out.println(scores*0.3 + scores*0.4 + scores*0.3);
    }
}

public class Caculator{
    public void getFinalScores(Push[] pushs){
        for(Push push : pushs){//利用多态,getFinalScores只和Push父类相关,当要新增某个学院的学生时只需要,新增子类并实现其getFinalScore方法即可,并将其实例传入,其他的代码都不需要修改
			push.getFinalScore();
        }
    }
}

类似的interface和实现类的之间也可以实现多态

多态使模块在复用的基础上具备了更强的可扩展性

注:重载和多态没有任何的关系,重载根本没有覆盖任何方法,并且重载在编译时期已经可以确定要调用的方法,谈不上动态!!!

5. 总结

面向对象三大特性

  1. 封装,提高程序内聚,降低耦合,降低程序维护使用成本。
  2. 继承,使模块间可以复用,少写代码,是多态的基础,同时在子父类之间产生了一种协议。
  3. 多态,使模块在复用的基础上具备了更强的可扩展性。

6.面向对象与抽象

  1. 面向对象就是通过对象把现实世界映射到计算机模型的一种编程方法,是抽象思维的一种体现。

  2. 抽象是计算机科学中及其重要的一种思维。

​ 2.1 抛弃细节,直接使用现成的api(指令集是CPU的抽象,编程时并不需要了解CPU底层的集成电路细节,直接使用指令集即可,大大降低了计算机的使用成本)。

​ 2.2 面向对象中抽象分为归纳演绎,归纳:从具体到本质,个性到共性,演绎:从本质到具体,共性到个性。

7.致谢

  1. 码出高效Java开发手册 杨冠宝(孤尽) 高海慧(鸣莎)
  2. HeadFirst Java Bert Bates Kathy Sierra

你可能感兴趣的:(面向对象本质)