面向对象更重要的是思想,具体的代码使用点很简单,我们先介绍几个概念,使用点之后一起介绍
面向对象三大特性-----封装、继承、多态是面向对象的中心(有的人会把抽象也加上),我们分别介绍一下
字面意思就是把数据封装起来,不让外部进行访问。封装的是什么数据,自然就是你不想让别人知道的,比如:杨幂的年龄,罗志祥的运动量,马云的资产及怎么赚到的等等。对应到程序那就是隐藏对象内部的复杂性,只对外公开简单的方法,便于外界调用。
具体操作就是将属性进行私有化,提供一个公共的方法用于属性的访问,也就是getter/setter方法,而且访问方法里面可以实现更加复杂细致的内部逻辑判断,比如访问美女的年龄,如果超过30就说自己20。
封装的特性可以保护数据的安全性:在面向过程设计中,都是围绕数据结构和算法展开的,数据结构是公开的,任何人都能访问。万一哪天要离职的人和刚入职的新人有意无意将数据进行访问并将其修改,如果修改错乱了之后,程序业务必然会出错或者崩溃。
? 而在面向对象设计中,由于类将属性进行了封装,不会暴露具体的属性,也就只能通过提供的外部方法进行访问,这样操作属性的手段是统一的,不会造成数据错乱。
还可以隔离复杂度:在代码中可以将某个代码块变成方法,抽象出某些工具类,供不同的程序进行调用。
使用的时候直接调用对应的功能方法,不用关心具体的实现。就比如现实世界开空调,你只按了一下遥控器,就启动了空调制冷功能。而具体制冷的过程实现你没关心过。
用一段代码演示一下有封装和无封装的例子
//顾客
public class Customer {
String name;
double balance; //余额
public Customer() {
}
public Customer(String name, double balance) {
this.name = name;
this.balance = balance;
}
}
//收银员
class Cashier {
public static void main(String[] args) {
Customer customer = new Customer("jack", 100);
double price = 50;
if (customer.balance > price) {
//由于余额是公开的,收银员直接访问余额多少钱然后进行操作。
//如果你的余额很多,有些图谋不轨的人肯定就针对上你了(把你的钱盗走)
customer.balance -= price;
System.out.println("付款成功!");
} else {
System.out.println("付款失败!");
}
System.out.println("剩余:" + customer.balance);
}
}
而面向对象的思维的代码如下:
//顾客
public class Customer {
private String name;
private double balance;
public Customer() {
}
public Customer(String name, double balance) {
this.name = name;
this.balance = balance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public boolean payment(double money) {
if (money > balance) {
return false;
} else {
balance -= money;
return true;
}
}
public void show(Customer customer) {
System.out.println("剩余:" + customer.getBalance());
}
}
//订单
public class Order {
private String name;
private double price;
public Order() {
}
public Order(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
//收银员
public class Cashier {
private double money;
public Cashier() {
}
public Cashier(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//扣款
public boolean deduction(Cashier cashier,Order order,Customer customer){
cashier.setMoney(order.getPrice());
boolean result = customer.payment(cashier.getMoney());
if(result){
System.out.println("付款成功!");
}
else {
System.out.println("付款失败!");
}
return result;
}
}
//测试类
public class Test {
public static void main(String[] args) {
Customer customer=new Customer("jack",5000);
Order order=new Order("p40",4488);
Cashier cashier=new Cashier();
cashier.setMoney(order.getPrice());
//这里收银员就只能调用收款方法,方法里面只把订单价格和顾客传入里面,剩下的就只能对应的角色才能看到
cashier.deduction(cashier,order,customer);
customer.show(customer);
}
}
封装的作用:保护数据的安全性和隔离复杂度
单从字面意思理解的话,可能会有些歧义,先和生物学做类比:程序中的继承就相当于生物学中的遗传相似的地方,而生物学中的遗传变异的部分对应程序中就是重写或者子类添加的属性和方法。
在现实的认知中,继承大多数指代父母遗留的事业、财产之类的,比如:李泽钜继承李嘉诚的事业。而对应到面向对象设计中,把“继承”理解成“遗传”就能更好的理解,生物学中的遗传都能理解,比如:儿子和父亲某些地方很像,比如五官的某些特点等等,有像的地方就有异的地方也就是“变异”。对应程序中也是如此,多个类有共同的属性和方法时,就可以抽取成子类和父类。然后让子类继承父类,就代表子类与父类具有某些相同的属性和方法;而那些异的地方就需要子类进行对应的更改。
另外,之前说过类是对对象的抽象,而现在的继承是对类的抽象,抽象和继承是前后衔接的关系,先有抽象,通过抽象得出类,后通过继承来表达抽象结果
对前面说的举个例子:教师、员工、学生有共同的特点,比如:都有姓名、年龄,都要吃饭睡觉等。但是也有不同的地方,教师有教师编号,要教学;学生有学生编号,要学习;员工有员工编号,要工作。
教师:
属性:姓名、年龄、教师编号
方法:吃饭、睡觉、教学
员工:
属性:姓名、年龄、员工编号
方法:吃饭、睡觉、工作
学生:
属性:姓名、年龄、学生编号
方法:吃饭、睡觉、学习
优化一下:它们首先都可以抽象为人类,然后具有相同属性和方法的地方就可以抽取到人类中,不同的地方就单独写一个子类继承父类,这样就继承了父类所有的属性和方法
人类:
属性:姓名、年龄
方法:吃饭、睡觉
教师继承人类:
属性:教师编号
方法:教学
员工继承人类:
属性:员工编号
方法:工作
学生继承人类:
属性:学生编号
方法:学习
就把姓名、年龄、吃饭、睡觉抽取为父类,然后教师、员工、学生继承人类就有了姓名、年龄、吃饭、睡觉信息,然后可以子类对应更改属性、方法
//人类
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void eat(){
System.out.println("eat");
}
public void sleep(){
System.out.println("sleep");
}
}
//教师继承人类
public class Teacher extends Person{
private Integer teacherId;
public Integer getTeacherId() {
return teacherId;
}
public void setTeacherId(Integer teacherId) {
this.teacherId = teacherId;
}
public void teach(){
System.out.printf("%s教学,编号:%d",super.getName(),teacherId);
}
}
//学生继承人类
public class Student extends Person{
private Integer studentId;
public Integer getStudentId() {
return studentId;
}
public void setTeacherId(Integer studentId) {
this.studentId = studentId;
}
public void study(){
System.out.printf("%s学习,编号:%d",super.getName(),studentId);
}
}
//员工继承人类
public class Employee extends Person {
private Integer empno;
public Integer getEmpno() {
return empno;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public void work() {
System.out.printf("%s工作,编号:%d,年龄:%d", super.getName(), empno, super.getAge());
}
}
//测试类
public class Test {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.setTeacherId(42);
teacher.setName("James");
teacher.teach();
System.out.println();
Student student = new Student();
student.setTeacherId(18);
student.setName("张三");
student.study();
System.out.println();
Employee emp = new Employee();
emp.setEmpno(1001);
emp.setName("smith");
emp.setAge(18);
emp.work();
}
}
/*
执行结果:
James教学,编号:42
张三学习,编号:18
smith工作,编号:1001,年龄:18
*/
再举个子类重写的例子
//体育比赛,会有很多项目,先只写一个大概的方法,可以理解成占位作用
public class SportsCompetition {
private String name;
public void playSports(String name){
System.out.printf("进行%s项目比赛!",name);
}
}
//田径比赛,重写父类的playSports方法,等待实际使用的时候传此方法的实参
public class TrackFieldCompetition extends SportsCompetition{
@Override
public void playSports(String name) {
//此方法表示调用父类的方法,但传入的是子类自己的参数
super.playSports(name);
}
}
//球类比赛,同样重写父类的playSports方法,等待实际使用的时候传此方法的实参
public class BallGame extends SportsCompetition {
@Override
public void playSports(String name) {
super.playSports(name);
}
}
//测试类
TrackFieldCompetition competition=new TrackFieldCompetition();
competition.playSports("田径");
BallGame competition2=new BallGame();
System.out.println();
competition.playSports("球类");
/*
执行结果:
进行田径项目比赛!
进行球类项目比赛!
*
总结:继承就是表示子类和父类的关系,当多个类有相同的属性和方法时,就对应的抽取成父类。然后子类继承父类,就拥有父类的所有信息,即:子类与父类是is-a的关系
继承的好处:1. 提高代码的复用性;2. 提高代码的可维护性和扩展性;是多态的前提
还是和生物学做类比,生物中多态指生物体或物种有许多不同的展现形式或阶段,在面向对象中的含义其实也是如此,就是指调用同一个方法,不同的对象产生不同的结果。上面的体育比赛案例其实就可以运用多态,我们先用多态重新测试一下
//父类引用指向子类对象
SportsCompetition competition1=new TrackFieldCompetition();
competition1.playSports("田径");
SportsCompetition competition2=new BallGame();
competition3.playSports("球类");
SportsCompetition中定义的playSports方法,传入了一个参数name,但是实际使用的时候,还是要看传入的实参是TrackFieldCompetition对象还是BallGame对象
再写一个案例
public class Animal {
public void talk() {
System.out.println("xxx叫!");
}
}
public class Dog extends Animal {
@Override
public void talk() {
System.out.println("Dog...wangwangwang~~~");
}
}
public class Cat extends Animal {
@Override
public void talk() {
System.out.println("Cat....miaomiaomiao~~~");
}
}
测试类
public class Test {
public static void main(String[] args) {
test(new Dog());//调用的实参是Dog对象
test(new Cat());//调用的实参是Cat对象
}
//形参是animal对象,根据传进的实参是哪个对象判断调用哪个方法
public static void test(Animal animal){
animal.talk();
}
}
/*
执行结果:
Dog...wangwangwang~~~
Cat....miaomiaomiao~~~
*
如果不用多态的话,代码就变成这样
public class Test2 {
public static void main(String[] args) {
test(new Dog());
test(new Cat());
}
public static void test(Dog dog){
dog.talk();
}
public static void test(Cat cat){
cat.talk();
}
}
上面为啥test方法名可以相同,那是因为形参列表不同,构成了重载。如果不用重载的话方法每次都要起不一样的比如testDog,testCat等等:,万一有100个动物呢?那起名字就浪费点时间
显然Test的代码比Test2的代码要简洁一些,而且如果用Test2的代码的话,如果有Pat,Chicken等动物子类的话,都需要添加对应的方法,即:再添加对应的test方法,这样代码重复度就有点高了,违反了面向对象的设计原则:开闭原则(面向扩展开放,面向修改关闭),如果用Test的代码增加新的子类时,调用者不用更改代码就能适用,即:不用更改test方法的代码。
总结一下:
多态的要素:继承,重写,父类引用指向子类对象
多态的好处:实现代码的通用性;提高程序的可扩展性
多态的应用:父类当做方法的形参,传入具体的子类的对象;父类当做方法的返回值,返回的是具体的子类的对象
接口当做方法的形参,传入具体的实现类的对象;接口当做方法的返回值,返回的是具体的实现类的对象
像上面的体育比赛类和Animal类本身没有什么作用也没必要进行实例化,就是给子类提供了一个模板,也可以说是占位。
那么其实可以设计成抽象类,然后让子类继承这样的抽象类
//体育比赛,abstract class就表示这个类是抽象类
public abstract class SportsCompetition {
//方法加abstract修饰,即:这是一个抽象方法,只包含定义方法,没有实现,即方法没有大括号。
public abstract void playSports(String name);
}
//田径比赛,实现SportsCompetition中定义的playSports方法
public class TrackFieldCompetition extends SportsCompetition {
@Override
public void playSports(String name) {
System.out.printf("进行%s项目比赛!",name);
}
}
//球类比赛,也实现SportsCompetition中定义的playSports方法
public class BallGame extends SportsCompetition {
@Override
public void playSports(String name) {
System.out.printf("进行%s项目比赛!",name);
}
}
//测试结果和上面一样
再修改一下Animal的那个案例
public abstract class Animal {
public abstract void talk();
}
剩下的Dog和Cat与上面的代码相同,使用也不变。最后的执行结果也相同
总结:抽象类的作用就是为子类提供了一个抽象模板,子类重写对应的抽象方法然后可以在它的基础上进行扩充实现。
说到接口,在现实都见到过各种各样的USB接口,比如:
还有其它各式各样的接口,那到底什么是接口呢?
接口英译interface,而interface翻译过来还有界面,端口,介面的意思,综合一下你就可能理解了,我们小学的时候数学老师就教过我们点连成线,线连成面;你看上面图片的接口是不是由许多点组成,这些点其实就是一个个功能点;至于界的含义呢,也很简单,就是组成面之后如果单独存在就没意义了,一定需要与设备进行连接才能发挥出特定的功能,在这里就可以把这个用来连接设备的东东看作是连接的媒介,把这两个定义分析之后组成一起就是界面了。翻译为接口可能主要原因是:它是用来连接设备的,以此产生特定的功能,比如充电接口与手机连接就是开启了充电功能,在程序中就是调用了充电的方法。
我们一句话再阐述一下接口的概念:是相关交互功能点定义的集合。再分别解释一下
举个例子
public interface USB {
//传输数据
public void transferData();
//规范设备连接
public void standardizeDeviceConnection();
//供电
public void poweredBy();
}
对应一下:相关功能点的集合就是上面的接口定义的一个个方法,而且符合USB协议
? 交互就是访问修饰符是public,即符合这个接口规范,实现此接口的对象都可以访问
? 定义就是一个个的功能只包含定义方法,没有实现,即方法没有大括号。也就是抽象方法
针对接口必定有实现,比如U盘实现USB接口规范,就有了USB所定义的方法能力,即has-a的关系。
//U盘实现USB接口,就具备了接口定义的传输数据,规范设备连接,供电的能力,实现类针对自身进行改动即可
public class UDisk implements USB {
@Override
public void transferData() {
System.out.println("U盘传输文件~~~");
}
@Override
public void StandardizeDeviceConnection() {
System.out.println("U盘连接设备~~~");
}
@Override
public void poweredBy() {
System.out.println("U盘与设备进行供电");
}
}
总结:接口就是一个规范、能力(这点也是接口的核心,随着代码量时间的积累会掌握更加深刻)
拓展:接口和抽象类乍一看达到的目标差不多,但差异的根本就是:抽象类脱离不了继承,而继承是is-a,关注的是类之间的关系,而接口是has-a的关系,更关注规范和实现。设计的时候就看类之间是什么关系。
下图就简单描述了接口的has-a和抽象类的is-a的区别
原文:https://www.cnblogs.com/hanyu-2020/p/14051154.html